ents 2.3.2__py3-none-any.whl

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.
ents/simulator/node.py ADDED
@@ -0,0 +1,161 @@
1
+ from math import sin
2
+
3
+ import requests
4
+ import numpy as np
5
+
6
+ from ..proto.encode import (
7
+ encode_power_measurement,
8
+ encode_teros12_measurement,
9
+ encode_teros21_measurement,
10
+ encode_bme280_measurement,
11
+ )
12
+
13
+
14
+ class NodeSimulator:
15
+ """Simulation class to simulate measurements for different sensors"""
16
+
17
+ # temporary storage for measurements to be uploaded
18
+ measurement_buffer: list[bytes] = []
19
+ # all measurements uploaded
20
+ measurements: list[bytes] = []
21
+ # all responses
22
+ responses: list[str] = []
23
+
24
+ # metrics for uploads
25
+ metrics: dict[str, int] = {
26
+ "total_requests": 0,
27
+ "failed_requests": 0,
28
+ "successful_requests": 0,
29
+ }
30
+
31
+ latency: list[float] = []
32
+
33
+ def __init__(self, cell: int, logger: int, sensors: list[str], fn=sin):
34
+ self.cell = cell
35
+ self.logger = logger
36
+ self.sensors = sensors
37
+ self.fn = fn
38
+
39
+ def __str__(self):
40
+ """String representation of the simulation class
41
+
42
+ Shows the current upload metrics
43
+ """
44
+ avg = np.array(self.latency).mean()
45
+
46
+ last = 0
47
+ if len(self.latency) > 0:
48
+ last = self.latency[-1]
49
+
50
+ return "total: {}, failed: {}, avg (ms): {}, last (ms): {}".format(
51
+ self.metrics["total_requests"],
52
+ self.metrics["failed_requests"],
53
+ avg * 100,
54
+ last * 100,
55
+ )
56
+
57
+ def send_next(self, url: str) -> bool:
58
+ """Sends measurements to a dirtviz instance
59
+
60
+ Args:
61
+ url: URL of the dirtviz instance
62
+
63
+ Returns:
64
+ True if there are measurements to send, False otherwise
65
+ """
66
+
67
+ # get next measurement
68
+ try:
69
+ meas = self.measurements.pop()
70
+ except IndexError as _:
71
+ return False
72
+
73
+ headers = {"Content-Type": "application/octet-stream"}
74
+ result = requests.post(url, data=meas, headers=headers)
75
+
76
+ # store result
77
+ self.responses.append(result.text)
78
+ self.metrics["total_requests"] += 1
79
+ if result.status_code == 200:
80
+ self.metrics["successful_requests"] += 1
81
+ else:
82
+ self.metrics["failed_requests"] += 1
83
+ self.latency.append(result.elapsed.total_seconds())
84
+
85
+ return True
86
+
87
+ def measure(self, ts: int):
88
+ """Simulate measurements
89
+
90
+ Args:
91
+ ts: Timestamp of the measurement
92
+
93
+ Returns:
94
+ None
95
+ """
96
+
97
+ if "power" in self.sensors:
98
+
99
+ voltage = self.fn(ts) * 2
100
+ current = self.fn(ts) * 0.5
101
+
102
+ meas = encode_power_measurement(
103
+ ts=ts,
104
+ cell_id=self.cell,
105
+ logger_id=self.logger,
106
+ voltage=voltage,
107
+ current=current,
108
+ )
109
+ self.measurements.append(meas)
110
+ self.measurement_buffer.append(meas)
111
+
112
+ if "teros12" in self.sensors:
113
+
114
+ vwc_raw = self.fn(ts) * 300 + 2500
115
+ vwc_adj = self.fn(ts) * 0.05 + 0.2
116
+ temp = self.fn(ts) * 5 + 25
117
+ ec = self.fn(ts) * 4 + 15
118
+
119
+ meas = encode_teros12_measurement(
120
+ ts=ts,
121
+ cell_id=self.cell,
122
+ logger_id=self.logger,
123
+ vwc_raw=vwc_raw,
124
+ vwc_adj=vwc_adj,
125
+ temp=temp,
126
+ ec=int(ec),
127
+ )
128
+ self.measurements.append(meas)
129
+ self.measurement_buffer.append(meas)
130
+
131
+ if "teros21" in self.sensors:
132
+
133
+ matric_pot = self.fn(ts) * 200 + 1000
134
+ temp = self.fn(ts) * 5 + 25
135
+
136
+ meas = encode_teros21_measurement(
137
+ ts=ts,
138
+ cell_id=self.cell,
139
+ logger_id=self.logger,
140
+ matric_pot=matric_pot,
141
+ temp=temp,
142
+ )
143
+ self.measurements.append(meas)
144
+ self.measurement_buffer.append(meas)
145
+
146
+ if "bme280" in self.sensors:
147
+
148
+ temp = self.fn(ts) * 50 + 250
149
+ humidity = self.fn(ts) * 200 + 2000
150
+ pressure = self.fn(ts) * 2000 + 43000
151
+
152
+ meas = encode_bme280_measurement(
153
+ ts=ts,
154
+ cell_id=self.cell,
155
+ logger_id=self.logger,
156
+ temperature=int(temp),
157
+ humidity=int(humidity),
158
+ pressure=int(pressure),
159
+ )
160
+ self.measurements.append(meas)
161
+ self.measurement_buffer.append(meas)
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: ents
3
+ Version: 2.3.2
4
+ Summary: Python package for Environmental NeTworked Sensor (ENTS)
5
+ Project-URL: Homepage, https://github.com/jlab-sensing/soil-power-sensor-firmware
6
+ Project-URL: Issues, https://github.com/jlab-sensing/soil-power-sensor-firmware/issues
7
+ Author-email: John Madden <jmadden173@pm.me>
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.11
13
+ Requires-Dist: matplotlib
14
+ Requires-Dist: pandas
15
+ Requires-Dist: protobuf>5.0.0
16
+ Requires-Dist: pyserial
17
+ Requires-Dist: requests
18
+ Requires-Dist: scikit-learn
19
+ Requires-Dist: tqdm
20
+ Provides-Extra: dev
21
+ Requires-Dist: black; extra == 'dev'
22
+ Requires-Dist: ruff; extra == 'dev'
23
+ Provides-Extra: gui
24
+ Requires-Dist: pyqt5; extra == 'gui'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Soil Power Sensor Python Protobuf Bindings
28
+
29
+ The soil power sensor protobuf protocol is implemented as a Python package that allows for `Measurement` messages to be decoded into a dictionary and `Response` messages to be encoded. The generated files from protobuf are also accessible for more complex use cases.
30
+
31
+
32
+ ## Installation
33
+
34
+ Use the following to install the `ents` package with gui via `pip`:
35
+
36
+ ```bash
37
+ pip install ents[gui]
38
+ ```
39
+
40
+ You can also install the package from source with the following:
41
+
42
+ ```bash
43
+ # install package
44
+ pip install .[gui]
45
+ ```
46
+
47
+ If you are planning to develop the package we recommend you install the package
48
+ in editable mode with development dependencies. This allows you to make changes
49
+ to the source code and have them reflected in the package without needing to
50
+ reinstall it.
51
+
52
+ ```bash
53
+ # install development dependencies
54
+ pip install -e .[gui,dev]
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ The following example code demonstrates decoding the measurement message and encoding a response.
60
+
61
+ ```python
62
+ from ents import encode, decode
63
+
64
+ # get data encoded by the soil power sensor
65
+ data = ...
66
+
67
+ meas_dict = decode(data)
68
+
69
+ # process data
70
+ ...
71
+
72
+ # send response
73
+ resp_str = encode(success=True)
74
+ ```
75
+
76
+ The formatting of the dictionary depends on the type of measurement sent. The key `type` is included on all measurement types and can be used to determine the type of message. See the source `*.proto` files to get the full list of types to get the full list of types and keys. A list is provided in [Message Types](#message-types). The Python protobuf API uses camel case when naming keys. The key `ts` is in ISO 8601 format as a string.
77
+
78
+ ## Message Types
79
+
80
+ Type `power`
81
+ ```python
82
+ meas_dict = {
83
+ "type": "power",
84
+ "loggerId": ...,
85
+ "cellId": ...,
86
+ "ts": ...,
87
+ "data": {
88
+ "voltage": ...,
89
+ "current": ...
90
+ },
91
+ "data_type": {
92
+ "voltage": float,
93
+ "voltage": float
94
+ }
95
+ }
96
+ ```
97
+
98
+ Type `teros12`
99
+ ```python
100
+ meas_dict = {
101
+ "type": "teros12",
102
+ "loggerId": ...,
103
+ "cellId": ...,
104
+ "ts": ...,
105
+ "data": {
106
+ "vwcRaw": ...,
107
+ "vwcAdj": ...,
108
+ "temp": ...,
109
+ "ec": ...
110
+ },
111
+ "data_type": {
112
+ "vwcRaw": float,
113
+ "vwcAdj": float,
114
+ "temp": float,
115
+ "ec": int
116
+ }
117
+ }
118
+ ```
119
+
120
+ Type `bme280` with `raw=True` (default)
121
+ ```python
122
+ meas_dict = {
123
+ "type": "bme280",
124
+ "loggerId": ...,
125
+ "cellId": ...,
126
+ "ts": ...,
127
+ "data": {
128
+ "pressure": ...,
129
+ "temperature": ...,
130
+ "humidity": ...,
131
+ },
132
+ "data_type": {
133
+ "pressure": int,
134
+ "temperature": int,
135
+ "humidity": int,
136
+ }
137
+ }
138
+ ```
139
+
140
+ Type `bme280` with `raw=False`
141
+ ```python
142
+ meas_dict = {
143
+ "type": "bme280",
144
+ "loggerId": ...,
145
+ "cellId": ...,
146
+ "ts": ...,
147
+ "data": {
148
+ "pressure": ...,
149
+ "temperature": ...,
150
+ "humidity": ...,
151
+ },
152
+ "data_type": {
153
+ "pressure": float,
154
+ "temperature": float,
155
+ "humidity": float,
156
+ }
157
+ }
158
+ ```
159
+
160
+
161
+ ## Simulator
162
+
163
+ Simulate WiFi sensor uploads without requiring ENTS hardware.
164
+
165
+ ### Examples
166
+
167
+ The examples below can be tested standalone (without ents-backend), by running the http server in `tools/http_server.py` to see the request format.
168
+
169
+ #### Upload a days worth of power measurements on a 60 second interval
170
+
171
+ ```shell
172
+ ents sim --url http://localhost:3000/api/sensor/ --mode batch --sensor power --cell 200 --logger 200 --start 2025-05-01 --end 2025-05-02 --freq 60
173
+ ```
174
+
175
+ ```
176
+ ...
177
+ total: 1437, failed: 0, avg (ms): 0.10716012526096033, last (ms): 0.0896
178
+ total: 1438, failed: 0, avg (ms): 0.10714290681502087, last (ms): 0.0824
179
+ total: 1439, failed: 0, avg (ms): 0.10712599027102154, last (ms): 0.0828
180
+ total: 1440, failed: 0, avg (ms): 0.10710909722222223, last (ms): 0.0828
181
+ total: 1441, failed: 0, avg (ms): 0.10709035392088828, last (ms): 0.08009999999999999
182
+ Done!
183
+ ```
184
+
185
+ #### Upload measurements every 10 seconds
186
+
187
+ ```shell
188
+ ents sim --url http://localhost:3000/api/sensor/ --mode stream --sensor power --cell 200 --logger 200 --freq 10
189
+ ```
190
+
191
+ ```
192
+ Use CTRL+C to stop the simulation
193
+ total: 1, failed: 1, avg (ms): 23.386100000000003, last (ms): 23.386100000000003
194
+ total: 2, failed: 2, avg (ms): 13.668950000000002, last (ms): 3.9517999999999995
195
+ total: 3, failed: 3, avg (ms): 10.795566666666668, last (ms): 5.0488
196
+ total: 4, failed: 4, avg (ms): 8.97235, last (ms): 3.5027000000000004
197
+ ```
198
+
199
+ ## Testing
200
+
201
+ To run the package tests, create a virtual environment, install as an editable package (if you haven't done so already), and run `unittest`.
202
+
203
+ ```bash
204
+ cd python/
205
+ python -m unittest
206
+ ```
@@ -0,0 +1,26 @@
1
+ ents/__init__.py,sha256=KyqH3dz_BdFHWxD9n4DFItgsrxO7rDtt2IObWkdLsns,582
2
+ ents/cli.py,sha256=QhH7ZQtpEEfM9pyF3SrpfHVPw5IMbLZSIe6ng9C4y_c,16570
3
+ ents/calibrate/PingSMU.py,sha256=1iXozKaPE0ucgB3qsTI5mVk__n6pqC8Z2nV_GRfU2gA,1253
4
+ ents/calibrate/PingSPS.py,sha256=pNQN1ngWQ2Z7Q1DoWCIe4DN02hZfNOrKpLH7I0bAd8U,1876
5
+ ents/calibrate/README.md,sha256=ctdpfS5tN-PnDBn8sCTAn4gi0E-ZLUDXYnLZuqleDmM,109
6
+ ents/calibrate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ ents/calibrate/linear_regression.py,sha256=U-m55mRn-kMj9m-lP-YsbVwIU5ZuA8PiHfNiibefokY,1717
8
+ ents/calibrate/plots.py,sha256=RmuvyL_oTxA4oMNqDR5k82XelvF6whRfYKXNmFbeYeo,2411
9
+ ents/calibrate/recorder.py,sha256=fce5cW2DiCSOMQ-CSRL7raghbR2Yeg8Nx9obyY-0yOg,18416
10
+ ents/calibrate/requirements.txt,sha256=zgyk2vKGVKLGatxI_q9Eik4JIOdtTAc6Gkuo-E7zbRI,87
11
+ ents/config/README.md,sha256=SRtr5YuM64MSZQdy60F8Ib_AI2MR-MbbAdjS3jcjVD4,4447
12
+ ents/config/__init__.py,sha256=3eyHUKg-ZPx82h2CGiNyzaaI7Y7koEOUX9pzrlnVcJw,51
13
+ ents/config/adv_trace.py,sha256=hEDBNbVslJ2lJ-8tfJhPXxQMZN9Nylqv8RK42waRhsM,1290
14
+ ents/config/user_config.py,sha256=SAudV12wyE-5Z0weZJQzBELRXR3-oevIgbrimUF2TmI,39198
15
+ ents/proto/__init__.py,sha256=5THafGlxirjEUATIXB2EVpdOY5APEjztzOAHlRGNG2c,720
16
+ ents/proto/decode.py,sha256=rVnfCBYK637EXYj9FJBOGUSDRQ0xVrqAo7ZHyNRcCCk,2864
17
+ ents/proto/encode.py,sha256=kPAD5-bL6zNEjh6z1n1FusdM9z5UretypxHHAiuGGA4,7753
18
+ ents/proto/esp32.py,sha256=4QOHZNH1SqkJg4TLvJgrAbhdJg88ZNtqoMNLBcWUX2U,4657
19
+ ents/proto/soil_power_sensor_pb2.py,sha256=63Wthg0WROwYX6gkTYsHmY2KZYkPmv_Q6Jjkjcb4ZB4,6729
20
+ ents/simulator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ ents/simulator/node.py,sha256=vDdt_Q2BiEo1Z-aMZnm9_Y-aAtPU_lhcWW-xSDYR54c,4444
22
+ ents-2.3.2.dist-info/METADATA,sha256=kYwyrbAbaLcxzxOGaP7_USrNQsWDsaJnmdg3EnzeRbA,5256
23
+ ents-2.3.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ ents-2.3.2.dist-info/entry_points.txt,sha256=1U6ViZDTy4vv1jFp5RisBPC0NFqmtV3f18eUnWKX0Nk,95
25
+ ents-2.3.2.dist-info/licenses/LICENSE,sha256=2WLObzfS99jXXcPS5INqnmDVZ1nlO8Rj8mzQn2xkn14,1086
26
+ ents-2.3.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ ents = ents.cli:entry
3
+
4
+ [gui_scripts]
5
+ ents-gui = ents.config.user_config:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 jLab in Smart Sensing at UCSC
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.