yrcom 0.2.0__tar.gz → 0.3.1__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.
- {yrcom-0.2.0/src/yrcom.egg-info → yrcom-0.3.1}/PKG-INFO +11 -2
- {yrcom-0.2.0 → yrcom-0.3.1}/README.md +5 -1
- {yrcom-0.2.0 → yrcom-0.3.1}/pyproject.toml +2 -1
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom/__init__.py +9 -1
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom/hses_client.py +21 -1
- yrcom-0.3.1/src/yrcom/hses_server_codec.py +93 -0
- {yrcom-0.2.0 → yrcom-0.3.1/src/yrcom.egg-info}/PKG-INFO +11 -2
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom.egg-info/SOURCES.txt +2 -0
- yrcom-0.3.1/src/yrcom.egg-info/requires.txt +9 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/tests/test_hses_client.py +24 -0
- yrcom-0.3.1/tests/test_hses_server_codec.py +74 -0
- yrcom-0.2.0/src/yrcom.egg-info/requires.txt +0 -3
- {yrcom-0.2.0 → yrcom-0.3.1}/LICENSE +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/setup.cfg +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom/hses_errors.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom/hses_file.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom/hses_sim.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom/robot_connection.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom/yaskawa_client.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom.egg-info/dependency_links.txt +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom.egg-info/entry_points.txt +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/src/yrcom.egg-info/top_level.txt +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/tests/test_hses_file.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/tests/test_hses_sim.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/tests/test_robot_connection.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/tests/test_yaskawa_client.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.1}/tests/test_yaskawa_commands.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yrcom
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Yaskawa robot communication — Ethernet Server, HSES and HSES File Control (pure stdlib)
|
|
5
5
|
Author: cmosbk
|
|
6
6
|
License: MIT
|
|
@@ -12,12 +12,21 @@ Description-Content-Type: text/markdown
|
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
15
|
+
Provides-Extra: docs
|
|
16
|
+
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
17
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
18
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
|
|
19
|
+
Requires-Dist: black>=24; extra == "docs"
|
|
15
20
|
Dynamic: license-file
|
|
16
21
|
|
|
17
22
|
# yrcom
|
|
18
23
|
|
|
19
24
|
[](https://github.com/CMOSBK/yrcom/actions/workflows/ci.yml)
|
|
20
25
|
|
|
26
|
+
📖 **Documentation: <https://cmosbk.github.io/yrcom/>** — getting started, the
|
|
27
|
+
transport guide and the full API reference (every command, generated from the
|
|
28
|
+
source).
|
|
29
|
+
|
|
21
30
|
Yaskawa robot communication as a standalone Python library — a single,
|
|
22
31
|
hardware-verified communication core meant to be shared across projects
|
|
23
32
|
instead of being duplicated.
|
|
@@ -109,4 +118,4 @@ and a small HSES capture fixture (`tests/fixtures/`).
|
|
|
109
118
|
|
|
110
119
|
## License
|
|
111
120
|
|
|
112
|
-
MIT — see [LICENSE](LICENSE).
|
|
121
|
+
MIT — see [LICENSE](https://github.com/CMOSBK/yrcom/blob/main/LICENSE).
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/CMOSBK/yrcom/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
+
📖 **Documentation: <https://cmosbk.github.io/yrcom/>** — getting started, the
|
|
6
|
+
transport guide and the full API reference (every command, generated from the
|
|
7
|
+
source).
|
|
8
|
+
|
|
5
9
|
Yaskawa robot communication as a standalone Python library — a single,
|
|
6
10
|
hardware-verified communication core meant to be shared across projects
|
|
7
11
|
instead of being duplicated.
|
|
@@ -93,4 +97,4 @@ and a small HSES capture fixture (`tests/fixtures/`).
|
|
|
93
97
|
|
|
94
98
|
## License
|
|
95
99
|
|
|
96
|
-
MIT — see [LICENSE](LICENSE).
|
|
100
|
+
MIT — see [LICENSE](https://github.com/CMOSBK/yrcom/blob/main/LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "yrcom"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Yaskawa robot communication — Ethernet Server, HSES and HSES File Control (pure stdlib)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -15,6 +15,7 @@ dependencies = []
|
|
|
15
15
|
|
|
16
16
|
[project.optional-dependencies]
|
|
17
17
|
dev = ["pytest>=8"]
|
|
18
|
+
docs = ["mkdocs>=1.6", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.26", "black>=24"]
|
|
18
19
|
|
|
19
20
|
[project.scripts]
|
|
20
21
|
yrcom-sim = "yrcom.hses_sim:_main"
|
|
@@ -32,8 +32,15 @@ from .robot_connection import (
|
|
|
32
32
|
TCP_PORT,
|
|
33
33
|
)
|
|
34
34
|
from .hses_sim import HsesReplayServer
|
|
35
|
+
from .hses_server_codec import (
|
|
36
|
+
ServerCommand,
|
|
37
|
+
MalformedRequest,
|
|
38
|
+
decode_request,
|
|
39
|
+
encode_response,
|
|
40
|
+
encode_error,
|
|
41
|
+
)
|
|
35
42
|
|
|
36
|
-
__version__ = "0.
|
|
43
|
+
__version__ = "0.3.1"
|
|
37
44
|
|
|
38
45
|
__all__ = [
|
|
39
46
|
"YaskawaClient", "VarType", "EthernetServerClient",
|
|
@@ -45,4 +52,5 @@ __all__ = [
|
|
|
45
52
|
"RobotConnection", "PersistentTcpClient", "HsesWriteBlockedError",
|
|
46
53
|
"TRANSPORT_HSES", "TRANSPORT_ETHERNET", "HSES_PORT", "TCP_PORT",
|
|
47
54
|
"HsesReplayServer",
|
|
55
|
+
"ServerCommand", "MalformedRequest", "decode_request", "encode_response", "encode_error",
|
|
48
56
|
]
|
|
@@ -581,6 +581,8 @@ class HsesClient(_HsesSocket):
|
|
|
581
581
|
position_group: int = 0,
|
|
582
582
|
b_vars: "list[dict]" = [],
|
|
583
583
|
i_vars: "list[dict]" = [],
|
|
584
|
+
d_vars: "list[dict]" = [],
|
|
585
|
+
r_vars: "list[dict]" = [],
|
|
584
586
|
io_signals: "list[dict]" = [],
|
|
585
587
|
read_cio: bool = False,
|
|
586
588
|
) -> dict:
|
|
@@ -590,7 +592,11 @@ class HsesClient(_HsesSocket):
|
|
|
590
592
|
alarm_slots: number of alarm history slots (0=skip, max 4)
|
|
591
593
|
include_job: read executing job info
|
|
592
594
|
position_group: 0=skip, 1-8=pulse, 101-108=cartesian
|
|
593
|
-
b_vars/i_vars:
|
|
595
|
+
b_vars/i_vars/d_vars/r_vars:
|
|
596
|
+
list of {"label": str, "no": int}. Each becomes one
|
|
597
|
+
request inside the burst, so its value shares the
|
|
598
|
+
instant of the rest of the row rather than arriving a
|
|
599
|
+
round trip later.
|
|
594
600
|
io_signals: list of {"label": str, "logical": int}
|
|
595
601
|
|
|
596
602
|
Returns flat dict of all decoded fields.
|
|
@@ -622,6 +628,14 @@ class HsesClient(_HsesSocket):
|
|
|
622
628
|
reqs.append((_CMD_I_VAR, v["no"], 1, _SVC_GET_SINGLE, b""))
|
|
623
629
|
tags.append(f"i_var:{v['label']}")
|
|
624
630
|
|
|
631
|
+
for v in d_vars:
|
|
632
|
+
reqs.append((_CMD_D_VAR, v["no"], 1, _SVC_GET_SINGLE, b""))
|
|
633
|
+
tags.append(f"d_var:{v['label']}")
|
|
634
|
+
|
|
635
|
+
for v in r_vars:
|
|
636
|
+
reqs.append((_CMD_R_VAR, v["no"], 1, _SVC_GET_SINGLE, b""))
|
|
637
|
+
tags.append(f"r_var:{v['label']}")
|
|
638
|
+
|
|
625
639
|
# CIO counter: low byte at logical 72000 (inst 7200), high byte at 72010 (inst 7201)
|
|
626
640
|
if read_cio:
|
|
627
641
|
reqs.append((_CMD_IO_PLURAL, 7200, 0, _SVC_READ_ALL, struct.pack("<I", 2)))
|
|
@@ -666,6 +680,12 @@ class HsesClient(_HsesSocket):
|
|
|
666
680
|
elif tag.startswith("i_var:"):
|
|
667
681
|
if resp.status == 0 and len(resp.data) >= 2:
|
|
668
682
|
out[f"var_{tag[6:]}"] = struct.unpack_from("<h", resp.data, 0)[0]
|
|
683
|
+
elif tag.startswith("d_var:"):
|
|
684
|
+
if resp.status == 0 and len(resp.data) >= 4:
|
|
685
|
+
out[f"var_{tag[6:]}"] = struct.unpack_from("<i", resp.data, 0)[0]
|
|
686
|
+
elif tag.startswith("r_var:"):
|
|
687
|
+
if resp.status == 0 and len(resp.data) >= 4:
|
|
688
|
+
out[f"var_{tag[6:]}"] = struct.unpack_from("<f", resp.data, 0)[0]
|
|
669
689
|
elif tag == "cio":
|
|
670
690
|
if resp.status == 0 and len(resp.data) >= 6:
|
|
671
691
|
# response: 4-byte count + 2 data bytes
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Server-side HSES codec — the mirror image of hses_client.py's request
|
|
3
|
+
builder/response parser. Decodes incoming request bytes into a
|
|
4
|
+
ServerCommand; encodes results back into wire bytes.
|
|
5
|
+
|
|
6
|
+
Wire header is the 32-byte YERC header, identical in both directions:
|
|
7
|
+
0:4 magic "YERC"
|
|
8
|
+
4:6 header size (u16, always 32)
|
|
9
|
+
6:8 data size (u16)
|
|
10
|
+
8 Reserve1 (0x03 fixed)
|
|
11
|
+
9 processing division (1 = robot control)
|
|
12
|
+
10 ACK (0 = request, 1 = response)
|
|
13
|
+
11 request id (echoed back)
|
|
14
|
+
12:16 block number
|
|
15
|
+
16:24 Reserve2 ("99999999" fixed)
|
|
16
|
+
24:26 command (u16) — in a response this is the ECHOED command
|
|
17
|
+
26:28 request: instance (u16) | response: status (byte) + pad
|
|
18
|
+
28 request: attribute (byte) | response: added-status size (byte)
|
|
19
|
+
29 request: service (byte) | response: pad
|
|
20
|
+
30:32 request: padding | response: added-status word (u16)
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import struct
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
|
|
27
|
+
_MAGIC = b"YERC"
|
|
28
|
+
_HDR_SIZE = 0x0020
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class ServerCommand:
|
|
33
|
+
cmd: int
|
|
34
|
+
instance: int
|
|
35
|
+
attribute: int
|
|
36
|
+
service: int
|
|
37
|
+
req_id: int
|
|
38
|
+
payload: bytes
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MalformedRequest(Exception):
|
|
42
|
+
"""Raw bytes are too short, or the header magic/size doesn't match."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def decode_request(raw: bytes) -> ServerCommand:
|
|
46
|
+
if len(raw) < _HDR_SIZE:
|
|
47
|
+
raise MalformedRequest(f"request too short: {len(raw)} B")
|
|
48
|
+
if raw[0:4] != _MAGIC:
|
|
49
|
+
raise MalformedRequest(f"bad magic: {raw[0:4]!r}")
|
|
50
|
+
data_size = struct.unpack_from("<H", raw, 6)[0]
|
|
51
|
+
req_id = raw[11]
|
|
52
|
+
cmd = struct.unpack_from("<H", raw, 24)[0]
|
|
53
|
+
instance = struct.unpack_from("<H", raw, 26)[0]
|
|
54
|
+
attribute = raw[28]
|
|
55
|
+
service = raw[29]
|
|
56
|
+
payload = raw[_HDR_SIZE:_HDR_SIZE + data_size]
|
|
57
|
+
return ServerCommand(cmd=cmd, instance=instance, attribute=attribute,
|
|
58
|
+
service=service, req_id=req_id, payload=payload)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _response_header(cmd: int, req_id: int, data_size: int) -> bytearray:
|
|
62
|
+
hdr = bytearray(_HDR_SIZE)
|
|
63
|
+
hdr[0:4] = _MAGIC
|
|
64
|
+
struct.pack_into("<H", hdr, 4, _HDR_SIZE)
|
|
65
|
+
struct.pack_into("<H", hdr, 6, data_size)
|
|
66
|
+
hdr[8] = 0x03
|
|
67
|
+
hdr[9] = 1 # processing division: robot control
|
|
68
|
+
hdr[10] = 0x01 # ACK = response
|
|
69
|
+
hdr[11] = req_id
|
|
70
|
+
struct.pack_into("<I", hdr, 12, 0)
|
|
71
|
+
hdr[16:24] = b"99999999"
|
|
72
|
+
struct.pack_into("<H", hdr, 24, cmd)
|
|
73
|
+
return hdr
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def encode_response(cmd: int, req_id: int, data: bytes = b"") -> bytes:
|
|
77
|
+
"""Success response (status 0x00, no added-status)."""
|
|
78
|
+
hdr = _response_header(cmd, req_id, len(data))
|
|
79
|
+
hdr[26] = 0x00 # status
|
|
80
|
+
hdr[27] = 0
|
|
81
|
+
hdr[28] = 0 # added-status size
|
|
82
|
+
struct.pack_into("<H", hdr, 30, 0)
|
|
83
|
+
return bytes(hdr) + data
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def encode_error(cmd: int, req_id: int, status: int, added: int = 0) -> bytes:
|
|
87
|
+
"""Error response — empty data, given status/added-status."""
|
|
88
|
+
hdr = _response_header(cmd, req_id, 0)
|
|
89
|
+
hdr[26] = status
|
|
90
|
+
hdr[27] = 0
|
|
91
|
+
hdr[28] = 1 if added else 0
|
|
92
|
+
struct.pack_into("<H", hdr, 30, added)
|
|
93
|
+
return bytes(hdr)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yrcom
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Yaskawa robot communication — Ethernet Server, HSES and HSES File Control (pure stdlib)
|
|
5
5
|
Author: cmosbk
|
|
6
6
|
License: MIT
|
|
@@ -12,12 +12,21 @@ Description-Content-Type: text/markdown
|
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
15
|
+
Provides-Extra: docs
|
|
16
|
+
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
17
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
18
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
|
|
19
|
+
Requires-Dist: black>=24; extra == "docs"
|
|
15
20
|
Dynamic: license-file
|
|
16
21
|
|
|
17
22
|
# yrcom
|
|
18
23
|
|
|
19
24
|
[](https://github.com/CMOSBK/yrcom/actions/workflows/ci.yml)
|
|
20
25
|
|
|
26
|
+
📖 **Documentation: <https://cmosbk.github.io/yrcom/>** — getting started, the
|
|
27
|
+
transport guide and the full API reference (every command, generated from the
|
|
28
|
+
source).
|
|
29
|
+
|
|
21
30
|
Yaskawa robot communication as a standalone Python library — a single,
|
|
22
31
|
hardware-verified communication core meant to be shared across projects
|
|
23
32
|
instead of being duplicated.
|
|
@@ -109,4 +118,4 @@ and a small HSES capture fixture (`tests/fixtures/`).
|
|
|
109
118
|
|
|
110
119
|
## License
|
|
111
120
|
|
|
112
|
-
MIT — see [LICENSE](LICENSE).
|
|
121
|
+
MIT — see [LICENSE](https://github.com/CMOSBK/yrcom/blob/main/LICENSE).
|
|
@@ -5,6 +5,7 @@ src/yrcom/__init__.py
|
|
|
5
5
|
src/yrcom/hses_client.py
|
|
6
6
|
src/yrcom/hses_errors.py
|
|
7
7
|
src/yrcom/hses_file.py
|
|
8
|
+
src/yrcom/hses_server_codec.py
|
|
8
9
|
src/yrcom/hses_sim.py
|
|
9
10
|
src/yrcom/robot_connection.py
|
|
10
11
|
src/yrcom/yaskawa_client.py
|
|
@@ -16,6 +17,7 @@ src/yrcom.egg-info/requires.txt
|
|
|
16
17
|
src/yrcom.egg-info/top_level.txt
|
|
17
18
|
tests/test_hses_client.py
|
|
18
19
|
tests/test_hses_file.py
|
|
20
|
+
tests/test_hses_server_codec.py
|
|
19
21
|
tests/test_hses_sim.py
|
|
20
22
|
tests/test_robot_connection.py
|
|
21
23
|
tests/test_yaskawa_client.py
|
|
@@ -624,6 +624,30 @@ def test_poll_all_status_and_job():
|
|
|
624
624
|
assert out["job_name"] == "MASTER"
|
|
625
625
|
|
|
626
626
|
|
|
627
|
+
def test_poll_all_reads_every_variable_kind_in_one_burst():
|
|
628
|
+
"""D and R used to be reachable only as standalone reads, which put their
|
|
629
|
+
value a round trip away from the rest of the row."""
|
|
630
|
+
status_data = struct.pack("<II", 0x40, 0x40)
|
|
631
|
+
c, _ = _client([
|
|
632
|
+
_reply(hc._CMD_STATUS, status_data, req_id=1),
|
|
633
|
+
_reply(hc._CMD_B_VAR, struct.pack("<B", 7), req_id=2),
|
|
634
|
+
_reply(hc._CMD_I_VAR, struct.pack("<h", -3), req_id=3),
|
|
635
|
+
_reply(hc._CMD_D_VAR, struct.pack("<i", -70000), req_id=4),
|
|
636
|
+
_reply(hc._CMD_R_VAR, struct.pack("<f", 1.5), req_id=5),
|
|
637
|
+
])
|
|
638
|
+
out = c.poll_all(
|
|
639
|
+
include_job=False,
|
|
640
|
+
b_vars=[{"label": "b", "no": 1}],
|
|
641
|
+
i_vars=[{"label": "i", "no": 2}],
|
|
642
|
+
d_vars=[{"label": "d", "no": 3}],
|
|
643
|
+
r_vars=[{"label": "r", "no": 4}],
|
|
644
|
+
)
|
|
645
|
+
assert out["var_b"] == 7
|
|
646
|
+
assert out["var_i"] == -3
|
|
647
|
+
assert out["var_d"] == -70000 # signed 32-bit, beyond an I variable
|
|
648
|
+
assert out["var_r"] == pytest.approx(1.5)
|
|
649
|
+
|
|
650
|
+
|
|
627
651
|
def test_poll_all_raises_on_status_error():
|
|
628
652
|
c, _ = _client([_reply(hc._CMD_STATUS, b"", req_id=1, status=0x08)])
|
|
629
653
|
with pytest.raises(HsesError):
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Pure protocol-logic tests — no sockets, no MotoSim. Builds real request
|
|
2
|
+
bytes via HsesClient._build (the verified client-side packer) and checks the
|
|
3
|
+
new server-side decoder reads them back correctly."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from yrcom.hses_client import HsesClient
|
|
9
|
+
from yrcom.hses_server_codec import MalformedRequest, ServerCommand, decode_request
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_decode_matches_a_real_status_request():
|
|
13
|
+
raw = HsesClient._build(cmd=0x72, instance=1, attribute=0, service=0x01, req_id=5)
|
|
14
|
+
sc = decode_request(raw)
|
|
15
|
+
assert sc == ServerCommand(cmd=0x72, instance=1, attribute=0, service=0x01,
|
|
16
|
+
req_id=5, payload=b"")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_decode_carries_payload():
|
|
20
|
+
raw = HsesClient._build(cmd=0x7A, instance=3, attribute=1, service=0x10,
|
|
21
|
+
req_id=200, payload=b"\x2a\x00\x00\x00")
|
|
22
|
+
sc = decode_request(raw)
|
|
23
|
+
assert sc.cmd == 0x7A
|
|
24
|
+
assert sc.instance == 3
|
|
25
|
+
assert sc.payload == b"\x2a\x00\x00\x00"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_decode_rejects_short_input():
|
|
29
|
+
with pytest.raises(MalformedRequest):
|
|
30
|
+
decode_request(b"too short")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_decode_rejects_bad_magic():
|
|
34
|
+
raw = bytearray(HsesClient._build(cmd=0x72, instance=1, attribute=0,
|
|
35
|
+
service=0x01, req_id=1))
|
|
36
|
+
raw[0:4] = b"XXXX"
|
|
37
|
+
with pytest.raises(MalformedRequest):
|
|
38
|
+
decode_request(bytes(raw))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_encode_response_round_trips_through_real_client_parser():
|
|
42
|
+
# HsesClient._parse_resp is the real, hardware-verified response parser —
|
|
43
|
+
# proving our encoder's bytes satisfy it is the strongest test we have
|
|
44
|
+
# without a socket.
|
|
45
|
+
from yrcom.hses_client import HsesClient
|
|
46
|
+
from yrcom.hses_server_codec import encode_response
|
|
47
|
+
|
|
48
|
+
raw = encode_response(cmd=0x72, req_id=9, data=b"\x01\x00\x00\x00\x00\x00\x00\x00")
|
|
49
|
+
resp = HsesClient._parse_resp(raw)
|
|
50
|
+
assert resp.cmd == 0x72
|
|
51
|
+
assert resp.status == 0x00
|
|
52
|
+
assert resp.added == 0
|
|
53
|
+
assert resp.data == b"\x01\x00\x00\x00\x00\x00\x00\x00"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_encode_error_round_trips_and_raises_hses_error():
|
|
57
|
+
from yrcom.hses_client import HsesClient, HsesError
|
|
58
|
+
from yrcom.hses_server_codec import encode_error
|
|
59
|
+
|
|
60
|
+
raw = encode_error(cmd=0x75, req_id=1, status=0x1F, added=0xB005)
|
|
61
|
+
resp = HsesClient._parse_resp(raw)
|
|
62
|
+
assert resp.status == 0x1F
|
|
63
|
+
assert resp.added == 0xB005
|
|
64
|
+
assert resp.data == b""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_encode_error_without_added_status():
|
|
68
|
+
from yrcom.hses_client import HsesClient
|
|
69
|
+
from yrcom.hses_server_codec import encode_error
|
|
70
|
+
|
|
71
|
+
raw = encode_error(cmd=0x00, req_id=1, status=0x08)
|
|
72
|
+
resp = HsesClient._parse_resp(raw)
|
|
73
|
+
assert resp.status == 0x08
|
|
74
|
+
assert resp.added == 0
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|