|
|
@ -1,12 +1,12 @@ |
|
|
|
import serial |
|
|
|
import serial |
|
|
|
import gex |
|
|
|
import gex |
|
|
|
from gex import TinyFrame, PayloadParser, TF, PayloadBuilder |
|
|
|
from gex import TinyFrame, PayloadParser, TF, PayloadBuilder, TF_Msg |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Client: |
|
|
|
class Client: |
|
|
|
""" GEX client """ |
|
|
|
""" GEX client """ |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, port='/dev/ttyACM0', timeout=0.3): |
|
|
|
def __init__(self, port:str='/dev/ttyACM0', timeout:float=0.3): |
|
|
|
""" Set up the client. timeout - timeout for waiting for a response. """ |
|
|
|
""" Set up the client. timeout - timeout for waiting for a response. """ |
|
|
|
self.port = port |
|
|
|
self.port = port |
|
|
|
self.serial = serial.Serial(port=port, timeout=timeout) |
|
|
|
self.serial = serial.Serial(port=port, timeout=timeout) |
|
|
@ -17,8 +17,31 @@ class Client: |
|
|
|
resp = self.query_raw(type=gex.MSG_PING) |
|
|
|
resp = self.query_raw(type=gex.MSG_PING) |
|
|
|
print("GEX connected, version string: %s" % resp.data.decode('utf-8')) |
|
|
|
print("GEX connected, version string: %s" % resp.data.decode('utf-8')) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# lambda |
|
|
|
|
|
|
|
def unit_repot_lst(tf :TinyFrame, msg :TF_Msg): |
|
|
|
|
|
|
|
self.handle_unit_report(msg) |
|
|
|
|
|
|
|
return TF.STAY |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.tf.add_type_listener(gex.MSG_UNIT_REPORT, unit_repot_lst) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.unit_lu = {} |
|
|
|
|
|
|
|
self.report_handlers = {} |
|
|
|
|
|
|
|
|
|
|
|
self.load_units() |
|
|
|
self.load_units() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_unit_report(self, msg:TF_Msg): |
|
|
|
|
|
|
|
pp = PayloadParser(msg.data) |
|
|
|
|
|
|
|
callsign = pp.u8() |
|
|
|
|
|
|
|
event = pp.u8() |
|
|
|
|
|
|
|
payload = pp.tail() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if callsign in self.report_handlers: |
|
|
|
|
|
|
|
self.report_handlers[callsign](event, payload) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bind_report_listener(self, callsign:int, listener): |
|
|
|
|
|
|
|
""" Assign a report listener function to a callsign """ |
|
|
|
|
|
|
|
self.report_handlers[callsign] = listener |
|
|
|
|
|
|
|
|
|
|
|
def load_units(self): |
|
|
|
def load_units(self): |
|
|
|
""" Load a list of unit names and callsigns for look-up """ |
|
|
|
""" Load a list of unit names and callsigns for look-up """ |
|
|
|
resp = self.query_raw(type=gex.MSG_LIST_UNITS) |
|
|
|
resp = self.query_raw(type=gex.MSG_LIST_UNITS) |
|
|
@ -38,7 +61,7 @@ class Client: |
|
|
|
'type': type, |
|
|
|
'type': type, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def ini_read(self): |
|
|
|
def ini_read(self) -> str: |
|
|
|
""" Read the settings INI file """ |
|
|
|
""" Read the settings INI file """ |
|
|
|
buffer = self.bulk_read(cs=None, cmd=gex.MSG_INI_READ) |
|
|
|
buffer = self.bulk_read(cs=None, cmd=gex.MSG_INI_READ) |
|
|
|
return buffer.decode('utf-8') |
|
|
|
return buffer.decode('utf-8') |
|
|
@ -54,7 +77,7 @@ class Client: |
|
|
|
""" Persist INI settings to Flash """ |
|
|
|
""" Persist INI settings to Flash """ |
|
|
|
self.send_raw(type=gex.MSG_PERSIST_SETTINGS) |
|
|
|
self.send_raw(type=gex.MSG_PERSIST_SETTINGS) |
|
|
|
|
|
|
|
|
|
|
|
def get_callsign(self, name, type = None): |
|
|
|
def get_callsign(self, name:str, type:str = None) -> int: |
|
|
|
""" Find unit by name and type """ |
|
|
|
""" Find unit by name and type """ |
|
|
|
u = self.unit_lu[name] |
|
|
|
u = self.unit_lu[name] |
|
|
|
|
|
|
|
|
|
|
@ -67,9 +90,8 @@ class Client: |
|
|
|
def _write(self, data): |
|
|
|
def _write(self, data): |
|
|
|
""" Write bytes to the serial port """ |
|
|
|
""" Write bytes to the serial port """ |
|
|
|
self.serial.write(data) |
|
|
|
self.serial.write(data) |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def poll(self, attempts=10): |
|
|
|
def poll(self, attempts:int=10): |
|
|
|
""" Read messages sent by GEX """ |
|
|
|
""" Read messages sent by GEX """ |
|
|
|
first = True |
|
|
|
first = True |
|
|
|
while attempts > 0: |
|
|
|
while attempts > 0: |
|
|
@ -95,7 +117,7 @@ class Client: |
|
|
|
else: |
|
|
|
else: |
|
|
|
self.tf.accept(rv) |
|
|
|
self.tf.accept(rv) |
|
|
|
|
|
|
|
|
|
|
|
def send(self, cs, cmd, id=None, pld=None, listener=None): |
|
|
|
def send(self, cmd:int, cs:int=None, id:int=None, pld=None, listener=None): |
|
|
|
""" Send a command to a unit. If cs is None, cmd is used as message type """ |
|
|
|
""" Send a command to a unit. If cs is None, cmd is used as message type """ |
|
|
|
if cs is None: |
|
|
|
if cs is None: |
|
|
|
return self.tf.query(type=cmd, id=id, pld=pld, listener=listener) |
|
|
|
return self.tf.query(type=cmd, id=id, pld=pld, listener=listener) |
|
|
@ -107,7 +129,7 @@ class Client: |
|
|
|
buf.extend(pld) |
|
|
|
buf.extend(pld) |
|
|
|
self.tf.query(type=gex.MSG_UNIT_REQUEST, id=id, pld=buf, listener=listener) |
|
|
|
self.tf.query(type=gex.MSG_UNIT_REQUEST, id=id, pld=buf, listener=listener) |
|
|
|
|
|
|
|
|
|
|
|
def query(self, cs, cmd, id=None, pld=None): |
|
|
|
def query(self, cmd:int, cs:int=None, id:int=None, pld=None) -> TF_Msg: |
|
|
|
""" Query a unit. If cs is None, cmd is used as message type """ |
|
|
|
""" Query a unit. If cs is None, cmd is used as message type """ |
|
|
|
|
|
|
|
|
|
|
|
self._theframe = None |
|
|
|
self._theframe = None |
|
|
@ -116,7 +138,8 @@ class Client: |
|
|
|
self._theframe = frame |
|
|
|
self._theframe = frame |
|
|
|
return TF.CLOSE |
|
|
|
return TF.CLOSE |
|
|
|
|
|
|
|
|
|
|
|
self.send(cs, cmd, id=id, pld=pld, listener=lst) |
|
|
|
self.send(cs=cs, cmd=cmd, id=id, pld=pld, listener=lst) |
|
|
|
|
|
|
|
# Wait for the response (hope no unrelated frame comes in instead) |
|
|
|
self.poll() |
|
|
|
self.poll() |
|
|
|
|
|
|
|
|
|
|
|
if self._theframe is None: |
|
|
|
if self._theframe is None: |
|
|
@ -127,15 +150,15 @@ class Client: |
|
|
|
|
|
|
|
|
|
|
|
return self._theframe |
|
|
|
return self._theframe |
|
|
|
|
|
|
|
|
|
|
|
def query_raw(self, type, id=None, pld=None): |
|
|
|
def query_raw(self, type:int, id:int=None, pld=None) -> TF_Msg: |
|
|
|
""" Query GEX, without addressing a unit """ |
|
|
|
""" Query GEX, without addressing a unit """ |
|
|
|
return self.query(cs=None, cmd=type, id=id, pld=pld) |
|
|
|
return self.query(cs=None, cmd=type, id=id, pld=pld) |
|
|
|
|
|
|
|
|
|
|
|
def send_raw(self, type, id=None, pld=None): |
|
|
|
def send_raw(self, type:int, id=None, pld=None): |
|
|
|
""" Send to GEX, without addressing a unit """ |
|
|
|
""" Send to GEX, without addressing a unit """ |
|
|
|
return self.send(cs=None, cmd=type, id=id, pld=pld) |
|
|
|
return self.send(cs=None, cmd=type, id=id, pld=pld) |
|
|
|
|
|
|
|
|
|
|
|
def bulk_read(self, cs, cmd, id=None, pld=None, chunk=1024): |
|
|
|
def bulk_read(self, cmd:int, cs:int=None, id=None, pld=None, chunk=1024) -> bytearray: |
|
|
|
""" Perform a bulk read. If cs is None, cmd is used as message type """ |
|
|
|
""" Perform a bulk read. If cs is None, cmd is used as message type """ |
|
|
|
|
|
|
|
|
|
|
|
offer = self.query(cs=cs, cmd=cmd, id=id, pld=pld) |
|
|
|
offer = self.query(cs=cs, cmd=cmd, id=id, pld=pld) |
|
|
@ -167,7 +190,7 @@ class Client: |
|
|
|
|
|
|
|
|
|
|
|
return buffer |
|
|
|
return buffer |
|
|
|
|
|
|
|
|
|
|
|
def bulk_write(self, cs, cmd, bulk, id=None, pld=None): |
|
|
|
def bulk_write(self, cmd:int, bulk, cs:int=None, id:int=None, pld=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
Perform a bulk write. If cs is None, cmd is used as message type. |
|
|
|
Perform a bulk write. If cs is None, cmd is used as message type. |
|
|
|
bulk is the data to write. |
|
|
|
bulk is the data to write. |
|
|
|