2 Implementation of the protocol used by zx303 GPS+GPRS module
3 Description from https://github.com/tobadia/petGPS/tree/master/resources
6 from datetime import datetime
7 from inspect import isclass
8 from logging import getLogger
9 from struct import pack, unpack
11 __all__ = ("handle_packet", "make_response")
13 log = getLogger("gps303")
19 def __init__(self, *args, **kwargs):
21 for k, v in kwargs.items():
25 return "{}({})".format(
26 self.__class__.__name__,
30 'bytes.fromhex("{}")'.format(v.hex())
31 if isinstance(v, bytes)
34 for k, v in self.__dict__.items()
35 if not k.startswith("_")
40 def from_packet(cls, length, proto, payload):
41 adjust = 2 if proto == STATUS.PROTO else 4 # Weird special case
42 if length > 1 and len(payload) + adjust != length:
44 "length is %d but payload length is %d", length, len(payload)
46 return cls(length=length, proto=proto, payload=payload)
48 def response(self, *args):
51 assert len(args) == 1 and isinstance(args[0], bytes)
53 length = len(payload) + 1
56 return b"xx" + pack("BB", length, self.proto) + payload + b"\r\n"
59 class UNKNOWN(_GT06pkt):
63 class LOGIN(_GT06pkt):
67 def from_packet(cls, length, proto, payload):
68 self = super().from_packet(length, proto, payload)
69 self.imei = payload[:-1].hex()
70 self.ver = unpack("B", payload[-1:])[0]
74 return super().response(b"")
77 class SUPERVISION(_GT06pkt):
81 class HEARTBEAT(_GT06pkt):
85 class _GPS_POSITIONING(_GT06pkt):
88 def from_packet(cls, length, proto, payload):
89 self = super().from_packet(length, proto, payload)
90 self.dtime = payload[:6]
95 return super().response(self.dtime)
98 class GPS_POSITIONING(_GPS_POSITIONING):
102 class GPS_OFFLINE_POSITIONING(_GPS_POSITIONING):
106 class STATUS(_GT06pkt):
110 def from_packet(cls, length, proto, payload):
111 self = super().from_packet(length, proto, payload)
112 if len(payload) == 5:
113 self.batt, self.ver, self.intvl, self.signal, _ = unpack(
116 elif len(payload) == 4:
117 self.batt, self.ver, self.intvl, _ = unpack("BBBB", payload)
122 class HIBERNATION(_GT06pkt):
126 class RESET(_GT06pkt):
130 class WHITELIST_TOTAL(_GT06pkt):
134 class WIFI_OFFLINE_POSITIONING(_GT06pkt):
138 class TIME(_GT06pkt):
142 payload = pack("!HBBBBB", *datetime.utcnow().timetuple()[:6])
143 return super().response(payload)
146 class MOM_PHONE(_GT06pkt):
150 class STOP_ALARM(_GT06pkt):
154 class SETUP(_GT06pkt):
159 uploadIntervalSeconds=0x0300,
160 binarySwitch=0b00110001,
167 phoneNumbers=["", "", ""],
170 return pack("!I", x)[1:]
174 pack("!H", uploadIntervalSeconds),
175 pack("B", binarySwitch),
177 + [pack3b(el) for el in alarms]
179 pack("B", dndTimeSwitch),
181 + [pack3b(el) for el in dndTimes]
183 pack("B", gpsTimeSwitch),
184 pack("!H", gpsTimeStart),
185 pack("!H", gpsTimeStop),
187 + [b";".join([el.encode() for el in phoneNumbers])]
189 return super().response(payload)
192 class SYNCHRONOUS_WHITELIST(_GT06pkt):
196 class RESTORE_PASSWORD(_GT06pkt):
200 class WIFI_POSITIONING(_GT06pkt):
204 class MANUAL_POSITIONING(_GT06pkt):
208 class BATTERY_CHARGE(_GT06pkt):
212 class CHARGER_CONNECTED(_GT06pkt):
216 class CHARGER_DISCONNECTED(_GT06pkt):
220 class VIBRATION_RECEIVED(_GT06pkt):
224 class POSITION_UPLOAD_INTERVAL(_GT06pkt):
228 def from_packet(cls, length, proto, payload):
229 self = super().from_packet(length, proto, payload)
230 self.interval = unpack("!H", payload[:2])
234 return super().response(pack("!H", self.interval))
237 # Build a dict protocol number -> class
239 if True: # just to indent the code, sorry!
242 for name, cls in globals().items()
244 and issubclass(cls, _GT06pkt)
245 and not name.startswith("_")
247 if hasattr(cls, "PROTO"):
248 CLASSES[cls.PROTO] = cls
251 def handle_packet(packet, addr, when):
253 msg = UNKNOWN.from_packet(0, 0, packet)
255 xx, length, proto = unpack("!2sBB", packet[:4])
257 payload = packet[4:-2]
258 if xx != b"xx" or crlf != b"\r\n" or proto not in CLASSES:
259 msg = UNKNOWN.from_packet(length, proto, packet)
261 msg = CLASSES[proto].from_packet(length, proto, payload)
264 def make_response(msg):
265 return msg.response()