yrcom 0.2.0__tar.gz → 0.3.0__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.0}/PKG-INFO +2 -2
- {yrcom-0.2.0 → yrcom-0.3.0}/README.md +1 -1
- {yrcom-0.2.0 → yrcom-0.3.0}/pyproject.toml +1 -1
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/__init__.py +9 -1
- yrcom-0.3.0/src/yrcom/hses_server_codec.py +93 -0
- {yrcom-0.2.0 → yrcom-0.3.0/src/yrcom.egg-info}/PKG-INFO +2 -2
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/SOURCES.txt +2 -0
- yrcom-0.3.0/tests/test_hses_server_codec.py +74 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/LICENSE +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/setup.cfg +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_client.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_errors.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_file.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_sim.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/robot_connection.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/yaskawa_client.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/dependency_links.txt +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/entry_points.txt +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/requires.txt +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/top_level.txt +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_hses_client.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_hses_file.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_hses_sim.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_robot_connection.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_yaskawa_client.py +0 -0
- {yrcom-0.2.0 → yrcom-0.3.0}/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.0
|
|
4
4
|
Summary: Yaskawa robot communication — Ethernet Server, HSES and HSES File Control (pure stdlib)
|
|
5
5
|
Author: cmosbk
|
|
6
6
|
License: MIT
|
|
@@ -109,4 +109,4 @@ and a small HSES capture fixture (`tests/fixtures/`).
|
|
|
109
109
|
|
|
110
110
|
## License
|
|
111
111
|
|
|
112
|
-
MIT — see [LICENSE](LICENSE).
|
|
112
|
+
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.0"
|
|
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"
|
|
@@ -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.0"
|
|
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
|
]
|
|
@@ -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.0
|
|
4
4
|
Summary: Yaskawa robot communication — Ethernet Server, HSES and HSES File Control (pure stdlib)
|
|
5
5
|
Author: cmosbk
|
|
6
6
|
License: MIT
|
|
@@ -109,4 +109,4 @@ and a small HSES capture fixture (`tests/fixtures/`).
|
|
|
109
109
|
|
|
110
110
|
## License
|
|
111
111
|
|
|
112
|
-
MIT — see [LICENSE](LICENSE).
|
|
112
|
+
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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|