Add Orange Pi scales.py service
This commit is contained in:
@@ -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()
|
||||||
Reference in New Issue
Block a user