cta2045 0.1.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.
cta2045-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clark Communications Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cta2045-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: cta2045
3
+ Version: 0.1.0
4
+ Summary: CTA-2045 (ANSI/CTA-2045-B) protocol library — encode/decode of the SGD↔UCM demand-response interface
5
+ Author: Clark Communications Corporation
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://ebus.energy
8
+ Project-URL: Repository, https://github.com/electrification-bus/python-cta2045
9
+ Project-URL: Issues, https://github.com/electrification-bus/python-cta2045/issues
10
+ Keywords: cta-2045,cta2045,ecoport,demand-response,smart-grid,water-heater,ucm,sgd
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Home Automation
19
+ Classifier: Topic :: System :: Hardware
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # python-cta2045
28
+
29
+ [![PyPI version](https://img.shields.io/pypi/v/cta2045.svg)](https://pypi.org/project/cta2045/)
30
+ [![Python versions](https://img.shields.io/pypi/pyversions/cta2045.svg)](https://pypi.org/project/cta2045/)
31
+ [![CI](https://github.com/electrification-bus/python-cta2045/actions/workflows/ci.yml/badge.svg)](https://github.com/electrification-bus/python-cta2045/actions/workflows/ci.yml)
32
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+
35
+ A CTA-2045 (ANSI/CTA-2045-B) protocol library in Python — encode/decode of the demand-response interface between a Smart Grid Device (SGD, e.g. a water heater) and a Universal Communications Module (UCM).
36
+
37
+ > **Status: pre-alpha.** The core protocol — Basic DR and Intermediate DR encode/decode, plus an abstract UCM interface — is implemented and tested. The public API may still change before 1.0, and the package is not yet published to PyPI.
38
+ >
39
+ > **Not certified.** This is an independent implementation of the published protocol. It has **not** been tested or certified under any conformance program (EcoPort or otherwise), and carries no warranty of interoperability with any certified device.
40
+
41
+ ## What is CTA-2045?
42
+
43
+ CTA-2045 (consumer-facing brand: **EcoPort**) standardizes a modular communications socket on an appliance (the SGD) into which a UCM plugs to provide grid demand-response. CTA-2045 specifies only the SGD↔UCM link; a UCM's upward (network) interface is vendor-specific. This library implements the CTA-2045 message layer itself, independent of any vendor or transport: bytes (or ASCII-hex) in, structured Python objects out, and back. It has no runtime dependencies and does no I/O.
44
+
45
+ See [References](#references) for the standard and the EcoPort program.
46
+
47
+ ## Install
48
+
49
+ Not yet on PyPI. For now, install from source:
50
+
51
+ ```
52
+ pip install -e ".[dev]"
53
+ ```
54
+
55
+ Requires Python 3.10+.
56
+
57
+ ## Quick start
58
+
59
+ Encode a command to send to a device, and decode messages received from one:
60
+
61
+ ```python
62
+ from cta2045 import app
63
+ from cta2045.codec import bytes_to_hex
64
+
65
+ # Encode a 10-minute shed command (UCM -> SGD). Durations are in MINUTES.
66
+ msg = app.shed(10)
67
+ msg.to_bytes() # b'\x08\x01\x00\x02\x01\x11'
68
+ bytes_to_hex(msg.to_bytes()) # '080100020111'
69
+
70
+ # Decode messages received from a device (one or more concatenated frames):
71
+ for m in app.decode_hex('080100021302'):
72
+ print(m.category, m.operational_state)
73
+ # BasicDRCategory.State_Query_Response OperationalState.Running_Curtailed
74
+ ```
75
+
76
+ ### Basic DR commands
77
+
78
+ ```python
79
+ app.shed(10) # curtail load for 10 minutes
80
+ app.end() # end the current shed event
81
+ app.load_up(30) # store energy for 30 minutes
82
+ app.critical_peak(60) # critical peak event
83
+ app.grid_emergency(15) # grid emergency event
84
+ app.power_level(50) # request 50% power
85
+ ```
86
+
87
+ Each returns a `BasicDR` object; call `.to_bytes()` for the wire frame. Durations are minutes; pass an explicit `cta2045.codec.Duration` for the `Unknown` / `Too Long` sentinels or for second-level control. Note the wire format quantizes durations (it can only carry `2·n²` seconds) — use `Duration.nearest()` to see the value that will actually be transmitted.
88
+
89
+ ### Advanced Load Up
90
+
91
+ ```python
92
+ from cta2045 import app
93
+ from cta2045.codec import bytes_to_hex
94
+ from cta2045.enums import AdvancedLoadUpUnits
95
+
96
+ # Store 500 Wh (5 × 100 Wh) of extra energy over 60 minutes (CTA-2045-B § 11.6)
97
+ cmd = app.advanced_load_up(60, 5, AdvancedLoadUpUnits.Wh_100)
98
+ bytes_to_hex(cmd.to_bytes()) # '080200070C00003C000502'
99
+ ```
100
+
101
+ ### Decoding device replies
102
+
103
+ `decode_all(bytes)` / `decode_hex(str)` return a list of message objects — `BasicDR`, `IntermediateDR` (whose `.body` is a `CommodityReadReply`, `GetInformationReply`, `AdvancedLoadUp`, or `ThermostatResponse`), or `UnknownMessage` for message types this library doesn't yet decode. Decoding is **lenient**: an unrecognized opcode or enum value is preserved (as `category=None` with a raw `opcode1`, or as a raw `int`) rather than raising — structural errors (truncated frames) still raise `cta2045.codec.CodecError`.
104
+
105
+ ```python
106
+ from cta2045 import app
107
+
108
+ for m in app.decode_all(raw_bytes):
109
+ if isinstance(m, app.IntermediateDR) and isinstance(m.body, app.CommodityReadReply):
110
+ for r in m.body.reports:
111
+ print(r.code, r.instantaneous, r.cumulative)
112
+ ```
113
+
114
+ ### Implementing a UCM binding
115
+
116
+ `cta2045.ucm.Ucm` is an abstract interface that turns the codec into a UCM client. Subclass it, implement the single transport primitive `transmit()`, and you get the full DR command set plus inbound decoding for free:
117
+
118
+ ```python
119
+ from cta2045.ucm import Ucm
120
+ from cta2045.enums import AdvancedLoadUpUnits
121
+
122
+ class MyUcm(Ucm):
123
+ def transmit(self, frame: bytes) -> None:
124
+ ... # send `frame` to the SGD over your serial link
125
+
126
+ def on_message(self, message) -> None:
127
+ ... # handle one decoded inbound message
128
+
129
+ ucm = MyUcm()
130
+ ucm.shed(10) # build + transmit a shed command
131
+ ucm.advanced_load_up(60, 5, AdvancedLoadUpUnits.Wh_100)
132
+ ucm.receive(inbound_bytes) # decode + dispatch to on_message()
133
+ ```
134
+
135
+ Vendor-proprietary UCM bindings (which add a specific UCM's network API) live in separate, non-open packages.
136
+
137
+ ## Package layout
138
+
139
+ - `cta2045.enums` — on-the-wire enumerations (message/DR-command types, device types, operational states, commodity codes, capabilities, …).
140
+ - `cta2045.app` — application-layer messages: Basic DR, Intermediate DR (commodity/energy reads, GetInformation, Advanced Load Up), with encoders and decoders.
141
+ - `cta2045.codec` — ASCII-hex ↔ bytes, frame header parse/build, and field encodings (e.g. the event-duration byte).
142
+ - `cta2045.link` — reserved for a future RS-485 link-layer implementation (enabling a Pi/RS-485 "own-UCM").
143
+ - `cta2045.ucm` — abstract UCM interface; vendor-proprietary bindings live in separate packages.
144
+
145
+ ## Development
146
+
147
+ ```
148
+ pip install -e ".[dev]"
149
+ pytest
150
+ ruff check . && ruff format --check .
151
+ ```
152
+
153
+ These three checks are the quality gate; all must pass before a change is merged. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to file issues, propose changes, and the project's spec-conformance posture.
154
+
155
+ ## Scope & boundaries
156
+
157
+ This is a pure codec for the CTA-2045 message/application layer — no transport, no I/O. The CTA-2045 link layer (RS-485 or SPI serial framing, ACK/NAK, CRC) is a separate concern; the `cta2045.link` namespace is reserved for it.
158
+
159
+ ## References
160
+
161
+ - **ANSI/CTA-2045-B** — *Modular Communications Interface for Energy Management*, the standard this library implements: [overview](https://www.cta.tech/standards/ansicta-2045-b/) · [purchase / download](https://shop.cta.tech/products/modular-communications-interface-for-energy-management). The Intermediate DR application messages come from the companion ANSI/CTA-2045.3.
162
+ - **EcoPort** — the consumer-facing brand for CTA-2045-certified products; certification is run by the [OpenADR Alliance](https://www.openadr.org/ecoport).
163
+ - **Certification & testing** — [OpenADR and CTA-2045 / EcoPort testing (UL Solutions)](https://www.ul.com/resources/openadr-and-cta-2045ecoport-testing).
164
+
165
+ ## License
166
+
167
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,141 @@
1
+ # python-cta2045
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/cta2045.svg)](https://pypi.org/project/cta2045/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/cta2045.svg)](https://pypi.org/project/cta2045/)
5
+ [![CI](https://github.com/electrification-bus/python-cta2045/actions/workflows/ci.yml/badge.svg)](https://github.com/electrification-bus/python-cta2045/actions/workflows/ci.yml)
6
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ A CTA-2045 (ANSI/CTA-2045-B) protocol library in Python — encode/decode of the demand-response interface between a Smart Grid Device (SGD, e.g. a water heater) and a Universal Communications Module (UCM).
10
+
11
+ > **Status: pre-alpha.** The core protocol — Basic DR and Intermediate DR encode/decode, plus an abstract UCM interface — is implemented and tested. The public API may still change before 1.0, and the package is not yet published to PyPI.
12
+ >
13
+ > **Not certified.** This is an independent implementation of the published protocol. It has **not** been tested or certified under any conformance program (EcoPort or otherwise), and carries no warranty of interoperability with any certified device.
14
+
15
+ ## What is CTA-2045?
16
+
17
+ CTA-2045 (consumer-facing brand: **EcoPort**) standardizes a modular communications socket on an appliance (the SGD) into which a UCM plugs to provide grid demand-response. CTA-2045 specifies only the SGD↔UCM link; a UCM's upward (network) interface is vendor-specific. This library implements the CTA-2045 message layer itself, independent of any vendor or transport: bytes (or ASCII-hex) in, structured Python objects out, and back. It has no runtime dependencies and does no I/O.
18
+
19
+ See [References](#references) for the standard and the EcoPort program.
20
+
21
+ ## Install
22
+
23
+ Not yet on PyPI. For now, install from source:
24
+
25
+ ```
26
+ pip install -e ".[dev]"
27
+ ```
28
+
29
+ Requires Python 3.10+.
30
+
31
+ ## Quick start
32
+
33
+ Encode a command to send to a device, and decode messages received from one:
34
+
35
+ ```python
36
+ from cta2045 import app
37
+ from cta2045.codec import bytes_to_hex
38
+
39
+ # Encode a 10-minute shed command (UCM -> SGD). Durations are in MINUTES.
40
+ msg = app.shed(10)
41
+ msg.to_bytes() # b'\x08\x01\x00\x02\x01\x11'
42
+ bytes_to_hex(msg.to_bytes()) # '080100020111'
43
+
44
+ # Decode messages received from a device (one or more concatenated frames):
45
+ for m in app.decode_hex('080100021302'):
46
+ print(m.category, m.operational_state)
47
+ # BasicDRCategory.State_Query_Response OperationalState.Running_Curtailed
48
+ ```
49
+
50
+ ### Basic DR commands
51
+
52
+ ```python
53
+ app.shed(10) # curtail load for 10 minutes
54
+ app.end() # end the current shed event
55
+ app.load_up(30) # store energy for 30 minutes
56
+ app.critical_peak(60) # critical peak event
57
+ app.grid_emergency(15) # grid emergency event
58
+ app.power_level(50) # request 50% power
59
+ ```
60
+
61
+ Each returns a `BasicDR` object; call `.to_bytes()` for the wire frame. Durations are minutes; pass an explicit `cta2045.codec.Duration` for the `Unknown` / `Too Long` sentinels or for second-level control. Note the wire format quantizes durations (it can only carry `2·n²` seconds) — use `Duration.nearest()` to see the value that will actually be transmitted.
62
+
63
+ ### Advanced Load Up
64
+
65
+ ```python
66
+ from cta2045 import app
67
+ from cta2045.codec import bytes_to_hex
68
+ from cta2045.enums import AdvancedLoadUpUnits
69
+
70
+ # Store 500 Wh (5 × 100 Wh) of extra energy over 60 minutes (CTA-2045-B § 11.6)
71
+ cmd = app.advanced_load_up(60, 5, AdvancedLoadUpUnits.Wh_100)
72
+ bytes_to_hex(cmd.to_bytes()) # '080200070C00003C000502'
73
+ ```
74
+
75
+ ### Decoding device replies
76
+
77
+ `decode_all(bytes)` / `decode_hex(str)` return a list of message objects — `BasicDR`, `IntermediateDR` (whose `.body` is a `CommodityReadReply`, `GetInformationReply`, `AdvancedLoadUp`, or `ThermostatResponse`), or `UnknownMessage` for message types this library doesn't yet decode. Decoding is **lenient**: an unrecognized opcode or enum value is preserved (as `category=None` with a raw `opcode1`, or as a raw `int`) rather than raising — structural errors (truncated frames) still raise `cta2045.codec.CodecError`.
78
+
79
+ ```python
80
+ from cta2045 import app
81
+
82
+ for m in app.decode_all(raw_bytes):
83
+ if isinstance(m, app.IntermediateDR) and isinstance(m.body, app.CommodityReadReply):
84
+ for r in m.body.reports:
85
+ print(r.code, r.instantaneous, r.cumulative)
86
+ ```
87
+
88
+ ### Implementing a UCM binding
89
+
90
+ `cta2045.ucm.Ucm` is an abstract interface that turns the codec into a UCM client. Subclass it, implement the single transport primitive `transmit()`, and you get the full DR command set plus inbound decoding for free:
91
+
92
+ ```python
93
+ from cta2045.ucm import Ucm
94
+ from cta2045.enums import AdvancedLoadUpUnits
95
+
96
+ class MyUcm(Ucm):
97
+ def transmit(self, frame: bytes) -> None:
98
+ ... # send `frame` to the SGD over your serial link
99
+
100
+ def on_message(self, message) -> None:
101
+ ... # handle one decoded inbound message
102
+
103
+ ucm = MyUcm()
104
+ ucm.shed(10) # build + transmit a shed command
105
+ ucm.advanced_load_up(60, 5, AdvancedLoadUpUnits.Wh_100)
106
+ ucm.receive(inbound_bytes) # decode + dispatch to on_message()
107
+ ```
108
+
109
+ Vendor-proprietary UCM bindings (which add a specific UCM's network API) live in separate, non-open packages.
110
+
111
+ ## Package layout
112
+
113
+ - `cta2045.enums` — on-the-wire enumerations (message/DR-command types, device types, operational states, commodity codes, capabilities, …).
114
+ - `cta2045.app` — application-layer messages: Basic DR, Intermediate DR (commodity/energy reads, GetInformation, Advanced Load Up), with encoders and decoders.
115
+ - `cta2045.codec` — ASCII-hex ↔ bytes, frame header parse/build, and field encodings (e.g. the event-duration byte).
116
+ - `cta2045.link` — reserved for a future RS-485 link-layer implementation (enabling a Pi/RS-485 "own-UCM").
117
+ - `cta2045.ucm` — abstract UCM interface; vendor-proprietary bindings live in separate packages.
118
+
119
+ ## Development
120
+
121
+ ```
122
+ pip install -e ".[dev]"
123
+ pytest
124
+ ruff check . && ruff format --check .
125
+ ```
126
+
127
+ These three checks are the quality gate; all must pass before a change is merged. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to file issues, propose changes, and the project's spec-conformance posture.
128
+
129
+ ## Scope & boundaries
130
+
131
+ This is a pure codec for the CTA-2045 message/application layer — no transport, no I/O. The CTA-2045 link layer (RS-485 or SPI serial framing, ACK/NAK, CRC) is a separate concern; the `cta2045.link` namespace is reserved for it.
132
+
133
+ ## References
134
+
135
+ - **ANSI/CTA-2045-B** — *Modular Communications Interface for Energy Management*, the standard this library implements: [overview](https://www.cta.tech/standards/ansicta-2045-b/) · [purchase / download](https://shop.cta.tech/products/modular-communications-interface-for-energy-management). The Intermediate DR application messages come from the companion ANSI/CTA-2045.3.
136
+ - **EcoPort** — the consumer-facing brand for CTA-2045-certified products; certification is run by the [OpenADR Alliance](https://www.openadr.org/ecoport).
137
+ - **Certification & testing** — [OpenADR and CTA-2045 / EcoPort testing (UL Solutions)](https://www.ul.com/resources/openadr-and-cta-2045ecoport-testing).
138
+
139
+ ## License
140
+
141
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cta2045"
7
+ version = "0.1.0"
8
+ description = "CTA-2045 (ANSI/CTA-2045-B) protocol library — encode/decode of the SGD↔UCM demand-response interface"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "Clark Communications Corporation"}
14
+ ]
15
+ keywords = ["cta-2045", "cta2045", "ecoport", "demand-response", "smart-grid", "water-heater", "ucm", "sgd"]
16
+ classifiers = [
17
+ "Development Status :: 2 - Pre-Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Home Automation",
25
+ "Topic :: System :: Hardware",
26
+ ]
27
+ # Pure standard library — no runtime dependencies (this is a protocol codec, not a transport).
28
+ dependencies = []
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://ebus.energy"
35
+ Repository = "https://github.com/electrification-bus/python-cta2045"
36
+ Issues = "https://github.com/electrification-bus/python-cta2045/issues"
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["src"]
40
+
41
+ [tool.ruff]
42
+ line-length = 120
43
+
44
+ [tool.ruff.lint]
45
+ ignore = ["E402"]
46
+
47
+ [tool.ruff.format]
48
+ quote-style = "double"
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
cta2045-0.1.0/setup.py ADDED
@@ -0,0 +1,32 @@
1
+ """Legacy setup.py shim for setuptools < 61 (no PEP 621 support).
2
+
3
+ Modern setuptools (>=61) reads all package metadata from pyproject.toml's
4
+ [project] table and ignores the args passed here. This shim exists only so that
5
+ older setuptools — notably the 59.5.0 pinned in Yocto kirkstone — can build a
6
+ wheel with correct name, version, and packages from the sdist. Without it, the
7
+ legacy build cannot read [project] and emits an UNKNOWN-0.0.0 wheel with no
8
+ modules (see the python-sdk / kirkstone history for the full saga).
9
+
10
+ ``find_packages`` is used so every subpackage (cta2045.app, cta2045.codec,
11
+ cta2045.link, cta2045.ucm) is included — a hand-listed ``packages=["cta2045"]``
12
+ would silently drop them. The version is read from the package so it cannot
13
+ drift from pyproject.toml; bump it in src/cta2045/__init__.py.
14
+ """
15
+
16
+ import re
17
+ from pathlib import Path
18
+
19
+ from setuptools import find_packages, setup
20
+
21
+ version = re.search(
22
+ r'^__version__ = "([^"]+)"',
23
+ Path("src/cta2045/__init__.py").read_text(encoding="utf-8"),
24
+ re.M,
25
+ ).group(1)
26
+
27
+ setup(
28
+ name="cta2045",
29
+ version=version,
30
+ package_dir={"": "src"},
31
+ packages=find_packages(where="src"),
32
+ )
@@ -0,0 +1,20 @@
1
+ """cta2045 — a CTA-2045 protocol library.
2
+
3
+ CTA-2045 (ANSI/CTA-2045-B) is the modular communications interface between a
4
+ Smart Grid Device (SGD — e.g. a water heater) and a Universal Communications
5
+ Module (UCM). This library encodes and decodes CTA-2045 messages.
6
+
7
+ Package layout:
8
+
9
+ - ``cta2045.enums`` — on-the-wire enumerations (the canonical vocabulary).
10
+ - ``cta2045.app`` — application-layer messages (Basic DR, Intermediate DR,
11
+ commodity/energy, GetInformation).
12
+ - ``cta2045.codec`` — ASCII-hex ↔ bytes and message framing.
13
+ - ``cta2045.link`` — reserved for a future CTA-2045 RS485 link layer.
14
+ - ``cta2045.ucm`` — abstract UCM interface; vendor-proprietary bindings live
15
+ in separate (non-open) packages.
16
+
17
+ Status: scaffold. The implementation is tracked in the project's issue tracker.
18
+ """
19
+
20
+ __version__ = "0.1.0"