From ab2f1899300086a205da82dab24245a287e44217 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 6 Jun 2026 21:32:02 +0000 Subject: [PATCH] Add Orange Pi scales.py service --- orange_pi/scales.py | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 orange_pi/scales.py diff --git a/orange_pi/scales.py b/orange_pi/scales.py new file mode 100644 index 0000000..542e66c --- /dev/null +++ b/orange_pi/scales.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +""" +Весовой сервис для Orange Pi +Читает данные с весового терминала A9 через USB-RS232 (WaveShare конвертер) +и публикует вес в MQTT топик scale/weight + +Установка: + pip3 install pyserial paho-mqtt --break-system-packages + +Автозапуск: + systemctl enable scales + systemctl start scales +""" +import subprocess +import paho.mqtt.client as mqtt +import re +import time +import datetime + +SERIAL_PORT = '/dev/ttyUSB0' +SERIAL_BAUD = 9600 + +# MQTT локальный (сервер с ИИ системой) +MQTT1_HOST = '192.168.20.9' +MQTT1_PORT = 1883 +MQTT1_USER = '' +MQTT1_PASS = '' + +# MQTT наш VPS +MQTT2_HOST = '77.222.43.248' +MQTT2_PORT = 1884 +MQTT2_USER = 'esp32' +MQTT2_PASS = 'Esp32Scales#2026' + +MQTT_WEIGHT_TOPIC = 'scale/weight' +MQTT_GO_TOPIC = 'scale/traffic/go' + +MIN_WEIGHT = 200 # кг — минимум для определения машины на платформе +STABLE_DELTA = 50 # кг — допустимый разброс для стабилизации +STABLE_TIME = 10 # сек — время стабилизации + +gpio_ok = False + +def on_message(client, userdata, msg): + print(f"[MQTT] {msg.topic}: {msg.payload}") + if msg.topic == MQTT_GO_TOPIC: + print("[LIGHT] Получен GO — зелёный!") + +mqtt1 = mqtt.Client(client_id="scales_opi_1") +mqtt2 = mqtt.Client(client_id="scales_opi_2") + +def mqtt_connect(): + try: + mqtt1.on_message = on_message + mqtt1.connect(MQTT1_HOST, MQTT1_PORT, keepalive=60) + mqtt1.subscribe(MQTT_GO_TOPIC) + mqtt1.loop_start() + print("[MQTT1] Подключён к 192.168.20.9") + except Exception as e: + print(f"[MQTT1] Ошибка: {e}") + try: + mqtt2.username_pw_set(MQTT2_USER, MQTT2_PASS) + mqtt2.connect(MQTT2_HOST, MQTT2_PORT, keepalive=60) + mqtt2.loop_start() + print("[MQTT2] Подключён к VPS") + except Exception as e: + print(f"[MQTT2] Ошибка: {e}") + +def publish_weight(w): + ts = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S') + payload = f"{w:.1f}" + try: + mqtt1.publish(MQTT_WEIGHT_TOPIC, payload) + print(f"[MQTT1] Отправлен вес: {payload} кг в {ts}") + except Exception as e: + print(f"[MQTT1] Ошибка: {e}") + try: + mqtt2.publish(MQTT_WEIGHT_TOPIC, payload) + print(f"[MQTT2] Отправлен вес: {payload} кг в {ts}") + except Exception as e: + print(f"[MQTT2] Ошибка: {e}") + +def parse_weight(data): + """ + Формат A9: STX + +XXXXXXX_C + ETX + Пример: \x02+00176001B\x03 = 1760.0 кг + """ + m = re.search(rb'\x02([+-]\d{8}[A-Z0-9])\x03', data) + if m: + s = m.group(1).decode('ascii') + digits = s[1:8] # 7 цифр без знака и контрольной суммы + w = float(digits[:-1] + '.' + digits[-1]) + return w + return None + +def main(): + mqtt_connect() + print("[BOOT] Готов, жду машину...") + + subprocess.run(['stty', '-F', '/dev/ttyUSB0', 'raw', '9600', + 'cs8', '-cstopb', '-parenb']) + + proc = subprocess.Popen( + ['cat', '/dev/ttyUSB0'], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL + ) + + state = 'EMPTY' + last_weights = [] + buf = b"" + + while True: + try: + chunk = proc.stdout.read(12) + if not chunk: + time.sleep(0.1) + continue + except: + time.sleep(0.1) + continue + + buf += chunk + while b'\x02' in buf and b'\x03' in buf: + s = buf.find(b'\x02') + e = buf.find(b'\x03', s) + if s == -1 or e == -1: + break + frame = buf[s:e+1] + buf = buf[e+1:] + w = parse_weight(frame) + if w is None: + continue + + now = time.time() + print(f"[RAW] {w} кг") + + if state == 'EMPTY': + if w >= MIN_WEIGHT: + state = 'LOADING' + last_weights = [] + print(f"[STATE] Машина заезжает: {w} кг") + + elif state == 'LOADING': + if w < MIN_WEIGHT / 2: + state = 'EMPTY' + last_weights = [] + print("[STATE] Машина уехала") + continue + last_weights.append((now, w)) + last_weights = [(t, v) for t, v in last_weights + if now - t <= STABLE_TIME] + if len(last_weights) >= 5: + vals = [v for _, v in last_weights] + if max(vals) - min(vals) <= STABLE_DELTA: + avg = sum(vals) / len(vals) + print(f"[STATE] Вес стабилен: {avg:.1f} кг") + publish_weight(avg) + state = 'WAIT_GO' + print("[STATE] Жду GO от сервера") + + elif state == 'WAIT_GO': + if w < MIN_WEIGHT / 2: + state = 'EMPTY' + print("[STATE] Машина уехала без GO") + +if __name__ == '__main__': + main()