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 +21 -0
- cta2045-0.1.0/PKG-INFO +167 -0
- cta2045-0.1.0/README.md +141 -0
- cta2045-0.1.0/pyproject.toml +51 -0
- cta2045-0.1.0/setup.cfg +4 -0
- cta2045-0.1.0/setup.py +32 -0
- cta2045-0.1.0/src/cta2045/__init__.py +20 -0
- cta2045-0.1.0/src/cta2045/app/__init__.py +654 -0
- cta2045-0.1.0/src/cta2045/codec/__init__.py +218 -0
- cta2045-0.1.0/src/cta2045/enums.py +324 -0
- cta2045-0.1.0/src/cta2045/link/__init__.py +9 -0
- cta2045-0.1.0/src/cta2045/ucm/__init__.py +109 -0
- cta2045-0.1.0/src/cta2045.egg-info/PKG-INFO +167 -0
- cta2045-0.1.0/src/cta2045.egg-info/SOURCES.txt +20 -0
- cta2045-0.1.0/src/cta2045.egg-info/dependency_links.txt +1 -0
- cta2045-0.1.0/src/cta2045.egg-info/requires.txt +3 -0
- cta2045-0.1.0/src/cta2045.egg-info/top_level.txt +1 -0
- cta2045-0.1.0/tests/test_app.py +313 -0
- cta2045-0.1.0/tests/test_codec.py +131 -0
- cta2045-0.1.0/tests/test_enums.py +60 -0
- cta2045-0.1.0/tests/test_import.py +15 -0
- cta2045-0.1.0/tests/test_ucm.py +66 -0
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
|
+
[](https://pypi.org/project/cta2045/)
|
|
30
|
+
[](https://pypi.org/project/cta2045/)
|
|
31
|
+
[](https://github.com/electrification-bus/python-cta2045/actions/workflows/ci.yml)
|
|
32
|
+
[](https://github.com/astral-sh/ruff)
|
|
33
|
+
[](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).
|
cta2045-0.1.0/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# python-cta2045
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/cta2045/)
|
|
4
|
+
[](https://pypi.org/project/cta2045/)
|
|
5
|
+
[](https://github.com/electrification-bus/python-cta2045/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/astral-sh/ruff)
|
|
7
|
+
[](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"]
|
cta2045-0.1.0/setup.cfg
ADDED
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"
|