sensecap-lorawan 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.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: sensecap-lorawan
3
+ Version: 0.1.0
4
+ Summary: SenseCAP LoRaWAN device models and command-line reader
5
+ Author: Paulus Schoutsen
6
+ Author-email: Paulus Schoutsen <balloob@gmail.com>
7
+ License-Expression: Apache-2.0
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Framework :: AsyncIO
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Topic :: Home Automation
14
+ Classifier: Typing :: Typed
15
+ Requires-Dist: lorawan-connection>=0.14.0,<0.15
16
+ Requires-Dist: lorawan-connection[chirpstack]>=0.14.0,<0.15 ; extra == 'chirpstack'
17
+ Requires-Dist: lorawan-connection[tts]>=0.14.0,<0.15 ; extra == 'tts'
18
+ Requires-Python: >=3.12
19
+ Project-URL: Issues, https://github.com/balloob/sensecap-lorawan/issues
20
+ Project-URL: Repository, https://github.com/balloob/sensecap-lorawan
21
+ Provides-Extra: chirpstack
22
+ Provides-Extra: tts
23
+ Description-Content-Type: text/markdown
24
+
25
+ # SenseCAP LoRaWAN
26
+
27
+ Read SenseCAP sensors through ChirpStack or The Things Stack using
28
+ [`lorawan-connection`](https://github.com/home-assistant-libs/lorawan-connection).
29
+
30
+ | Model | Readings | Automatic discovery |
31
+ | --- | --- | --- |
32
+ | S2100 | Data logger input channels | ChirpStack, TTS |
33
+ | S2101 | Air temperature, humidity | ChirpStack, TTS |
34
+ | S2102 | Light intensity | ChirpStack, TTS |
35
+ | S2103 | Air temperature, humidity, CO₂ | ChirpStack, TTS |
36
+ | S2104 | Soil temperature, moisture | ChirpStack, TTS |
37
+ | S2105 | Soil temperature, moisture, conductivity | ChirpStack, TTS |
38
+ | S2106 | Temperature, pH | ChirpStack |
39
+ | S2107 | Three temperature probes, including buffered readings | ChirpStack |
40
+ | S2120 | Temperature, humidity, light, UV, wind, pressure, rainfall | ChirpStack, TTS |
41
+ | A1101 | Vision AI measurements | ChirpStack |
42
+ | A1102 | Vision detections and digital meter readings | Explicit Python use |
43
+ | T1000 A/B | Position, temperature, light, motion, Wi-Fi/BLE scans | ChirpStack, TTS |
44
+ | T1000-E | Position, temperature, light, acceleration, Wi-Fi/BLE scans | Explicit Python use |
45
+ | T2000 A/B/C | Position, acceleration, motion, Wi-Fi/BLE scans | ChirpStack, TTS |
46
+ | WM1110 | GNSS fragments, Wi-Fi/BLE scans, environmental and custom readings | Explicit Python use |
47
+ | Gen1 sensors | Air temperature/humidity, CO₂, light, pressure, soil temperature/moisture | TTS |
48
+
49
+ Coverage follows every device family in Seeed's
50
+ [SenseCAP-Decoder](https://github.com/Seeed-Solution/SenseCAP-Decoder/tree/d0a234205fb9574dd03abd49561b60c243c0d1e3).
51
+ The A1102, T1000-E, and WM1110 classes have no automatic discovery identifiers:
52
+ they are absent from the SenseCAP catalogs checked for this release. Use their
53
+ `decode()` methods directly, or subclass them with your deployment's model identity.
54
+
55
+ Tracker support requires SenseCAP LoRaWAN firmware. Meshtastic firmware and
56
+ T1000 Mapper payloads are outside this decoder set. Wi-Fi/BLE scans and GNSS
57
+ fragments remain scan data; the library does not resolve them through a location
58
+ service or assemble fragmented GNSS messages. This library decodes uplinks and
59
+ does not implement device configuration commands.
60
+
61
+ Raw payloads are decoded locally; a server-side payload formatter is not required.
62
+
63
+ ## Command-line reader
64
+
65
+ The `sensecap-lorawan` command lists devices or prints readings as they arrive.
66
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) to run it with
67
+ `uvx`, or use `pip install "sensecap-lorawan[chirpstack]"` for ChirpStack and
68
+ `pip install "sensecap-lorawan[tts]"` for The Things Stack.
69
+
70
+ ### ChirpStack
71
+
72
+ Use a ChirpStack v4 gRPC endpoint, an API key, and the tenant UUID. The endpoint must
73
+ expose the gRPC API; forwarding only the web interface is not sufficient.
74
+
75
+ ```sh
76
+ export CHIRPSTACK_API_KEY="your-api-key"
77
+ uvx --from "sensecap-lorawan[chirpstack]" sensecap-lorawan \
78
+ --backend chirpstack \
79
+ --server https://chirpstack.example.com:8080 \
80
+ --tenant YOUR_TENANT_UUID \
81
+ --application YOUR_APPLICATION_UUID
82
+ ```
83
+
84
+ Repeat `--application` to select more applications. If omitted, all accessible
85
+ applications in the selected tenant are used. A global key can omit `--tenant`
86
+ to discover its accessible tenants.
87
+
88
+ ### The Things Stack
89
+
90
+ Use the Application Server gRPC endpoint and an application API key with device
91
+ read and application traffic read rights. Specify each application explicitly.
92
+
93
+ ```sh
94
+ export TTS_API_KEY="your-api-key"
95
+ uvx --from "sensecap-lorawan[tts]" sensecap-lorawan \
96
+ --backend tts \
97
+ --server https://eu1.cloud.thethings.network:8884 \
98
+ --identity-server https://eu1.cloud.thethings.network:8884 \
99
+ --application YOUR_APPLICATION_ID
100
+ ```
101
+
102
+ Set `--identity-server` to your deployment's Identity Server if it differs from
103
+ the Application Server. Repeat `--application` for multiple applications.
104
+
105
+ Add `--list` to print the current devices and exit, or `--json` for newline-delimited
106
+ JSON output. Use `--api-key-file /path/to/key` instead of an environment variable
107
+ if preferred. Stop watching with Ctrl+C. Run `sensecap-lorawan --help` for all options.
108
+
109
+ With a pip installation, use `sensecap-lorawan` directly in place of the
110
+ `uvx --from ... sensecap-lorawan` prefix. `python -m sensecap_lorawan` also works.
111
+
112
+ ## Python
113
+
114
+ ```python
115
+ from lorawan_connection.backend.chirpstack import ChirpStackConnection
116
+ from sensecap_lorawan import SenseCapDeviceCollection
117
+
118
+
119
+ async def list_sensors(
120
+ endpoint: str, api_key: str, tenant: str, application: str
121
+ ) -> None:
122
+ connection = ChirpStackConnection(
123
+ endpoint,
124
+ api_key,
125
+ tenant_id=tenant,
126
+ application_ids=[application],
127
+ network_id=endpoint,
128
+ )
129
+ devices = SenseCapDeviceCollection(connection)
130
+ try:
131
+ await connection.async_connect()
132
+ await devices.async_setup()
133
+ for device in devices.devices.values():
134
+ print(device.descriptor.name, type(device).__name__)
135
+ finally:
136
+ devices.close()
137
+ await connection.close()
138
+ ```
139
+
140
+ The collection also accepts a `TTSConnection`. Subscribe with
141
+ `devices.subscribe_device_added(listener)` and `device.add_update_listener(listener)`
142
+ to receive live readings. Values remain `None` until the sensor reports them.
143
+
144
+ ### Reading measurements
145
+
146
+ Existing properties such as `S2101.temperature`, `S2101.humidity`, and
147
+ `S2102.illuminance` remain available. All models also expose
148
+ `measurements[channel][measurement_id]`, whose `Measurement` contains `value`,
149
+ `timestamp`, and protocol-specific `attributes`. Timestamps are timezone-aware
150
+ UTC values when transmitted by the device; otherwise they are `None`.
151
+
152
+ ```python
153
+ from sensecap_lorawan import S2107
154
+
155
+ payload = S2107.decode(bytes.fromhex("470000ff9c8000"))
156
+ for measurement in payload.measurements:
157
+ print(measurement.channel, measurement.value)
158
+ # 1 0.0
159
+ # 2 -1.0
160
+ # 3 None
161
+ ```
162
+
163
+ For a live device, use `device.measurement_value(4203, channel=2)` or
164
+ `device.temperature(channel=2)` on an S2107. The S2100 uses input channel numbers as measurement
165
+ IDs within channel 1, matching Seeed's decoder. Unknown seven-byte measurement
166
+ IDs are retained. See Seeed's [measurement ID list](https://wiki.seeedstudio.com/sensecraft-data-platform/api/appendix/list_of_measurement_ids/)
167
+ for their meanings and units.
168
+
169
+ `metadata` contains reported versions, intervals, model information, and errors.
170
+ `battery_percentage` reads the battery value from the vendor payload;
171
+ `battery_level` remains the standardized LoRaWAN status value. Tracker event
172
+ measurements contain numeric event IDs, positioning status is a numeric code,
173
+ and Wi-Fi/BLE scan values are lists of `{mac, rssi}` dictionaries.
174
+
175
+ Direct decoding raises `ValueError` for malformed or unknown frame layouts. Live
176
+ models discard those payloads atomically. Partial updates retain other readings;
177
+ older readings cannot replace newer values on the same channel and measurement ID.
178
+ Device timestamps take precedence over receipt time for buffered readings.
179
+ Fault/absent values become `None`; zero remains a valid reading. The seven-byte
180
+ protocol's two-byte trailer is not CRC-validated, matching Seeed's decoder.
181
+
182
+ ## Development
183
+
184
+ ```sh
185
+ uv sync
186
+ uv run pytest
187
+ uv run ruff check .
188
+ uv run mypy
189
+ uv build
190
+ ```
191
+
192
+ Tests include captured S2101/S2102 uplinks, reference outputs for every decoder
193
+ family, and malformed, partial, and out-of-order messages.
194
+ See [fixture provenance and regeneration](tests/fixtures/README.md).
195
+
196
+ ## Releases
197
+
198
+ The source version stays `0.0.0`. Publishing a GitHub release named `0.1.0`, for
199
+ example, builds that version and publishes it to PyPI through Trusted Publishing.
200
+ See [RELEASING.md](RELEASING.md) for the one-time publisher setup.
201
+
202
+ ## License
203
+
204
+ Apache-2.0. Extracted from the SenseCAP examples in
205
+ [`lorawan-connection`](https://github.com/home-assistant-libs/lorawan-connection).
@@ -0,0 +1,181 @@
1
+ # SenseCAP LoRaWAN
2
+
3
+ Read SenseCAP sensors through ChirpStack or The Things Stack using
4
+ [`lorawan-connection`](https://github.com/home-assistant-libs/lorawan-connection).
5
+
6
+ | Model | Readings | Automatic discovery |
7
+ | --- | --- | --- |
8
+ | S2100 | Data logger input channels | ChirpStack, TTS |
9
+ | S2101 | Air temperature, humidity | ChirpStack, TTS |
10
+ | S2102 | Light intensity | ChirpStack, TTS |
11
+ | S2103 | Air temperature, humidity, CO₂ | ChirpStack, TTS |
12
+ | S2104 | Soil temperature, moisture | ChirpStack, TTS |
13
+ | S2105 | Soil temperature, moisture, conductivity | ChirpStack, TTS |
14
+ | S2106 | Temperature, pH | ChirpStack |
15
+ | S2107 | Three temperature probes, including buffered readings | ChirpStack |
16
+ | S2120 | Temperature, humidity, light, UV, wind, pressure, rainfall | ChirpStack, TTS |
17
+ | A1101 | Vision AI measurements | ChirpStack |
18
+ | A1102 | Vision detections and digital meter readings | Explicit Python use |
19
+ | T1000 A/B | Position, temperature, light, motion, Wi-Fi/BLE scans | ChirpStack, TTS |
20
+ | T1000-E | Position, temperature, light, acceleration, Wi-Fi/BLE scans | Explicit Python use |
21
+ | T2000 A/B/C | Position, acceleration, motion, Wi-Fi/BLE scans | ChirpStack, TTS |
22
+ | WM1110 | GNSS fragments, Wi-Fi/BLE scans, environmental and custom readings | Explicit Python use |
23
+ | Gen1 sensors | Air temperature/humidity, CO₂, light, pressure, soil temperature/moisture | TTS |
24
+
25
+ Coverage follows every device family in Seeed's
26
+ [SenseCAP-Decoder](https://github.com/Seeed-Solution/SenseCAP-Decoder/tree/d0a234205fb9574dd03abd49561b60c243c0d1e3).
27
+ The A1102, T1000-E, and WM1110 classes have no automatic discovery identifiers:
28
+ they are absent from the SenseCAP catalogs checked for this release. Use their
29
+ `decode()` methods directly, or subclass them with your deployment's model identity.
30
+
31
+ Tracker support requires SenseCAP LoRaWAN firmware. Meshtastic firmware and
32
+ T1000 Mapper payloads are outside this decoder set. Wi-Fi/BLE scans and GNSS
33
+ fragments remain scan data; the library does not resolve them through a location
34
+ service or assemble fragmented GNSS messages. This library decodes uplinks and
35
+ does not implement device configuration commands.
36
+
37
+ Raw payloads are decoded locally; a server-side payload formatter is not required.
38
+
39
+ ## Command-line reader
40
+
41
+ The `sensecap-lorawan` command lists devices or prints readings as they arrive.
42
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) to run it with
43
+ `uvx`, or use `pip install "sensecap-lorawan[chirpstack]"` for ChirpStack and
44
+ `pip install "sensecap-lorawan[tts]"` for The Things Stack.
45
+
46
+ ### ChirpStack
47
+
48
+ Use a ChirpStack v4 gRPC endpoint, an API key, and the tenant UUID. The endpoint must
49
+ expose the gRPC API; forwarding only the web interface is not sufficient.
50
+
51
+ ```sh
52
+ export CHIRPSTACK_API_KEY="your-api-key"
53
+ uvx --from "sensecap-lorawan[chirpstack]" sensecap-lorawan \
54
+ --backend chirpstack \
55
+ --server https://chirpstack.example.com:8080 \
56
+ --tenant YOUR_TENANT_UUID \
57
+ --application YOUR_APPLICATION_UUID
58
+ ```
59
+
60
+ Repeat `--application` to select more applications. If omitted, all accessible
61
+ applications in the selected tenant are used. A global key can omit `--tenant`
62
+ to discover its accessible tenants.
63
+
64
+ ### The Things Stack
65
+
66
+ Use the Application Server gRPC endpoint and an application API key with device
67
+ read and application traffic read rights. Specify each application explicitly.
68
+
69
+ ```sh
70
+ export TTS_API_KEY="your-api-key"
71
+ uvx --from "sensecap-lorawan[tts]" sensecap-lorawan \
72
+ --backend tts \
73
+ --server https://eu1.cloud.thethings.network:8884 \
74
+ --identity-server https://eu1.cloud.thethings.network:8884 \
75
+ --application YOUR_APPLICATION_ID
76
+ ```
77
+
78
+ Set `--identity-server` to your deployment's Identity Server if it differs from
79
+ the Application Server. Repeat `--application` for multiple applications.
80
+
81
+ Add `--list` to print the current devices and exit, or `--json` for newline-delimited
82
+ JSON output. Use `--api-key-file /path/to/key` instead of an environment variable
83
+ if preferred. Stop watching with Ctrl+C. Run `sensecap-lorawan --help` for all options.
84
+
85
+ With a pip installation, use `sensecap-lorawan` directly in place of the
86
+ `uvx --from ... sensecap-lorawan` prefix. `python -m sensecap_lorawan` also works.
87
+
88
+ ## Python
89
+
90
+ ```python
91
+ from lorawan_connection.backend.chirpstack import ChirpStackConnection
92
+ from sensecap_lorawan import SenseCapDeviceCollection
93
+
94
+
95
+ async def list_sensors(
96
+ endpoint: str, api_key: str, tenant: str, application: str
97
+ ) -> None:
98
+ connection = ChirpStackConnection(
99
+ endpoint,
100
+ api_key,
101
+ tenant_id=tenant,
102
+ application_ids=[application],
103
+ network_id=endpoint,
104
+ )
105
+ devices = SenseCapDeviceCollection(connection)
106
+ try:
107
+ await connection.async_connect()
108
+ await devices.async_setup()
109
+ for device in devices.devices.values():
110
+ print(device.descriptor.name, type(device).__name__)
111
+ finally:
112
+ devices.close()
113
+ await connection.close()
114
+ ```
115
+
116
+ The collection also accepts a `TTSConnection`. Subscribe with
117
+ `devices.subscribe_device_added(listener)` and `device.add_update_listener(listener)`
118
+ to receive live readings. Values remain `None` until the sensor reports them.
119
+
120
+ ### Reading measurements
121
+
122
+ Existing properties such as `S2101.temperature`, `S2101.humidity`, and
123
+ `S2102.illuminance` remain available. All models also expose
124
+ `measurements[channel][measurement_id]`, whose `Measurement` contains `value`,
125
+ `timestamp`, and protocol-specific `attributes`. Timestamps are timezone-aware
126
+ UTC values when transmitted by the device; otherwise they are `None`.
127
+
128
+ ```python
129
+ from sensecap_lorawan import S2107
130
+
131
+ payload = S2107.decode(bytes.fromhex("470000ff9c8000"))
132
+ for measurement in payload.measurements:
133
+ print(measurement.channel, measurement.value)
134
+ # 1 0.0
135
+ # 2 -1.0
136
+ # 3 None
137
+ ```
138
+
139
+ For a live device, use `device.measurement_value(4203, channel=2)` or
140
+ `device.temperature(channel=2)` on an S2107. The S2100 uses input channel numbers as measurement
141
+ IDs within channel 1, matching Seeed's decoder. Unknown seven-byte measurement
142
+ IDs are retained. See Seeed's [measurement ID list](https://wiki.seeedstudio.com/sensecraft-data-platform/api/appendix/list_of_measurement_ids/)
143
+ for their meanings and units.
144
+
145
+ `metadata` contains reported versions, intervals, model information, and errors.
146
+ `battery_percentage` reads the battery value from the vendor payload;
147
+ `battery_level` remains the standardized LoRaWAN status value. Tracker event
148
+ measurements contain numeric event IDs, positioning status is a numeric code,
149
+ and Wi-Fi/BLE scan values are lists of `{mac, rssi}` dictionaries.
150
+
151
+ Direct decoding raises `ValueError` for malformed or unknown frame layouts. Live
152
+ models discard those payloads atomically. Partial updates retain other readings;
153
+ older readings cannot replace newer values on the same channel and measurement ID.
154
+ Device timestamps take precedence over receipt time for buffered readings.
155
+ Fault/absent values become `None`; zero remains a valid reading. The seven-byte
156
+ protocol's two-byte trailer is not CRC-validated, matching Seeed's decoder.
157
+
158
+ ## Development
159
+
160
+ ```sh
161
+ uv sync
162
+ uv run pytest
163
+ uv run ruff check .
164
+ uv run mypy
165
+ uv build
166
+ ```
167
+
168
+ Tests include captured S2101/S2102 uplinks, reference outputs for every decoder
169
+ family, and malformed, partial, and out-of-order messages.
170
+ See [fixture provenance and regeneration](tests/fixtures/README.md).
171
+
172
+ ## Releases
173
+
174
+ The source version stays `0.0.0`. Publishing a GitHub release named `0.1.0`, for
175
+ example, builds that version and publishes it to PyPI through Trusted Publishing.
176
+ See [RELEASING.md](RELEASING.md) for the one-time publisher setup.
177
+
178
+ ## License
179
+
180
+ Apache-2.0. Extracted from the SenseCAP examples in
181
+ [`lorawan-connection`](https://github.com/home-assistant-libs/lorawan-connection).
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "sensecap-lorawan"
3
+ version = "0.1.0"
4
+ description = "SenseCAP LoRaWAN device models and command-line reader"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ authors = [{ name = "Paulus Schoutsen", email = "balloob@gmail.com" }]
8
+ requires-python = ">=3.12"
9
+ dependencies = ["lorawan-connection>=0.14.0,<0.15"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Framework :: AsyncIO",
13
+ "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Programming Language :: Python :: 3.14",
16
+ "Topic :: Home Automation",
17
+ "Typing :: Typed",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ chirpstack = ["lorawan-connection[chirpstack]>=0.14.0,<0.15"]
22
+ tts = ["lorawan-connection[tts]>=0.14.0,<0.15"]
23
+
24
+ [project.scripts]
25
+ sensecap-lorawan = "sensecap_lorawan.__main__:main"
26
+
27
+ [project.urls]
28
+ Repository = "https://github.com/balloob/sensecap-lorawan"
29
+ Issues = "https://github.com/balloob/sensecap-lorawan/issues"
30
+
31
+ [build-system]
32
+ requires = ["uv_build>=0.8.4,<0.9.0"]
33
+ build-backend = "uv_build"
34
+
35
+ [dependency-groups]
36
+ dev = ["pytest>=9", "pytest-asyncio>=1", "pytest-cov>=6", "ruff>=0.11", "mypy>=1.19"]
37
+
38
+ [tool.pytest.ini_options]
39
+ asyncio_mode = "auto"
40
+ testpaths = ["tests"]
41
+
42
+ [tool.ruff]
43
+ target-version = "py312"
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "W", "I", "UP", "B"]
47
+
48
+ [tool.mypy]
49
+ python_version = "3.12"
50
+ strict = true
51
+ files = ["src"]
@@ -0,0 +1,108 @@
1
+ """SenseCAP device models for ChirpStack and The Things Stack."""
2
+
3
+ from lorawan_connection import DeviceCollection
4
+
5
+ from .device import SenseCapDevice
6
+ from .models import (
7
+ A1101,
8
+ A1102,
9
+ S2100,
10
+ S2101,
11
+ S2102,
12
+ S2103,
13
+ S2104,
14
+ S2105,
15
+ S2106,
16
+ S2107,
17
+ S2120,
18
+ T1000,
19
+ T1000A,
20
+ T1000B,
21
+ T1000E,
22
+ T2000,
23
+ VENDOR_ID,
24
+ WM1110,
25
+ Gen1AirTemperatureHumidity,
26
+ Gen1CO2,
27
+ Gen1Light,
28
+ Gen1Pressure,
29
+ Gen1SoilTemperatureMoisture,
30
+ )
31
+ from .payload import DecodedPayload, Measurement
32
+ from .records import decode_records
33
+
34
+
35
+ def decode_s2101(data: bytes) -> dict[str, float | None]:
36
+ """Decode S2101 channel-one temperature and humidity records."""
37
+ result: dict[str, float | None] = {}
38
+ for item in decode_records(data).measurements:
39
+ key = {4097: "temperature", 4098: "humidity"}.get(item.id)
40
+ if item.channel != 1 or key is None:
41
+ continue
42
+ value = item.value
43
+ if value is not None:
44
+ assert isinstance(value, (float, int))
45
+ if key == "temperature" and not -40 <= value <= 85:
46
+ raise ValueError("Temperature outside S2101 measurement range")
47
+ if key == "humidity" and not 0 <= value <= 100:
48
+ raise ValueError("Humidity outside valid range")
49
+ result[key] = value
50
+ return result
51
+
52
+
53
+ class SenseCapDeviceCollection(DeviceCollection[SenseCapDevice]):
54
+ """Discover SenseCAP devices using official catalog identities."""
55
+
56
+ DEVICES = (
57
+ S2100,
58
+ S2101,
59
+ S2102,
60
+ S2103,
61
+ S2104,
62
+ S2105,
63
+ S2106,
64
+ S2107,
65
+ S2120,
66
+ A1101,
67
+ T1000,
68
+ T1000A,
69
+ T1000B,
70
+ T2000,
71
+ Gen1AirTemperatureHumidity,
72
+ Gen1CO2,
73
+ Gen1Light,
74
+ Gen1Pressure,
75
+ Gen1SoilTemperatureMoisture,
76
+ )
77
+
78
+
79
+ __all__ = [
80
+ "S2100",
81
+ "S2101",
82
+ "S2102",
83
+ "S2103",
84
+ "S2104",
85
+ "S2105",
86
+ "S2106",
87
+ "S2107",
88
+ "S2120",
89
+ "A1101",
90
+ "A1102",
91
+ "T1000",
92
+ "T1000A",
93
+ "T1000B",
94
+ "T1000E",
95
+ "T2000",
96
+ "WM1110",
97
+ "Gen1AirTemperatureHumidity",
98
+ "Gen1CO2",
99
+ "Gen1Light",
100
+ "Gen1Pressure",
101
+ "Gen1SoilTemperatureMoisture",
102
+ "VENDOR_ID",
103
+ "DecodedPayload",
104
+ "Measurement",
105
+ "SenseCapDevice",
106
+ "SenseCapDeviceCollection",
107
+ "decode_s2101",
108
+ ]
@@ -0,0 +1,14 @@
1
+ """Discover SenseCAP devices and print their readings."""
2
+
3
+ from lorawan_connection.cli_helper import run
4
+
5
+ from . import SenseCapDeviceCollection
6
+
7
+
8
+ def main() -> None:
9
+ """Run the SenseCAP command-line interface."""
10
+ run(SenseCapDeviceCollection.DEVICES)
11
+
12
+
13
+ if __name__ == "__main__":
14
+ main()