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.
Files changed (26) hide show
  1. {yrcom-0.2.0/src/yrcom.egg-info → yrcom-0.3.0}/PKG-INFO +2 -2
  2. {yrcom-0.2.0 → yrcom-0.3.0}/README.md +1 -1
  3. {yrcom-0.2.0 → yrcom-0.3.0}/pyproject.toml +1 -1
  4. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/__init__.py +9 -1
  5. yrcom-0.3.0/src/yrcom/hses_server_codec.py +93 -0
  6. {yrcom-0.2.0 → yrcom-0.3.0/src/yrcom.egg-info}/PKG-INFO +2 -2
  7. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/SOURCES.txt +2 -0
  8. yrcom-0.3.0/tests/test_hses_server_codec.py +74 -0
  9. {yrcom-0.2.0 → yrcom-0.3.0}/LICENSE +0 -0
  10. {yrcom-0.2.0 → yrcom-0.3.0}/setup.cfg +0 -0
  11. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_client.py +0 -0
  12. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_errors.py +0 -0
  13. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_file.py +0 -0
  14. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/hses_sim.py +0 -0
  15. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/robot_connection.py +0 -0
  16. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom/yaskawa_client.py +0 -0
  17. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/dependency_links.txt +0 -0
  18. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/entry_points.txt +0 -0
  19. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/requires.txt +0 -0
  20. {yrcom-0.2.0 → yrcom-0.3.0}/src/yrcom.egg-info/top_level.txt +0 -0
  21. {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_hses_client.py +0 -0
  22. {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_hses_file.py +0 -0
  23. {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_hses_sim.py +0 -0
  24. {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_robot_connection.py +0 -0
  25. {yrcom-0.2.0 → yrcom-0.3.0}/tests/test_yaskawa_client.py +0 -0
  26. {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.2.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).
@@ -93,4 +93,4 @@ and a small HSES capture fixture (`tests/fixtures/`).
93
93
 
94
94
  ## License
95
95
 
96
- MIT — see [LICENSE](LICENSE).
96
+ 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.2.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.2.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.2.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