python-jebao 0.1.6__tar.gz → 0.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_jebao-0.1.6 → python_jebao-0.1.7}/PKG-INFO +1 -1
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/__init__.py +1 -1
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/const.py +4 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/discovery.py +2 -2
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/protocol.py +27 -13
- {python_jebao-0.1.6 → python_jebao-0.1.7}/pyproject.toml +1 -1
- {python_jebao-0.1.6 → python_jebao-0.1.7}/python_jebao.egg-info/PKG-INFO +1 -1
- {python_jebao-0.1.6 → python_jebao-0.1.7}/LICENSE +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/README.md +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/device.py +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/exceptions.py +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/mdp20000.py +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/jebao/retry.py +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/python_jebao.egg-info/SOURCES.txt +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/python_jebao.egg-info/dependency_links.txt +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/python_jebao.egg-info/requires.txt +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/python_jebao.egg-info/top_level.txt +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/setup.cfg +0 -0
- {python_jebao-0.1.6 → python_jebao-0.1.7}/setup.py +0 -0
|
@@ -31,8 +31,12 @@ CONTROL_COMMAND_SIZE = 323
|
|
|
31
31
|
|
|
32
32
|
# Product keys
|
|
33
33
|
PRODUCT_KEY_MDP20000 = "04020B28"
|
|
34
|
+
PRODUCT_KEY_MDP20000_V2 = "04D30Q34" # Newer MDP-20000 hardware revision
|
|
34
35
|
PRODUCT_KEY_MD44 = "04020B27" # Assumed, verify if needed
|
|
35
36
|
|
|
37
|
+
# All known product keys that identify an MDP-20000 (across hardware revisions)
|
|
38
|
+
PRODUCT_KEYS_MDP20000 = frozenset({PRODUCT_KEY_MDP20000, PRODUCT_KEY_MDP20000_V2})
|
|
39
|
+
|
|
36
40
|
# Model identifiers
|
|
37
41
|
MODEL_MDP20000 = "MDP-20000"
|
|
38
42
|
MODEL_MD44 = "MD-4.4"
|
|
@@ -12,7 +12,7 @@ from .const import (
|
|
|
12
12
|
MODEL_MD44,
|
|
13
13
|
MODEL_MDP20000,
|
|
14
14
|
PRODUCT_KEY_MD44,
|
|
15
|
-
|
|
15
|
+
PRODUCT_KEYS_MDP20000,
|
|
16
16
|
UDP_DISCOVERY_PORT,
|
|
17
17
|
UDP_LISTEN_PORT,
|
|
18
18
|
)
|
|
@@ -357,7 +357,7 @@ class JebaoDiscovery:
|
|
|
357
357
|
product_key = data[product_key_start : product_key_start + 8].rstrip(b"\x00").decode("ascii")
|
|
358
358
|
|
|
359
359
|
# Determine model from product key
|
|
360
|
-
if product_key
|
|
360
|
+
if product_key in PRODUCT_KEYS_MDP20000:
|
|
361
361
|
model = MODEL_MDP20000
|
|
362
362
|
elif product_key == PRODUCT_KEY_MD44:
|
|
363
363
|
model = MODEL_MD44
|
|
@@ -544,7 +544,8 @@ class JebaoProtocol:
|
|
|
544
544
|
Args:
|
|
545
545
|
timeout: How long to wait for bytes (default 0.1s)
|
|
546
546
|
"""
|
|
547
|
-
|
|
547
|
+
reader = self._reader
|
|
548
|
+
if reader is None:
|
|
548
549
|
return
|
|
549
550
|
|
|
550
551
|
try:
|
|
@@ -553,7 +554,7 @@ class JebaoProtocol:
|
|
|
553
554
|
try:
|
|
554
555
|
# Try to read header of next message with short timeout
|
|
555
556
|
header = await asyncio.wait_for(
|
|
556
|
-
|
|
557
|
+
reader.read(5), timeout=timeout
|
|
557
558
|
)
|
|
558
559
|
if not header:
|
|
559
560
|
# No more data
|
|
@@ -567,13 +568,13 @@ class JebaoProtocol:
|
|
|
567
568
|
# Pump sent an unsolicited message (e.g. periodic status broadcast).
|
|
568
569
|
# Consume the full frame so we don't desync the stream.
|
|
569
570
|
length = header[4]
|
|
570
|
-
body = await
|
|
571
|
+
body = await reader.readexactly(length)
|
|
571
572
|
msg_type = body[2] if len(body) > 2 else 0
|
|
572
573
|
total = len(header) + len(body)
|
|
573
574
|
# Type 0x00 messages carry an extra 129-byte padding frame
|
|
574
575
|
# (same as _read_raw). Skip it or stale bytes corrupt the next read.
|
|
575
576
|
if msg_type == MSG_EXTENDED_DATA:
|
|
576
|
-
padding = await
|
|
577
|
+
padding = await reader.readexactly(129)
|
|
577
578
|
total += len(padding)
|
|
578
579
|
_LOGGER.debug(
|
|
579
580
|
"Drained unsolicited message: type=0x%02x, %d bytes total",
|
|
@@ -618,15 +619,21 @@ class JebaoProtocol:
|
|
|
618
619
|
Raises:
|
|
619
620
|
JebaoConnectionError: Read failed
|
|
620
621
|
"""
|
|
621
|
-
if not self._reader:
|
|
622
|
-
raise JebaoConnectionError("Not connected")
|
|
623
|
-
|
|
624
622
|
async with self._read_lock:
|
|
623
|
+
# Bind the reader locally under the lock. disconnect() (called from
|
|
624
|
+
# our own IncompleteReadError handler, from _send_raw's error path,
|
|
625
|
+
# and from the coordinator) sets self._reader = None from another
|
|
626
|
+
# task; re-reading self._reader across the awaits below would raise
|
|
627
|
+
# AttributeError. Reading from a captured, closed StreamReader raises
|
|
628
|
+
# IncompleteReadError/ConnectionResetError instead, which we handle.
|
|
629
|
+
reader = self._reader
|
|
630
|
+
if reader is None:
|
|
631
|
+
raise JebaoConnectionError("Not connected")
|
|
625
632
|
try:
|
|
626
633
|
# Read header (4 bytes) + length byte (1 byte) = 5 bytes total
|
|
627
634
|
# Format: 00 00 00 03 [LENGTH]
|
|
628
635
|
_LOGGER.debug("Reading next message header...")
|
|
629
|
-
header = await
|
|
636
|
+
header = await reader.readexactly(5)
|
|
630
637
|
|
|
631
638
|
# Validate header - if invalid, try to re-sync
|
|
632
639
|
max_resync_attempts = 50
|
|
@@ -650,7 +657,7 @@ class JebaoProtocol:
|
|
|
650
657
|
max_resync_attempts,
|
|
651
658
|
)
|
|
652
659
|
# Shift header left by 1 byte and read 1 new byte
|
|
653
|
-
next_byte = await
|
|
660
|
+
next_byte = await reader.readexactly(1)
|
|
654
661
|
header = header[1:] + next_byte
|
|
655
662
|
else:
|
|
656
663
|
# Failed to re-sync after max attempts
|
|
@@ -662,7 +669,7 @@ class JebaoProtocol:
|
|
|
662
669
|
_LOGGER.debug(f"Valid header found, reading {length} more bytes")
|
|
663
670
|
|
|
664
671
|
# Read the remaining bytes based on length field
|
|
665
|
-
remaining = await
|
|
672
|
+
remaining = await reader.readexactly(length)
|
|
666
673
|
|
|
667
674
|
result = header + remaining
|
|
668
675
|
msg_type = result[7] if len(result) > 7 else 0
|
|
@@ -672,7 +679,7 @@ class JebaoProtocol:
|
|
|
672
679
|
if msg_type == MSG_EXTENDED_DATA:
|
|
673
680
|
# Read fixed padding (129 bytes) that pump sends for extended responses
|
|
674
681
|
# The pump uses fixed-frame protocol with padding (0xee or 0x00)
|
|
675
|
-
padding = await
|
|
682
|
+
padding = await reader.readexactly(129)
|
|
676
683
|
|
|
677
684
|
# Validate padding - should be mostly 0xee or 0x00 patterns
|
|
678
685
|
# Check first 4 bytes and last byte
|
|
@@ -707,8 +714,15 @@ class JebaoProtocol:
|
|
|
707
714
|
f"Message complete: {len(result)} bytes, type=0x{msg_type:02x}"
|
|
708
715
|
)
|
|
709
716
|
return result
|
|
710
|
-
except asyncio.IncompleteReadError as err:
|
|
711
|
-
|
|
717
|
+
except (asyncio.IncompleteReadError, ConnectionResetError) as err:
|
|
718
|
+
# Expected when the pump drops an idle connection. The reconnect
|
|
719
|
+
# is logged by the coordinator/retry layer, so keep this at debug
|
|
720
|
+
# to avoid flooding the log every poll.
|
|
721
|
+
_LOGGER.debug(
|
|
722
|
+
"%s Connection closed by peer (%s) - will reconnect",
|
|
723
|
+
self.device_identifier,
|
|
724
|
+
type(err).__name__,
|
|
725
|
+
)
|
|
712
726
|
# Mark connection as closed so reconnection can happen
|
|
713
727
|
await self.disconnect()
|
|
714
728
|
raise JebaoConnectionError("Connection closed") from err
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|