Initial commit
This commit is contained in:
commit
e813114d92
4 changed files with 226 additions and 0 deletions
90
client.py
Normal file
90
client.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
from config import *
|
||||
from httpx_ws import aconnect_ws
|
||||
import orjson
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
class MMClient:
|
||||
def __init__(self):
|
||||
self.url = WEBSOCKET_URL
|
||||
self.ws = None
|
||||
self.lock = asyncio.Lock()
|
||||
self.recv_queue = asyncio.Queue()
|
||||
self.send_queue = asyncio.Queue()
|
||||
self.pending_acks = {}
|
||||
self.connected = False
|
||||
|
||||
async def run(self):
|
||||
while True:
|
||||
try:
|
||||
async with aconnect_ws(self.url) as ws:
|
||||
self.ws = ws
|
||||
self.connected = True
|
||||
send_task = asyncio.create_task(self._send_loop())
|
||||
recv_task = asyncio.create_task(self._recv_loop())
|
||||
await asyncio.gather(send_task, recv_task)
|
||||
except Exception as e:
|
||||
print(f"Got exception: {e}. Retrying in 5 seconds...")
|
||||
self.connected = False
|
||||
await asyncio.sleep(5)
|
||||
|
||||
async def _send_loop(self):
|
||||
while True:
|
||||
packet = await self.send_queue.get()
|
||||
|
||||
# Requeue packet for later if disconnected
|
||||
if not self.connected:
|
||||
await self.send_queue.put(packet)
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
|
||||
print(packet)
|
||||
packet = orjson.dumps(packet)
|
||||
await self.ws.send_bytes(packet)
|
||||
|
||||
async def _recv_loop(self):
|
||||
while True:
|
||||
try:
|
||||
packet = await self.ws.receive()
|
||||
packet = orjson.loads(packet.data)
|
||||
except Exception as e:
|
||||
print("Something went wrong", e)
|
||||
continue
|
||||
|
||||
if nonce := packet.get('nonce'):
|
||||
if nonce and nonce in self.pending_acks:
|
||||
fut = self.pending_acks.pop(nonce)
|
||||
if not fut.done():
|
||||
fut.set_result(packet)
|
||||
|
||||
print("RECEIVED EVENT", packet)
|
||||
|
||||
async def send_event(self, event_id, payload, ack=False):
|
||||
packet = {
|
||||
"_": event_id,
|
||||
"payload": payload,
|
||||
"nonce": str(uuid.uuid1())
|
||||
}
|
||||
|
||||
|
||||
print("Sending packet...")
|
||||
if ack:
|
||||
fut = asyncio.get_event_loop().create_future()
|
||||
self.pending_acks[str(packet["nonce"])] = fut
|
||||
|
||||
await self.send_queue.put(packet)
|
||||
|
||||
if ack:
|
||||
try:
|
||||
async with asyncio.timeout(2):
|
||||
print("WAITING FOR RETURN....")
|
||||
result = await fut
|
||||
except asyncio.TimeoutError:
|
||||
print("RETURN DID NOT ARRIVE....")
|
||||
return False
|
||||
else:
|
||||
print("GOT RETURN RESULT....", result)
|
||||
return result
|
||||
|
||||
print("CCCCCCC")
|
||||
return
|
||||
4
config.py
Normal file
4
config.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
WEBSOCKET_URL = 'ws://[::1]:8000/api/event/stream'
|
||||
82
device/serial.py
Normal file
82
device/serial.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import logging
|
||||
import asyncio
|
||||
import serial
|
||||
|
||||
log = logging.getLogger('serial-device')
|
||||
|
||||
def _zebra_beep(beep_id):
|
||||
command = [0x05, 0xE6, 0x04, 0x00, beep_id]
|
||||
checksum = (~sum(command) + 1)
|
||||
return bytes(command) + (checksum&0xffff).to_bytes(2, 'big')
|
||||
|
||||
def _honeywell_beep(beep_id):
|
||||
return b"0\x1b" + f"{beep_id}".encode('ASCII') + b','
|
||||
|
||||
_beep_map = {
|
||||
'ack': {'zebra': _zebra_beep(5), 'honeywell': _honeywell_beep(7)},
|
||||
'nack': {'zebra': _zebra_beep(15), 'honeywell': _honeywell_beep('b')},
|
||||
'err': {'zebra': _zebra_beep(16), 'honeywell': _honeywell_beep(8)}
|
||||
}
|
||||
|
||||
class SerialDevice:
|
||||
def __init__(self, path, mm, brand):
|
||||
self.path = path
|
||||
self.ser = None
|
||||
self.run = False
|
||||
self.mm = mm
|
||||
self.brand = None
|
||||
|
||||
def start(self):
|
||||
self.run = True
|
||||
asyncio.create_task(self.loop())
|
||||
|
||||
def stop(self):
|
||||
self.run = False
|
||||
|
||||
async def loop(self):
|
||||
with serial.Serial(self.path) as ser:
|
||||
|
||||
# Flush data that was sent during startup
|
||||
ser.reset_input_buffer()
|
||||
|
||||
self.ser = ser
|
||||
while self.run:
|
||||
try:
|
||||
await self.cycle(ser)
|
||||
except Exception as e:
|
||||
log.error(f"Halting serial loop due to exception {e}")
|
||||
return
|
||||
|
||||
async def cycle(self, ser):
|
||||
|
||||
buf = b''
|
||||
|
||||
# Try to read all the remaining data
|
||||
while ser.in_waiting:
|
||||
buf += ser.read()
|
||||
else:
|
||||
if not buf:
|
||||
# Sleep until data is available
|
||||
while not ser.in_waiting:
|
||||
await asyncio.sleep(0.1) # If there is no data, sleep
|
||||
return
|
||||
|
||||
# We have data, let's apply some basic cleaning
|
||||
buf = buf.strip(b"\r\n") # Remove line break
|
||||
|
||||
log.debug(f"RECV {buf}")
|
||||
res = await self.mm.send_event("BarcodeScan", {'content': buf.decode('ASCII')}, ack=True)
|
||||
|
||||
if beep := res.get('beep'):
|
||||
if beep not in _beep_map:
|
||||
beep = 'nack'
|
||||
|
||||
if self.brand in _beep_map[beep]:
|
||||
log.debug(f"BEEP {beep}")
|
||||
ser.write(_beep_map[beep][brand])
|
||||
|
||||
# Reset the input buffer (discard the response)
|
||||
# TODO: make this more elegant
|
||||
async with asyncio.timeout(0.5):
|
||||
ser.read()
|
||||
ser.reset_input_buffer()
|
||||
50
main.py
Normal file
50
main.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from config import *
|
||||
from client import MMClient
|
||||
import asyncio
|
||||
from glob import glob
|
||||
from os.path import join
|
||||
from device.serial import SerialDevice
|
||||
|
||||
serial_mapping = {
|
||||
'usb-Symbol*': (SerialDevice, 'zebra'),
|
||||
'usb-Honeywell*': (SerialDevice, 'honeywell'),
|
||||
'usb-XinChip*': (SerialDevice, 'china')
|
||||
}
|
||||
|
||||
class USBSerialDetect:
|
||||
def __init__(self, mm):
|
||||
self.devices = {}
|
||||
self.mm = mm
|
||||
|
||||
async def detect(self):
|
||||
while 1:
|
||||
for pat, handler in serial_mapping.items():
|
||||
for dev in glob(join('/dev/serial/by-id', pat)):
|
||||
if self.devices.get(dev):
|
||||
if self.devices[dev].ser.is_open:
|
||||
continue
|
||||
else:
|
||||
print("Destroying device.")
|
||||
del self.devices[dev]
|
||||
|
||||
print("Adding", dev, 'as', handler)
|
||||
self.devices[dev] = handler[0](dev, self.mm, handler[1])
|
||||
self.devices[dev].start()
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def main():
|
||||
|
||||
print("Connecting...")
|
||||
mm = MMClient()
|
||||
asyncio.create_task(mm.run())
|
||||
|
||||
print("Detecting barcodes...")
|
||||
a = USBSerialDetect(mm)
|
||||
asyncio.create_task(a.detect())
|
||||
|
||||
while 1:
|
||||
await asyncio.sleep(100)
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
Loading…
Reference in a new issue