#!/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()