"Respond",
)
+MODNAME = __name__.split(".")[-1]
PROTO_PREFIX = "BS:"
### Deframer ###
self.latitude = p.lat * p.nors
self.longitude = p.lon * p.eorw
- def rectified(self) -> Report:
+ def rectified(self) -> Tuple[str, Report]:
# self.gps_valid is supposed to mean it, but it does not. Perfectly
# good looking coordinates, with ten satellites, still get 'V'.
# I suspect that in reality, 'A' means "hint data is absent".
if self.gps_valid or self.num_of_sats > 3:
- return CoordReport(
+ return MODNAME, CoordReport(
devtime=str(self.devtime),
battery_percentage=self.battery_percentage,
accuracy=self.positioning_accuracy,
longitude=self.longitude,
)
else:
- return HintReport(
+ return MODNAME, HintReport(
devtime=str(self.devtime),
battery_percentage=self.battery_percentage,
mcc=self.mcc,
for cls in CLASSES.values()
if hasattr(cls, "rectified")
]
+
+
+def make_response(cmd: str, imei: str, **kwargs: Any) -> Optional[BeeSurePkt]:
+ if cmd == "poweroff":
+ return POWEROFF.Out()
+ elif cmd == "refresh":
+ return MONITOR.Out()
+ elif cmd == "message":
+ return MESSAGE.Out(message=kwargs.get("txt", "Hello"))
+ return None
from typing import Any, cast, Dict, List, Optional, Tuple, Union
from types import SimpleNamespace
-from .protomodule import ProtoModule
+from .protomodule import ProtoClass, ProtoModule
CONF = "/etc/loctrkd.conf"
pmods: List[ProtoModule] = []
return None
+def pmod_by_name(pmodname: str) -> Optional[ProtoModule]:
+ for pmod in pmods:
+ if pmod.__name__.split(".")[-1] == pmodname:
+ return pmod
+ return None
+
+
+def make_response(
+ pmodname: str, cmd: str, imei: str, **kwargs: Any
+) -> Optional[ProtoClass.Out]:
+ pmod = pmod_by_name(pmodname)
+ if pmod is None:
+ return None
+ return pmod.make_response(cmd, imei, **kwargs)
+
+
def parse_message(proto: str, packet: bytes, is_incoming: bool = True) -> Any:
pmod = pmod_for_proto(proto)
return pmod.parse_message(packet, is_incoming) if pmod else None
from datetime import datetime
from json import dumps, loads
from sqlite3 import connect, OperationalError, Row
-from typing import Any, Dict, List, Tuple
+from typing import Any, Dict, List, Optional, Tuple
__all__ = "fetch", "initdb", "stow", "stowloc"
latitude real,
longitude real,
remainder text
+)""",
+ """create table if not exists pmodmap (
+ imei text not null unique,
+ pmod text not null
)""",
)
DB.commit()
+def stowpmod(imei: str, pmod: str) -> None:
+ assert DB is not None
+ DB.execute(
+ """insert or replace into pmodmap
+ (imei, pmod) values (:imei, :pmod)
+ """,
+ {"imei": imei, "pmod": pmod},
+ )
+ DB.commit()
+
+
def fetch(imei: str, backlog: int) -> List[Dict[str, Any]]:
assert DB is not None
cur = DB.cursor()
result.append(dic)
cur.close()
return list(reversed(result))
+
+
+def fetchpmod(imei: str) -> Optional[Any]:
+ assert DB is not None
+ ret = None
+ cur = DB.cursor()
+ cur.execute("select pmod from pmodmap where imei = ?", (imei,))
+ result = cur.fetchone()
+ if result:
+ ret = result[0]
+ cur.close()
+ return ret
class ProtoModule:
+ __name__: str
+
class Stream:
def recv(self, segment: bytes) -> List[Union[bytes, str]]:
...
@staticmethod
def class_by_prefix(prefix: str) -> Union[Type[ProtoClass], List[str]]:
...
+
+ @staticmethod
+ def make_response(
+ cmd: str, imei: str, **kwargs: Any
+ ) -> Optional[ProtoClass.Out]:
+ ...
datetime.fromtimestamp(zmsg.when).astimezone(tz=timezone.utc),
msg,
)
- rect: Report = msg.rectified()
+ pmod, rect = msg.rectified()
log.debug("rectified: %s", rect)
if isinstance(rect, (CoordReport, StatusReport)):
- zpub.send(Rept(imei=zmsg.imei, payload=rect.json).packed)
+ zpub.send(
+ Rept(imei=zmsg.imei, pmod=pmod, payload=rect.json).packed
+ )
elif isinstance(rect, HintReport):
try:
lat, lon, acc = qry.lookup(
import zmq
from . import common
-from .evstore import initdb, stow, stowloc
+from .evstore import initdb, stow, stowloc, stowpmod
from .zmsg import Bcast, Rept
log = getLogger("loctrkd/storage")
rept = Rept(zrep.recv(zmq.NOBLOCK))
except zmq.Again:
break
+ if rept.imei is not None and rept.pmod is not None:
+ stowpmod(rept.imei, rept.pmod)
data = loads(rept.payload)
log.debug("R IMEI %s %s", rept.imei, data)
if data.pop("type") == "location":
import zmq
from . import common
-from .evstore import initdb, fetch
+from .evstore import initdb, fetch, fetchpmod
from .protomodule import ProtoModule
-from .zmsg import Rept, rtopic
+from .zmsg import Rept, Resp, rtopic
log = getLogger("loctrkd/wsgateway")
self.ready = False
self.imeis: Set[str] = set()
+ def __str__(self) -> str:
+ return f"{self.__class__.__name__}(fd={self.sock.fileno()}, addr={self.addr})"
+
def close(self) -> None:
log.debug("Closing fd %d", self.sock.fileno())
self.sock.close()
return result
+def sendcmd(zpush: Any, wsmsg: Dict[str, Any]) -> Dict[str, Any]:
+ imei = wsmsg.pop("imei", None)
+ cmd = wsmsg.pop("type", None)
+ if imei is None or cmd is None:
+ log.info("Unhandled message %s %s %s", cmd, imei, wsmsg)
+ return {
+ "type": "cmdresult",
+ "imei": imei,
+ "result": "Did not get imei or cmd",
+ }
+ pmod = fetchpmod(imei)
+ if pmod is None:
+ log.info("Uknown type of recipient for %s %s %s", cmd, imei, wsmsg)
+ return {
+ "type": "cmdresult",
+ "imei": imei,
+ "result": "Type of the terminal is unknown",
+ }
+ tmsg = common.make_response(pmod, cmd, imei, **wsmsg)
+ if tmsg is None:
+ log.info("Could not make packet for %s %s %s", cmd, imei, wsmsg)
+ return {
+ "type": "cmdresult",
+ "imei": imei,
+ "result": f"{cmd} unimplemented for terminal protocol {pmod}",
+ }
+ resp = Resp(imei=imei, when=time(), packet=tmsg.packed)
+ log.debug("Response: %s", resp)
+ zpush.send(resp.packed)
+ return {
+ "type": "cmdresult",
+ "imei": imei,
+ "result": f"{cmd} sent to {imei}",
+ }
+
+
def runserver(conf: ConfigParser) -> None:
global htmlfile
initdb(conf.get("storage", "dbfn"))
zctx = zmq.Context() # type: ignore
zsub = zctx.socket(zmq.SUB) # type: ignore
zsub.connect(conf.get("rectifier", "publishurl"))
+ zpush = zctx.socket(zmq.PUSH) # type: ignore
+ zpush.connect(conf.get("collector", "listenurl"))
tcpl = socket(AF_INET6, SOCK_STREAM)
tcpl.setblocking(False)
tcpl.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
for msg in backlog(imei, numback)
]
)
+ else:
+ tosend.append((clnt, sendcmd(zpush, wsmsg)))
towrite.add(sk)
elif fl & zmq.POLLOUT:
log.debug("Write now open for fd %d", sk)
class Rept(_Zmsg):
- """Broadcast Zzmq message with "rectified" proto-agnostic json data"""
+ """Broadcast zmq message with "rectified" proto-agnostic json data"""
- KWARGS = (("imei", None), ("payload", ""))
+ KWARGS = (("imei", None), ("payload", ""), ("pmod", None))
@property
def packed(self) -> bytes:
return (
pack(
- "16s",
- "0000000000000000"
+ "16s16s",
+ b"0000000000000000"
if self.imei is None
else self.imei.encode(),
+ b" "
+ if self.pmod is None
+ else self.pmod.encode(),
)
+ self.payload.encode()
)
self.imei = (
None if imei == b"0000000000000000" else imei.decode().strip("\0")
)
- self.payload = buffer[16:].decode()
+ pmod = buffer[16:32]
+ self.pmod = (
+ None if pmod == b" " else pmod.decode().strip("\0")
+ )
+ self.payload = buffer[32:].decode()
"Respond",
)
+MODNAME = __name__.split(".")[-1]
PROTO_PREFIX: str = "ZX:"
### Deframer ###
ttup = (tup[0] % 100,) + tup[1:6]
return pack("BBBBBB", *ttup)
- def rectified(self) -> CoordReport: # JSON-able dict
- return CoordReport(
+ def rectified(self) -> Tuple[str, CoordReport]: # JSON-able dict
+ return MODNAME, CoordReport(
devtime=str(self.devtime),
battery_percentage=None,
accuracy=None,
def out_encode(self) -> bytes: # Set interval in minutes
return pack("B", self.upload_interval)
- def rectified(self) -> StatusReport:
- return StatusReport(battery_percentage=self.batt)
+ def rectified(self) -> Tuple[str, StatusReport]:
+ return MODNAME, StatusReport(battery_percentage=self.batt)
class HIBERNATION(GPS303Pkt): # Server can send to send devicee to sleep
]
)
- def rectified(self) -> HintReport:
- return HintReport(
+ def rectified(self) -> Tuple[str, HintReport]:
+ return MODNAME, HintReport(
devtime=str(self.devtime),
battery_percentage=None,
mcc=self.mcc,
for cls in CLASSES.values()
if hasattr(cls, "rectified")
]
+
+
+def make_response(cmd: str, imei: str, **kwargs: Any) -> Optional[GPS303Pkt]:
+ if cmd == "poweroff":
+ return HIBERNATION.Out()
+ return None