2 Implementation of the protocol used by zx303 GPS+GPRS module
3 Description from https://github.com/tobadia/petGPS/tree/master/resources
6 from inspect import isclass
7 from logging import getLogger
8 from struct import pack, unpack
10 __all__ = ("handle_packet", "make_response")
12 log = getLogger("gps303")
18 def __init__(self, *args, **kwargs):
20 for k, v in kwargs.items():
24 return "{}({})".format(
25 self.__class__.__name__,
29 'bytes.fromhex("{}")'.format(v.hex())
30 if isinstance(v, bytes)
33 for k, v in self.__dict__.items()
34 if not k.startswith("_")
39 def from_packet(cls, length, proto, payload):
40 adjust = 2 if proto == STATUS.PROTO else 4 # Weird special case
41 if length > 1 and len(payload) + adjust != length:
43 "length is %d but payload length is %d", length, len(payload)
45 return cls(length=length, proto=proto, payload=payload)
47 def response(self, *args):
50 assert len(args) == 1 and isinstance(args[0], bytes)
52 length = len(payload) + 1
55 return b"xx" + pack("BB", length, self.proto) + payload + b"\r\n"
58 class UNKNOWN(_GT06pkt):
62 class LOGIN(_GT06pkt):
66 def from_packet(cls, length, proto, payload):
67 self = super().from_packet(length, proto, payload)
68 self.imei = payload[:-1].hex()
69 self.ver = unpack("B", payload[-1:])[0]
73 return super().response(b"")
76 class SUPERVISION(_GT06pkt):
80 class HEARTBEAT(_GT06pkt):
84 class GPS_POSITIONING(_GT06pkt):
88 class GPS_OFFLINE_POSITIONING(_GT06pkt):
92 class STATUS(_GT06pkt):
96 def from_packet(cls, length, proto, payload):
97 self = super().from_packet(length, proto, payload)
99 self.batt, self.ver, self.intvl, self.signal, _ = unpack(
102 elif len(payload) == 4:
103 self.batt, self.ver, self.intvl, _ = unpack("BBBB", payload)
108 class HIBERNATION(_GT06pkt):
112 class RESET(_GT06pkt):
116 class WHITELIST_TOTAL(_GT06pkt):
120 class WIFI_OFFLINE_POSITIONING(_GT06pkt):
124 class TIME(_GT06pkt):
128 class MOM_PHONE(_GT06pkt):
132 class STOP_ALARM(_GT06pkt):
136 class SETUP(_GT06pkt):
141 uploadIntervalSeconds=0x0300,
142 binarySwitch=0b00110001,
149 phoneNumbers=["", "", ""],
152 return pack("!I", x)[1:]
156 pack("!H", uploadIntervalSeconds),
157 pack("B", binarySwitch),
159 + [pack3b(el) for el in alarms]
161 pack("B", dndTimeSwitch),
163 + [pack3b(el) for el in dndTimes]
165 pack("B", gpsTimeSwitch),
166 pack("!H", gpsTimeStart),
167 pack("!H", gpsTimeStop),
169 + [b";".join([el.encode() for el in phoneNumbers])]
171 return super().response(payload)
174 class SYNCHRONOUS_WHITELIST(_GT06pkt):
178 class RESTORE_PASSWORD(_GT06pkt):
182 class WIFI_POSITIONING(_GT06pkt):
186 class MANUAL_POSITIONING(_GT06pkt):
190 class BATTERY_CHARGE(_GT06pkt):
194 class CHARGER_CONNECTED(_GT06pkt):
198 class CHARGER_DISCONNECTED(_GT06pkt):
202 class VIBRATION_RECEIVED(_GT06pkt):
206 class POSITION_UPLOAD_INTERVAL(_GT06pkt):
210 # Build a dict protocol number -> class
212 if True: # just to indent the code, sorry!
215 for name, cls in globals().items()
217 and issubclass(cls, _GT06pkt)
218 and not name.startswith("_")
220 if hasattr(cls, "PROTO"):
221 CLASSES[cls.PROTO] = cls
224 def handle_packet(packet, addr, when):
226 msg = UNKNOWN.from_packet(0, 0, packet)
228 xx, length, proto = unpack("!2sBB", packet[:4])
230 payload = packet[4:-2]
231 if xx != b"xx" or crlf != b"\r\n" or proto not in CLASSES:
232 msg = UNKNOWN.from_packet(length, proto, packet)
234 msg = CLASSES[proto].from_packet(length, proto, payload)
237 def make_response(msg):
238 return msg.response()