]> www.average.org Git - loctrkd.git/blob - loctrkd/rectifier.py
Implement sending commands from the web interface
[loctrkd.git] / loctrkd / rectifier.py
1 """ Estimate coordinates from WIFI_POSITIONING and send back """
2
3 from configparser import ConfigParser
4 from datetime import datetime, timezone
5 from importlib import import_module
6 from logging import getLogger
7 from os import umask
8 from struct import pack
9 from typing import cast, List, Tuple
10 import zmq
11
12 from . import common
13 from .common import CoordReport, HintReport, StatusReport, Report
14 from .zmsg import Bcast, Rept, Resp, topic
15
16 log = getLogger("loctrkd/rectifier")
17
18
19 class QryModule:
20     @staticmethod
21     def init(conf: ConfigParser) -> None:
22         ...
23
24     @staticmethod
25     def shut() -> None:
26         ...
27
28     @staticmethod
29     def lookup(
30         mcc: int,
31         mnc: int,
32         gsm_cells: List[Tuple[int, int, int]],
33         wifi_aps: List[Tuple[str, int]],
34     ) -> Tuple[float, float, float]:
35         ...
36
37
38 def runserver(conf: ConfigParser) -> None:
39     qry = cast(
40         QryModule,
41         import_module("." + conf.get("rectifier", "lookaside"), __package__),
42     )
43     qry.init(conf)
44     proto_needanswer = dict(common.exposed_protos())
45     # Is this https://github.com/zeromq/pyzmq/issues/1627 still not fixed?!
46     zctx = zmq.Context()  # type: ignore
47     zsub = zctx.socket(zmq.SUB)  # type: ignore
48     zsub.connect(conf.get("collector", "publishurl"))
49     for proto in proto_needanswer.keys():
50         zsub.setsockopt(zmq.SUBSCRIBE, topic(proto))
51     zpush = zctx.socket(zmq.PUSH)  # type: ignore
52     zpush.connect(conf.get("collector", "listenurl"))
53     zpub = zctx.socket(zmq.PUB)  # type: ignore
54     oldmask = umask(0o117)
55     zpub.bind(conf.get("rectifier", "publishurl"))
56     umask(oldmask)
57
58     try:
59         while True:
60             zmsg = Bcast(zsub.recv())
61             msg = common.parse_message(
62                 zmsg.proto, zmsg.packet, is_incoming=zmsg.is_incoming
63             )
64             log.debug(
65                 "IMEI %s from %s at %s: %s",
66                 zmsg.imei,
67                 zmsg.peeraddr,
68                 datetime.fromtimestamp(zmsg.when).astimezone(tz=timezone.utc),
69                 msg,
70             )
71             pmod, rect = msg.rectified()
72             log.debug("rectified: %s", rect)
73             if isinstance(rect, (CoordReport, StatusReport)):
74                 zpub.send(
75                     Rept(imei=zmsg.imei, pmod=pmod, payload=rect.json).packed
76                 )
77             elif isinstance(rect, HintReport):
78                 try:
79                     lat, lon, acc = qry.lookup(
80                         rect.mcc,
81                         rect.mnc,
82                         rect.gsm_cells,
83                         list((mac, strng) for _, mac, strng in rect.wifi_aps),
84                     )
85                     log.debug(
86                         "Approximated lat=%s, lon=%s, acc=%s for %s",
87                         lat,
88                         lon,
89                         acc,
90                         rect,
91                     )
92                     if proto_needanswer.get(zmsg.proto, False):
93                         resp = Resp(
94                             imei=zmsg.imei,
95                             when=zmsg.when,  # not the current time, but the original!
96                             packet=msg.Out(latitude=lat, longitude=lon).packed,
97                         )
98                         log.debug("Sending reponse %s", resp)
99                         zpush.send(resp.packed)
100                     rept = CoordReport(
101                         devtime=rect.devtime,
102                         battery_percentage=rect.battery_percentage,
103                         accuracy=acc,
104                         altitude=None,
105                         speed=None,
106                         direction=None,
107                         latitude=lat,
108                         longitude=lon,
109                     )
110                     log.debug("Sending report %s", rept)
111                     zpub.send(
112                         Rept(
113                             imei=zmsg.imei,
114                             payload=rept.json,
115                         ).packed
116                     )
117                 except Exception as e:
118                     log.exception(
119                         "Lookup for %s rectified as %s resulted in %s",
120                         msg,
121                         rect,
122                         e,
123                     )
124
125     except KeyboardInterrupt:
126         zsub.close()
127         zpub.close()
128         zpush.close()
129         zctx.destroy()  # type: ignore
130         qry.shut()
131
132
133 if __name__.endswith("__main__"):
134     runserver(common.init(log))