chore: del move and update qr with dockerfile
This commit is contained in:
@@ -1,88 +0,0 @@
|
|||||||
# LD2410B Conveyor Counter
|
|
||||||
|
|
||||||
ESPHome прошивка для подсчета товаров на конвейере с помощью mmWave сенсора **Hi-Link LD2410B** + **ESP32**. Данные передаются через **MQTT**.
|
|
||||||
|
|
||||||
## Схема подключения
|
|
||||||
|
|
||||||
```
|
|
||||||
ESP32 DevKit V1 LD2410B
|
|
||||||
──────────────────────────────────
|
|
||||||
GPIO17 (TX) ────► RX
|
|
||||||
GPIO16 (RX) ────► TX
|
|
||||||
GPIO18 ────► OUT (digital motion)
|
|
||||||
3.3V / 5V ────► VCC
|
|
||||||
GND ────► GND
|
|
||||||
GPIO2 ────► LED (status, onboard)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Важно:** Питание LD2410B — **5V** (не 3.3V!). UART логика 3.3V — совместима с ESP32.
|
|
||||||
|
|
||||||
## Как работает
|
|
||||||
|
|
||||||
1. **UART** (256000 baud) — чтение дистанции и энергии движения для каждой "зоны" (gate)
|
|
||||||
2. **GPIO OUT** — быстрый бинарный сигнал присутствия
|
|
||||||
3. **Конечный автомат** определяет момент прохода товара:
|
|
||||||
- `State 0` — ожидание
|
|
||||||
- `State 1` — товар в зоне (energy > threshold)
|
|
||||||
- `State 2` — товар прошел (debounce 300ms) → +1 к счетчику
|
|
||||||
|
|
||||||
## MQTT топики
|
|
||||||
|
|
||||||
| Топик | Описание |
|
|
||||||
|---|---|
|
|
||||||
| `ld2410b/conveyor/sensor/items_count/state` | Общий счетчик товаров |
|
|
||||||
| `ld2410b/conveyor/sensor/moving_distance/state` | Дистанция до цели (cm) |
|
|
||||||
| `ld2410b/conveyor/sensor/trigger_distance/state` | Дистанция в зоне детекции |
|
|
||||||
| `ld2410b/conveyor/text_sensor/counter_status/state` | Строка статуса (state, energy, count) |
|
|
||||||
| `ld2410b/conveyor/button/reset_counter/command` | Сброс счетчика (payload: любая) |
|
|
||||||
| `ld2410b/conveyor/number/energy_threshold/command` | Порог энергии детекции |
|
|
||||||
| `ld2410b/conveyor/number/min_distance_cm/command` | Мин. дистанция зоны (cm) |
|
|
||||||
| `ld2410b/conveyor/number/max_distance_cm/command` | Макс. дистанция зоны (cm) |
|
|
||||||
|
|
||||||
## Настройка порогов
|
|
||||||
|
|
||||||
### Energy Threshold
|
|
||||||
- **По умолчанию:** 50
|
|
||||||
- **Диапазон:** 10–100
|
|
||||||
- Чем выше — тем меньше ложных срабатываний, но нужно больше энергии движения для детекции
|
|
||||||
|
|
||||||
### Min / Max Distance
|
|
||||||
- **По умолчанию:** 75–200 см
|
|
||||||
- Установите диапазон, в котором товары проходят через луч
|
|
||||||
- LD2410B имеет минимальную дистанцию ~75 см (gate 0)
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Установите ESPHome
|
|
||||||
pip install esphome
|
|
||||||
|
|
||||||
# Скомпилируйте и залейте
|
|
||||||
esphome run ld2410b_counter.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## secrets.yaml
|
|
||||||
|
|
||||||
Создайте файл `secrets.yaml` рядом с конфигом:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
mqtt_broker: "192.168.1.100"
|
|
||||||
mqtt_user: "esphome"
|
|
||||||
mqtt_pass: "your_password"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Отладка
|
|
||||||
|
|
||||||
1. Откройте веб-интерфейс ESP (по IP устройства)
|
|
||||||
2. Следите за `Counter Status` — строка вида `State: 1 | E: 65 | D: 120 | Count: 42`
|
|
||||||
3. Если счетчик не увеличивается:
|
|
||||||
- Уменьшите `Energy Threshold`
|
|
||||||
- Проверьте что товар попадает в диапазон `Min/Max Distance`
|
|
||||||
4. Если ложные срабатывания — увеличьте `Energy Threshold` или сузьте диапазон дистанций
|
|
||||||
|
|
||||||
## Ограничения LD2410B
|
|
||||||
|
|
||||||
- **Минимальная дистанция:** ~75 см
|
|
||||||
- **Угол обзора:** ~120° (для сужения используйте физический экран/тубус перед сенсором)
|
|
||||||
- **Не различает** направление движения (для этого нужен LD2450 с трекингом X/Y)
|
|
||||||
- **Макс. скорость конвейера:** зависит от размера товаров и debounce (300ms). Для быстрого конвейера уменьшите debounce в коде.
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
esphome:
|
|
||||||
name: ld2410b-counter
|
|
||||||
friendly_name: "LD2410B Conveyor Counter"
|
|
||||||
|
|
||||||
esp32:
|
|
||||||
board: esp32dev
|
|
||||||
framework:
|
|
||||||
type: arduino
|
|
||||||
|
|
||||||
# Подключение LD2410B по UART
|
|
||||||
uart:
|
|
||||||
tx_pin: GPIO17
|
|
||||||
rx_pin: GPIO16
|
|
||||||
baud_rate: 256000
|
|
||||||
parity: NONE
|
|
||||||
stop_bits: 1
|
|
||||||
|
|
||||||
# GPIO OUT пин для быстрого детектирования движения
|
|
||||||
binary_sensor:
|
|
||||||
- platform: gpio
|
|
||||||
pin: GPIO18
|
|
||||||
name: "LD2410B Motion GPIO"
|
|
||||||
id: motion_gpio
|
|
||||||
device_class: motion
|
|
||||||
internal: true
|
|
||||||
|
|
||||||
ld2410:
|
|
||||||
|
|
||||||
sensor:
|
|
||||||
# Дистанция до движущейся цели
|
|
||||||
- platform: ld2410
|
|
||||||
moving_distance:
|
|
||||||
name: "Moving Distance"
|
|
||||||
id: moving_dist
|
|
||||||
|
|
||||||
# Дистанция до неподвижной цели
|
|
||||||
- platform: ld2410
|
|
||||||
still_distance:
|
|
||||||
name: "Still Distance"
|
|
||||||
id: still_dist
|
|
||||||
|
|
||||||
# Энергия движения в зоне 0 (ближняя зона, 0.75-1.5м)
|
|
||||||
- platform: ld2410
|
|
||||||
g0:
|
|
||||||
move_energy:
|
|
||||||
name: "Gate 0 Move Energy"
|
|
||||||
id: g0_energy
|
|
||||||
internal: true
|
|
||||||
still_energy:
|
|
||||||
name: "Gate 0 Still Energy"
|
|
||||||
id: g0_still
|
|
||||||
internal: true
|
|
||||||
|
|
||||||
# Энергия движения в зоне 1 (1.5-2.25м)
|
|
||||||
- platform: ld2410
|
|
||||||
g1:
|
|
||||||
move_energy:
|
|
||||||
name: "Gate 1 Move Energy"
|
|
||||||
id: g1_energy
|
|
||||||
internal: true
|
|
||||||
|
|
||||||
# Счетчик прошедших товаров
|
|
||||||
- platform: template
|
|
||||||
name: "Items Count"
|
|
||||||
id: items_count
|
|
||||||
icon: "mdi:counter"
|
|
||||||
unit_of_measurement: "items"
|
|
||||||
accuracy_decimals: 0
|
|
||||||
state_class: total_increasing
|
|
||||||
device_class: count
|
|
||||||
|
|
||||||
# Дистанция срабатывания (для отладки)
|
|
||||||
- platform: template
|
|
||||||
name: "Trigger Distance"
|
|
||||||
id: trigger_dist
|
|
||||||
icon: "mdi:ruler"
|
|
||||||
unit_of_measurement: "cm"
|
|
||||||
accuracy_decimals: 0
|
|
||||||
update_interval: 0.5s
|
|
||||||
|
|
||||||
# MQTT
|
|
||||||
mqtt:
|
|
||||||
broker: !secret mqtt_broker
|
|
||||||
username: !secret mqtt_user
|
|
||||||
password: !secret mqtt_pass
|
|
||||||
topic_prefix: ld2410b/conveyor
|
|
||||||
|
|
||||||
# Глобальные переменные для логики подсчета
|
|
||||||
globals:
|
|
||||||
- id: item_count_total
|
|
||||||
type: int
|
|
||||||
restore_value: true
|
|
||||||
initial_value: "0"
|
|
||||||
|
|
||||||
- id: item_state
|
|
||||||
type: int
|
|
||||||
restore_value: no
|
|
||||||
initial_value: "0"
|
|
||||||
|
|
||||||
- id: last_trigger_time
|
|
||||||
type: unsigned long
|
|
||||||
restore_value: no
|
|
||||||
initial_value: "0"
|
|
||||||
|
|
||||||
- id: energy_threshold
|
|
||||||
type: float
|
|
||||||
restore_value: no
|
|
||||||
initial_value: "50.0"
|
|
||||||
|
|
||||||
- id: distance_min_cm
|
|
||||||
type: float
|
|
||||||
restore_value: no
|
|
||||||
initial_value: "75.0"
|
|
||||||
|
|
||||||
- id: distance_max_cm
|
|
||||||
type: float
|
|
||||||
restore_value: no
|
|
||||||
initial_value: "200.0"
|
|
||||||
|
|
||||||
text_sensor:
|
|
||||||
# Статус счетчика
|
|
||||||
- platform: template
|
|
||||||
name: "Counter Status"
|
|
||||||
id: counter_status
|
|
||||||
icon: "mdi:information"
|
|
||||||
update_interval: 1s
|
|
||||||
|
|
||||||
# Кнопки управления
|
|
||||||
button:
|
|
||||||
- platform: template
|
|
||||||
name: "Reset Counter"
|
|
||||||
id: reset_counter
|
|
||||||
on_press:
|
|
||||||
- lambda: |-
|
|
||||||
id(item_count_total) = 0;
|
|
||||||
id(items_count).publish_state(0);
|
|
||||||
|
|
||||||
# Number для настройки порогов из Home Assistant / MQTT
|
|
||||||
number:
|
|
||||||
- platform: template
|
|
||||||
name: "Energy Threshold"
|
|
||||||
id: energy_threshold_ui
|
|
||||||
min_value: 10
|
|
||||||
max_value: 100
|
|
||||||
step: 5
|
|
||||||
initial_value: 50
|
|
||||||
set_action:
|
|
||||||
- lambda: id(energy_threshold) = x;
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "Min Distance (cm)"
|
|
||||||
id: dist_min_ui
|
|
||||||
min_value: 50
|
|
||||||
max_value: 150
|
|
||||||
step: 5
|
|
||||||
initial_value: 75
|
|
||||||
set_action:
|
|
||||||
- lambda: id(distance_min_cm) = x;
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "Max Distance (cm)"
|
|
||||||
id: dist_max_ui
|
|
||||||
min_value: 100
|
|
||||||
max_value: 500
|
|
||||||
step: 10
|
|
||||||
initial_value: 200
|
|
||||||
set_action:
|
|
||||||
- lambda: id(distance_max_cm) = x;
|
|
||||||
|
|
||||||
# Основной цикл подсчета + статус
|
|
||||||
interval:
|
|
||||||
- interval: 50ms
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
// Получаем текущие значения
|
|
||||||
float energy = id(g0_energy).state;
|
|
||||||
float dist = id(moving_dist).state;
|
|
||||||
|
|
||||||
// Проверка валидности дистанции
|
|
||||||
bool valid_dist = (dist >= id(distance_min_cm)) && (dist <= id(distance_max_cm));
|
|
||||||
|
|
||||||
// Обновляем отладочный сенсор дистанции
|
|
||||||
id(trigger_dist).publish_state(valid_dist ? dist : 0);
|
|
||||||
|
|
||||||
// Конечный автомат: 0 = ожидание, 1 = объект в зоне, 2 = debounce после прохода
|
|
||||||
unsigned long now = millis();
|
|
||||||
int current_state = id(item_state);
|
|
||||||
|
|
||||||
// Объект вошел в зону (энергия выше порога + дистанция в пределах)
|
|
||||||
if (current_state == 0 && energy > id(energy_threshold) && valid_dist) {
|
|
||||||
id(item_state) = 1;
|
|
||||||
id(last_trigger_time) = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Объект ушел из зоны — инкремент счетчика
|
|
||||||
if (current_state == 1 && energy < (id(energy_threshold) * 0.5)) {
|
|
||||||
id(item_state) = 2;
|
|
||||||
id(item_count_total)++;
|
|
||||||
id(items_count).publish_state(id(item_count_total));
|
|
||||||
id(last_trigger_time) = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debounce: ждем 300мс после прохода перед новым детектом
|
|
||||||
if (current_state == 2 && (now - id(last_trigger_time)) > 300) {
|
|
||||||
id(item_state) = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сброс stuck состояния (объект "завис" в зоне больше 5 сек)
|
|
||||||
if (current_state == 1 && (now - id(last_trigger_time)) > 5000) {
|
|
||||||
id(item_state) = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
- interval: 1s
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
std::string status = "State: " + std::to_string(id(item_state)) +
|
|
||||||
" | E: " + std::to_string(id(g0_energy).state, 0) +
|
|
||||||
" | D: " + std::to_string(id(moving_dist).state, 0) +
|
|
||||||
" | Count: " + std::to_string(id(item_count_total));
|
|
||||||
id(counter_status).publish_state(status.c_str());
|
|
||||||
|
|
||||||
# Светодиод статуса (GPIO2 на ESP32 devkit)
|
|
||||||
output:
|
|
||||||
- platform: gpio
|
|
||||||
pin: GPIO2
|
|
||||||
id: status_led
|
|
||||||
|
|
||||||
light:
|
|
||||||
- platform: binary
|
|
||||||
id: led_status
|
|
||||||
output: status_led
|
|
||||||
internal: true
|
|
||||||
|
|
||||||
logger:
|
|
||||||
level: INFO
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# QR/Barcode Scanner
|
|
||||||
|
|
||||||
Считыватель QR-кодов и штрихкодов на базе **pyzbar** + **OpenCV** для **Raspberry Pi Camera Module v3**.
|
|
||||||
|
|
||||||
## Зависимости
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install libzbar0 libgstreamer1.0-dev
|
|
||||||
pip install opencv-python pyzbar
|
|
||||||
```
|
|
||||||
|
|
||||||
## Запуск
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python scanner.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Поддерживаемые форматы
|
|
||||||
|
|
||||||
- QR-код
|
|
||||||
- Code 128, Code 39, EAN-13, EAN-8
|
|
||||||
- UPC-A, UPC-E
|
|
||||||
- Interleaved 2 of 5
|
|
||||||
- И другие форматы, поддерживаемые `pyzbar`
|
|
||||||
|
|
||||||
## Управление
|
|
||||||
|
|
||||||
| Клавиша | Действие |
|
|
||||||
|---------|-----------------------------------|
|
|
||||||
| `q` | Выход |
|
|
||||||
| `r` | Сброс списка отсканированных кодов|
|
|
||||||
|
|
||||||
## Как работает
|
|
||||||
|
|
||||||
1. Подключение камеры через GStreamer (`libcamerasrc`) — родной путь для Camera Module v3
|
|
||||||
2. Каждый кадр конвертируется в градации серого и передаётся в `pyzbar.decode()`
|
|
||||||
3. Найденные коды обводятся зелёной рамкой, данные выводятся в консоль и на экран
|
|
||||||
4. Дубликаты фильтруются — каждый уникальный код выводится один раз (до сброса)
|
|
||||||
|
|
||||||
## Fallback
|
|
||||||
|
|
||||||
Если GStreamer-пайплайн недоступен, скрипт автоматически пробует открыть камеру через `/dev/video0` (V4L2).
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""QR/Barcode scanner using pyzbar + OpenCV for Raspberry Pi Camera Module v3."""
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
from pyzbar import pyzbar
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Camera Module v3 uses libcamera (not V4L2 by default)
|
|
||||||
# GStreamer pipeline for libcamera
|
|
||||||
gst_pipeline = (
|
|
||||||
"libcamerasrc ! "
|
|
||||||
"video/x-raw, format=NV12, width=1280, height=960 ! "
|
|
||||||
"videoconvert ! "
|
|
||||||
"video/x-raw, format=BGR ! "
|
|
||||||
"appsink"
|
|
||||||
)
|
|
||||||
|
|
||||||
cap = cv2.VideoCapture(gst_pipeline, cv2.CAP_GSTREAMER)
|
|
||||||
|
|
||||||
if not cap.isOpened():
|
|
||||||
# Fallback: try V4L2 if libcamera pipeline fails
|
|
||||||
print("GStreamer pipeline failed, trying /dev/video0...")
|
|
||||||
cap = cv2.VideoCapture(0)
|
|
||||||
if not cap.isOpened():
|
|
||||||
print("Error: Cannot open camera")
|
|
||||||
return
|
|
||||||
|
|
||||||
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
|
|
||||||
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 960)
|
|
||||||
|
|
||||||
scanned_codes = set()
|
|
||||||
|
|
||||||
print("Scanner running. Press 'q' to quit, 'r' to reset scanned codes.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
ret, frame = cap.read()
|
|
||||||
if not ret:
|
|
||||||
print("Failed to grab frame")
|
|
||||||
break
|
|
||||||
|
|
||||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
||||||
codes = pyzbar.decode(gray)
|
|
||||||
|
|
||||||
for code in codes:
|
|
||||||
data = code.data.decode("utf-8")
|
|
||||||
code_type = code.type
|
|
||||||
|
|
||||||
if data not in scanned_codes:
|
|
||||||
scanned_codes.add(data)
|
|
||||||
print(f"[{code_type}] {data} (total unique: {len(scanned_codes)})")
|
|
||||||
|
|
||||||
# Draw bounding box
|
|
||||||
points = code.polygon
|
|
||||||
if len(points) == 4:
|
|
||||||
pts = [(p.x, p.y) for p in points]
|
|
||||||
for i in range(4):
|
|
||||||
cv2.line(frame, pts[i], pts[(i + 1) % 4], (0, 255, 0), 3)
|
|
||||||
|
|
||||||
# Draw data label
|
|
||||||
cv2.putText(
|
|
||||||
frame,
|
|
||||||
data[:40],
|
|
||||||
(points[0].x, points[0].y - 10),
|
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
|
||||||
0.5,
|
|
||||||
(0, 255, 0),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
|
|
||||||
cv2.imshow("Scanner", frame)
|
|
||||||
|
|
||||||
key = cv2.waitKey(1) & 0xFF
|
|
||||||
if key == ord("q"):
|
|
||||||
break
|
|
||||||
elif key == ord("r"):
|
|
||||||
scanned_codes.clear()
|
|
||||||
print("Scanned codes reset.")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
cap.release()
|
|
||||||
cv2.destroyAllWindows()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -51,5 +51,19 @@ services:
|
|||||||
/server
|
/server
|
||||||
"
|
"
|
||||||
|
|
||||||
|
scanner:
|
||||||
|
build:
|
||||||
|
context: ../scanner
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
SERVER_URL: http://app:8080
|
||||||
|
depends_on:
|
||||||
|
app:
|
||||||
|
condition: service_started
|
||||||
|
devices:
|
||||||
|
- /dev/video0:/dev/video0
|
||||||
|
profiles:
|
||||||
|
- scanner
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libzbar0 \
|
||||||
|
libzbar-dev \
|
||||||
|
libgl1-mesa-glx \
|
||||||
|
libglib2.0-0 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY scanner.py .
|
||||||
|
|
||||||
|
CMD ["python", "scanner.py"]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[project]
|
||||||
|
name = "scanner"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "QR Scanner for rostpoliplast"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"pyzbar==0.1.9",
|
||||||
|
"requests==2.31.0",
|
||||||
|
"opencv-python-headless==4.9.0.80",
|
||||||
|
"Pillow==10.2.0",
|
||||||
|
"numpy==1.26.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
dev-dependencies = []
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# QR Scanner dependencies
|
||||||
|
pyzbar==0.1.9
|
||||||
|
requests==2.31.0
|
||||||
|
opencv-python-headless==4.9.0.80
|
||||||
|
Pillow==10.2.0
|
||||||
|
numpy==1.26.4
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import cv2
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
from pyzbar.pyzbar import decode as decode_qr
|
||||||
|
|
||||||
|
SERVER_URL = os.getenv("SERVER_URL", "http://localhost:8080")
|
||||||
|
API_ENDPOINT = f"{SERVER_URL}/api/v1/bales"
|
||||||
|
|
||||||
|
|
||||||
|
def extract_type_from_url(url):
|
||||||
|
parsed = urlparse(url)
|
||||||
|
params = parse_qs(parsed.query)
|
||||||
|
if "type" in params:
|
||||||
|
return params["type"][0]
|
||||||
|
return parsed.path.split("=")[-1] if "=" in parsed.path else None
|
||||||
|
|
||||||
|
|
||||||
|
def scan_and_send():
|
||||||
|
cap = cv2.VideoCapture(0)
|
||||||
|
|
||||||
|
if not cap.isOpened():
|
||||||
|
print("Error: Cannot open camera", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("Scanning for QR codes... Press 'q' to quit")
|
||||||
|
print(f"Server: {SERVER_URL}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
ret, frame = cap.read()
|
||||||
|
if not ret:
|
||||||
|
print("Failed to grab frame")
|
||||||
|
break
|
||||||
|
|
||||||
|
qr_codes = decode_qr(frame)
|
||||||
|
|
||||||
|
for qr in qr_codes:
|
||||||
|
url_data = qr.data.decode("utf-8")
|
||||||
|
print(f"Found QR: {url_data}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
bale_type = extract_type_from_url(url_data)
|
||||||
|
|
||||||
|
if not bale_type:
|
||||||
|
print("Error: type not found in QR")
|
||||||
|
continue
|
||||||
|
|
||||||
|
response = requests.post(f"{API_ENDPOINT}?type={bale_type}", timeout=5)
|
||||||
|
|
||||||
|
if response.status_code in (200, 201):
|
||||||
|
print(f"OK: Bale created - {response.json()}")
|
||||||
|
else:
|
||||||
|
print(f"Error: {response.status_code} - {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Request error: {e}")
|
||||||
|
|
||||||
|
cv2.imshow("QR Scanner", frame)
|
||||||
|
|
||||||
|
if cv2.waitKey(1) & 0xFF == ord("q"):
|
||||||
|
break
|
||||||
|
|
||||||
|
cap.release()
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
scan_and_send()
|
||||||
Reference in New Issue
Block a user