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")
20 def __init__(self, *args, **kwargs):
22 for k, v in kwargs.items():
26 return "{}({})".format(
27 self.__class__.__name__,
31 'bytes.fromhex("{}")'.format(v.hex())
32 if isinstance(v, bytes)
35 for k, v in self.__dict__.items()
36 if not k.startswith("_")
41 def from_packet(cls, length, proto, payload):
42 adjust = 2 if proto == STATUS.PROTO else 4 # Weird special case
43 if length > 1 and len(payload) + adjust != length:
45 "length is %d but payload length is %d", length, len(payload)
47 return cls(length=length, proto=proto, payload=payload)
49 def response(self, *args):
52 assert len(args) == 1 and isinstance(args[0], bytes)
54 length = len(payload) + 1
57 return b"xx" + pack("BB", length, self.proto) + payload + b"\r\n"
60 class UNKNOWN(_GT06pkt):
64 class LOGIN(_GT06pkt):
68 def from_packet(cls, length, proto, payload):
69 self = super().from_packet(length, proto, payload)
70 self.imei = payload[:-1].hex()
71 self.ver = unpack("B", payload[-1:])[0]
75 return super().response(b"")
78 class SUPERVISION(_GT06pkt):
82 class HEARTBEAT(_GT06pkt):
86 class _GPS_POSITIONING(_GT06pkt):
89 def from_packet(cls, length, proto, payload):
90 self = super().from_packet(length, proto, payload)
91 self.dtime = payload[:6]
96 return super().response(self.dtime)
99 class GPS_POSITIONING(_GPS_POSITIONING):
103 class GPS_OFFLINE_POSITIONING(_GPS_POSITIONING):
107 class STATUS(_GT06pkt):
111 def from_packet(cls, length, proto, payload):
112 self = super().from_packet(length, proto, payload)
113 if len(payload) == 5:
114 self.batt, self.ver, self.intvl, self.signal, _ = unpack(
117 elif len(payload) == 4:
118 self.batt, self.ver, self.intvl, _ = unpack("BBBB", payload)
123 class HIBERNATION(_GT06pkt):
127 class RESET(_GT06pkt):
131 class WHITELIST_TOTAL(_GT06pkt):
135 class WIFI_OFFLINE_POSITIONING(_GT06pkt):
139 class TIME(_GT06pkt):
143 payload = pack("!HBBBBB", *datetime.utcnow().timetuple()[:6])
144 return super().response(payload)
147 class MOM_PHONE(_GT06pkt):
151 class STOP_ALARM(_GT06pkt):
155 class SETUP(_GT06pkt):
160 uploadIntervalSeconds=0x0300,
161 binarySwitch=0b00110001,
168 phoneNumbers=["", "", ""],
171 return pack("!I", x)[1:]
175 pack("!H", uploadIntervalSeconds),
176 pack("B", binarySwitch),
178 + [pack3b(el) for el in alarms]
180 pack("B", dndTimeSwitch),
182 + [pack3b(el) for el in dndTimes]
184 pack("B", gpsTimeSwitch),
185 pack("!H", gpsTimeStart),
186 pack("!H", gpsTimeStop),
188 + [b";".join([el.encode() for el in phoneNumbers])]
190 return super().response(payload)
193 class SYNCHRONOUS_WHITELIST(_GT06pkt):
197 class RESTORE_PASSWORD(_GT06pkt):
201 class WIFI_POSITIONING(_GT06pkt):
205 class MANUAL_POSITIONING(_GT06pkt):
209 class BATTERY_CHARGE(_GT06pkt):
213 class CHARGER_CONNECTED(_GT06pkt):
217 class CHARGER_DISCONNECTED(_GT06pkt):
221 class VIBRATION_RECEIVED(_GT06pkt):
225 class POSITION_UPLOAD_INTERVAL(_GT06pkt):
229 def from_packet(cls, length, proto, payload):
230 self = super().from_packet(length, proto, payload)
231 self.interval = unpack("!H", payload[:2])
235 return super().response(pack("!H", self.interval))
238 # Build a dict protocol number -> class
240 if True: # just to indent the code, sorry!
243 for name, cls in globals().items()
245 and issubclass(cls, _GT06pkt)
246 and not name.startswith("_")
248 if hasattr(cls, "PROTO"):
249 CLASSES[cls.PROTO] = cls
252 def handle_packet(packet, addr, when):
254 msg = UNKNOWN.from_packet(0, 0, packet)
256 xx, length, proto = unpack("!2sBB", packet[:4])
258 payload = packet[4:-2]
259 if xx != b"xx" or crlf != b"\r\n" or proto not in CLASSES:
260 msg = UNKNOWN.from_packet(length, proto, packet)
262 msg = CLASSES[proto].from_packet(length, proto, payload)
265 def make_response(msg):
266 return msg.response()
268 def set_config(config): # Note that we are setting _class_ attribute
269 _GT06pkt.CONFIG = config