Add WebSocket, multi API key, geocoding proxy, beacon map picker, and comprehensive bug fixes
- Multi API Key + permission system (read/write/admin) with SHA-256 hash - WebSocket real-time push (location, alarm, device_status, attendance, bluetooth) - Geocoding proxy endpoints for Amap POI search and reverse geocode - Beacon modal map-based location picker with search and click-to-select - GCJ-02 ↔ WGS-84 bidirectional coordinate conversion - Data cleanup scheduler (configurable retention days) - Fix GPS longitude sign inversion (course_status bit 11: 0=East, 1=West) - Fix 2G CellID 2→3 bytes across all protocols (0x28, 0x2C, parser.py) - Fix parser loop guards, alarm_source field length, CommandLog.sent_at - Fix geocoding IMEI parameterization, require_admin import - Improve API error messages for 422 validation errors - Remove beacon floor/area fields (consolidated into name) via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -123,14 +123,18 @@ class PacketBuilder:
|
||||
self,
|
||||
serial_number: int,
|
||||
protocol: int = PROTO_TIME_SYNC,
|
||||
language: int = 0x0001,
|
||||
) -> bytes:
|
||||
"""
|
||||
Build a time sync response (0x1F).
|
||||
|
||||
Returns the current UTC time as a 4-byte Unix timestamp.
|
||||
Returns the current UTC time as a 4-byte Unix timestamp + 2-byte language.
|
||||
For Chinese (0x0001), the timestamp is GMT+8.
|
||||
"""
|
||||
utc_now = int(time.time())
|
||||
info = struct.pack("!I", utc_now)
|
||||
if language == 0x0001:
|
||||
utc_now += 8 * 3600 # GMT+8 for Chinese
|
||||
info = struct.pack("!IH", utc_now, language)
|
||||
return self.build_response(protocol, serial_number, info)
|
||||
|
||||
def build_time_sync_8a_response(self, serial_number: int) -> bytes:
|
||||
@@ -186,8 +190,8 @@ class PacketBuilder:
|
||||
Complete packet.
|
||||
"""
|
||||
cmd_bytes = command.encode("ascii")
|
||||
# inner_len = server_flag(4) + cmd_content(N)
|
||||
inner_len = 4 + len(cmd_bytes)
|
||||
# inner_len = server_flag(4) + cmd_content(N) + language(2)
|
||||
inner_len = 4 + len(cmd_bytes) + 2
|
||||
|
||||
info = struct.pack("B", inner_len) # 1 byte inner length
|
||||
info += struct.pack("!I", server_flag) # 4 bytes server flag
|
||||
@@ -225,8 +229,8 @@ class PacketBuilder:
|
||||
Complete packet.
|
||||
"""
|
||||
msg_bytes = message_text.encode("utf-16-be")
|
||||
# inner_len = server_flag(4) + msg_content(N)
|
||||
inner_len = 4 + len(msg_bytes)
|
||||
# inner_len = server_flag(4) + msg_content(N) + language(2)
|
||||
inner_len = 4 + len(msg_bytes) + 2
|
||||
|
||||
info = struct.pack("B", inner_len) # 1 byte inner length
|
||||
info += struct.pack("!I", server_flag) # 4 bytes server flag
|
||||
@@ -238,94 +242,57 @@ class PacketBuilder:
|
||||
def build_address_reply_cn(
|
||||
self,
|
||||
serial_number: int,
|
||||
server_flag: int,
|
||||
address: str,
|
||||
server_flag: int = 0,
|
||||
address: str = "",
|
||||
phone: str = "",
|
||||
protocol: int = PROTO_LBS_ADDRESS_REQ,
|
||||
is_alarm: bool = False,
|
||||
) -> bytes:
|
||||
"""
|
||||
Build a Chinese address reply packet.
|
||||
Build a Chinese address reply packet (0x17).
|
||||
|
||||
Used as a response to protocol 0x17 (LBS Address Request)
|
||||
or similar address query protocols.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
serial_number : int
|
||||
Packet serial number.
|
||||
server_flag : int
|
||||
Server flag bits (32-bit).
|
||||
address : str
|
||||
Address string (encoded as UTF-16 Big-Endian).
|
||||
phone : str
|
||||
Phone number string (BCD encoded, even length, padded with 'F').
|
||||
protocol : int
|
||||
Protocol number to respond with (default 0x17).
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
Complete packet.
|
||||
Format: cmd_length(1) + server_flag(4) + ADDRESS/ALARMSMS + && + addr(UTF16BE) + && + phone(21) + ##
|
||||
"""
|
||||
flag_bytes = struct.pack("!I", server_flag)
|
||||
marker = b"ALARMSMS" if is_alarm else b"ADDRESS"
|
||||
separator = b"&&"
|
||||
terminator = b"##"
|
||||
|
||||
addr_bytes = address.encode("utf-16-be")
|
||||
addr_len = len(addr_bytes)
|
||||
# Phone field: 21 bytes ASCII, zero-padded
|
||||
phone_bytes = phone.encode("ascii", errors="ignore")[:21].ljust(21, b"0")
|
||||
|
||||
info = struct.pack("!I", server_flag) # 4 bytes server flag
|
||||
info += struct.pack("!H", addr_len) # 2 bytes address length
|
||||
info += addr_bytes # N bytes address
|
||||
|
||||
if phone:
|
||||
phone_padded = phone if len(phone) % 2 == 0 else phone + "F"
|
||||
phone_bcd = bytes.fromhex(phone_padded)
|
||||
info += struct.pack("B", len(phone_bcd)) # 1 byte phone length
|
||||
info += phone_bcd # N bytes phone BCD
|
||||
else:
|
||||
info += struct.pack("B", 0) # 0 phone length
|
||||
inner = flag_bytes + marker + separator + addr_bytes + separator + phone_bytes + terminator
|
||||
# 0x17 uses 1-byte cmd_length
|
||||
cmd_len = min(len(inner), 0xFF)
|
||||
info = bytes([cmd_len]) + inner
|
||||
|
||||
return self.build_response(protocol, serial_number, info)
|
||||
|
||||
def build_address_reply_en(
|
||||
self,
|
||||
serial_number: int,
|
||||
server_flag: int,
|
||||
address: str,
|
||||
server_flag: int = 0,
|
||||
address: str = "",
|
||||
phone: str = "",
|
||||
protocol: int = PROTO_ADDRESS_REPLY_EN,
|
||||
is_alarm: bool = False,
|
||||
) -> bytes:
|
||||
"""
|
||||
Build an English address reply packet (0x97).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
serial_number : int
|
||||
Packet serial number.
|
||||
server_flag : int
|
||||
Server flag bits (32-bit).
|
||||
address : str
|
||||
Address string (ASCII/UTF-8 encoded).
|
||||
phone : str
|
||||
Phone number string (BCD encoded, even length, padded with 'F').
|
||||
protocol : int
|
||||
Protocol number to respond with (default 0x97).
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
Complete packet.
|
||||
Format: cmd_length(2) + server_flag(4) + ADDRESS/ALARMSMS + && + addr(UTF-8) + && + phone(21) + ##
|
||||
"""
|
||||
flag_bytes = struct.pack("!I", server_flag)
|
||||
marker = b"ALARMSMS" if is_alarm else b"ADDRESS"
|
||||
separator = b"&&"
|
||||
terminator = b"##"
|
||||
|
||||
addr_bytes = address.encode("utf-8")
|
||||
addr_len = len(addr_bytes)
|
||||
phone_bytes = phone.encode("ascii", errors="ignore")[:21].ljust(21, b"0")
|
||||
|
||||
info = struct.pack("!I", server_flag) # 4 bytes server flag
|
||||
info += struct.pack("!H", addr_len) # 2 bytes address length
|
||||
info += addr_bytes # N bytes address
|
||||
|
||||
if phone:
|
||||
phone_padded = phone if len(phone) % 2 == 0 else phone + "F"
|
||||
phone_bcd = bytes.fromhex(phone_padded)
|
||||
info += struct.pack("B", len(phone_bcd)) # 1 byte phone length
|
||||
info += phone_bcd # N bytes phone BCD
|
||||
else:
|
||||
info += struct.pack("B", 0) # 0 phone length
|
||||
inner = flag_bytes + marker + separator + addr_bytes + separator + phone_bytes + terminator
|
||||
# 0x97 uses 2-byte cmd_length
|
||||
info = struct.pack("!H", len(inner)) + inner
|
||||
|
||||
return self.build_response(protocol, serial_number, info)
|
||||
|
||||
Reference in New Issue
Block a user