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
21 "GPS_OFFLINE_POSITIONING",
26 "WIFI_OFFLINE_POSITIONING",
31 "SYNCHRONOUS_WHITELIST",
37 "CHARGER_DISCONNECTED",
39 "POSITION_UPLOAD_INTERVAL",
42 log = getLogger("gps303")
49 def __init__(self, *args, **kwargs):
51 for k, v in kwargs.items():
55 return "{}({})".format(
56 self.__class__.__name__,
60 'bytes.fromhex("{}")'.format(v.hex())
61 if isinstance(v, bytes)
64 for k, v in self.__dict__.items()
65 if not k.startswith("_")
70 def from_packet(cls, proto, payload):
71 return cls(proto=proto, payload=payload)
73 def response(self, *args):
76 assert len(args) == 1 and isinstance(args[0], bytes)
78 length = len(payload) + 1
81 return b"xx" + pack("BB", length, self.proto) + payload + b"\r\n"
84 class UNKNOWN(_GT06pkt):
88 class LOGIN(_GT06pkt):
92 def from_packet(cls, proto, payload):
93 self = super().from_packet(proto, payload)
94 self.imei = payload[:-1].hex()
95 self.ver = unpack("B", payload[-1:])[0]
99 return super().response(b"")
102 class SUPERVISION(_GT06pkt):
106 class HEARTBEAT(_GT06pkt):
110 class _GPS_POSITIONING(_GT06pkt):
112 def from_packet(cls, proto, payload):
113 self = super().from_packet(proto, payload)
114 self.dtime = payload[:6]
115 # TODO parse the rest
119 return super().response(self.dtime)
122 class GPS_POSITIONING(_GPS_POSITIONING):
126 class GPS_OFFLINE_POSITIONING(_GPS_POSITIONING):
130 class STATUS(_GT06pkt):
134 def from_packet(cls, proto, payload):
135 self = super().from_packet(proto, payload)
136 if len(payload) == 5:
137 self.batt, self.ver, self.intvl, self.signal, _ = unpack(
140 elif len(payload) == 4:
141 self.batt, self.ver, self.intvl, _ = unpack("BBBB", payload)
146 class HIBERNATION(_GT06pkt):
150 class RESET(_GT06pkt):
154 class WHITELIST_TOTAL(_GT06pkt):
158 class WIFI_OFFLINE_POSITIONING(_GT06pkt):
162 class TIME(_GT06pkt):
166 payload = pack("!HBBBBB", *datetime.utcnow().timetuple()[:6])
167 return super().response(payload)
170 class MOM_PHONE(_GT06pkt):
174 class STOP_ALARM(_GT06pkt):
178 class SETUP(_GT06pkt):
183 uploadIntervalSeconds=0x0300,
184 binarySwitch=0b00110001,
191 phoneNumbers=["", "", ""],
194 return pack("!I", x)[1:]
198 pack("!H", uploadIntervalSeconds),
199 pack("B", binarySwitch),
201 + [pack3b(el) for el in alarms]
203 pack("B", dndTimeSwitch),
205 + [pack3b(el) for el in dndTimes]
207 pack("B", gpsTimeSwitch),
208 pack("!H", gpsTimeStart),
209 pack("!H", gpsTimeStop),
211 + [b";".join([el.encode() for el in phoneNumbers])]
213 return super().response(payload)
216 class SYNCHRONOUS_WHITELIST(_GT06pkt):
220 class RESTORE_PASSWORD(_GT06pkt):
224 class WIFI_POSITIONING(_GT06pkt):
228 class MANUAL_POSITIONING(_GT06pkt):
232 class BATTERY_CHARGE(_GT06pkt):
236 class CHARGER_CONNECTED(_GT06pkt):
240 class CHARGER_DISCONNECTED(_GT06pkt):
244 class VIBRATION_RECEIVED(_GT06pkt):
248 class POSITION_UPLOAD_INTERVAL(_GT06pkt):
252 def from_packet(cls, proto, payload):
253 self = super().from_packet(proto, payload)
254 self.interval = unpack("!H", payload[:2])
258 return super().response(pack("!H", self.interval))
261 # Build a dict protocol number -> class
263 if True: # just to indent the code, sorry!
266 for name, cls in globals().items()
268 and issubclass(cls, _GT06pkt)
269 and not name.startswith("_")
271 if hasattr(cls, "PROTO"):
272 CLASSES[cls.PROTO] = cls
274 def make_object(proto, payload):
276 return CLASSES[proto].from_packet(proto, payload)
278 return UNKNOWN.from_packet(proto, payload)
280 def handle_packet(packet, addr, when):
282 return UNKNOWN.from_packet(0, packet)
284 xx, length, proto = unpack("!2sBB", packet[:4])
286 payload = packet[4:-2]
287 adjust = 2 if proto == STATUS.PROTO else 4 # Weird special case
288 if length > 1 and len(payload) + adjust != length:
290 "length is %d but payload length is %d", length, len(payload)
292 if xx != b"xx" or crlf != b"\r\n":
293 return UNKNOWN.from_packet(proto, packet) # full packet as payload
295 return make_object(proto, payload)
298 def make_response(msg):
299 return msg.response()
302 def set_config(config): # Note that we are setting _class_ attribute
303 _GT06pkt.CONFIG = config