ents 2.3.4__py3-none-any.whl → 2.3.6__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 CHANGED
@@ -10,6 +10,8 @@ from ..proto.encode import (
10
10
  encode_bme280_measurement,
11
11
  )
12
12
 
13
+ from ..proto.sensor import encode_repeated_sensor_measurements
14
+
13
15
 
14
16
  class NodeSimulator:
15
17
  """Simulation class to simulate measurements for different sensors"""
@@ -159,3 +161,160 @@ class NodeSimulator:
159
161
  )
160
162
  self.measurements.append(meas)
161
163
  self.measurement_buffer.append(meas)
164
+
165
+
166
+ class NodeSimulatorGeneric:
167
+ """Simulation class to simulate measurements for different sensors"""
168
+
169
+ # temporary storage for measurements to be uploaded
170
+ measurement_buffer: list[bytes] = []
171
+ # all measurements uploaded
172
+ measurements: list[bytes] = []
173
+ # all responses
174
+ responses: list[str] = []
175
+ # all requests in format (headers, body)
176
+ requests: list[tuple[str, str]] = []
177
+
178
+ # metrics for uploads
179
+ metrics: dict[str, int] = {
180
+ "total_requests": 0,
181
+ "failed_requests": 0,
182
+ "successful_requests": 0,
183
+ }
184
+
185
+ latency: list[float] = []
186
+
187
+ def __init__(
188
+ self, cell: int, logger: int, sensors: list[str], _min=-1, _max=1, fn=sin
189
+ ):
190
+ """Initializes the simulation class.
191
+
192
+ Args:
193
+ cell: Cell ID of the node.
194
+ logger: Logger ID of the node.
195
+ sensors: List of sensors to simulate.
196
+ _min: Minimum value for the simulated sensor data.
197
+ _max: Maximum value for the simulated sensor data.
198
+ fn: Function to generate the simulated sensor data.
199
+ """
200
+
201
+ self.cell = cell
202
+ self.logger = logger
203
+ self.sensors = sensors
204
+ self.fn = fn
205
+ self._min = _min
206
+ self._max = _max
207
+
208
+ def __str__(self):
209
+ """String representation of the simulation class
210
+
211
+ Shows the current upload metrics
212
+ """
213
+ avg = np.array(self.latency).mean()
214
+
215
+ last = 0
216
+ if len(self.latency) > 0:
217
+ last = self.latency[-1]
218
+
219
+ return "total: {}, failed: {}, avg (ms): {}, last (ms): {}".format(
220
+ self.metrics["total_requests"],
221
+ self.metrics["failed_requests"],
222
+ avg * 100,
223
+ last * 100,
224
+ )
225
+
226
+ def send_next(self, url: str) -> bool:
227
+ """Sends measurements to a dirtviz instance
228
+
229
+ Args:
230
+ url: URL of the dirtviz instance
231
+
232
+ Returns:
233
+ True if there are measurements to send, False otherwise
234
+ """
235
+
236
+ # get next measurement
237
+ try:
238
+ meas = self.measurement_buffer.pop()
239
+ except IndexError as _:
240
+ return False
241
+
242
+ headers = {
243
+ "Content-Type": "application/octet-stream",
244
+ "SensorVersion": "2",
245
+ }
246
+ result = requests.post(url, data=meas, headers=headers)
247
+
248
+ # store result
249
+ self.requests.append((result.request.headers, result.request.body))
250
+ self.responses.append(result.text)
251
+ self.metrics["total_requests"] += 1
252
+ if result.status_code == 200:
253
+ self.metrics["successful_requests"] += 1
254
+ else:
255
+ self.metrics["failed_requests"] += 1
256
+ self.latency.append(result.elapsed.total_seconds())
257
+
258
+ return True
259
+
260
+ def measure(self, ts: int):
261
+ """Simulate measurements
262
+
263
+ Args:
264
+ ts: Timestamp of the measurement
265
+ """
266
+
267
+ meas = {
268
+ "meta": {
269
+ "ts": ts,
270
+ "loggerId": self.logger,
271
+ "cellId": self.cell,
272
+ },
273
+ "measurements": [],
274
+ }
275
+
276
+ scale = (self._max - self._min) / 2
277
+ offset = (self._max + self._min) / 2
278
+
279
+ for s in self.sensors:
280
+ meas["measurements"].append(
281
+ {
282
+ "type": s,
283
+ "decimal": self.fn(ts) * scale + offset,
284
+ }
285
+ )
286
+
287
+ serialized = encode_repeated_sensor_measurements(meas)
288
+ self.measurements.append(serialized)
289
+ self.measurement_buffer.append(serialized)
290
+
291
+ def last_measurement(self) -> bytes:
292
+ """Gets the last encoded measurement.
293
+
294
+
295
+ Returns:
296
+ Last encoded measurement.
297
+ """
298
+
299
+ return self.measurements[-1]
300
+
301
+ def last_request(self) -> str:
302
+ """Gets the last sent request.
303
+
304
+ Returns:
305
+ Formatted headers and body.
306
+ """
307
+
308
+ headers = self.requests[-1][0]
309
+ body = self.requests[-1][1]
310
+ request_str = f"{headers}\n\n{body}"
311
+ return request_str
312
+
313
+ def last_response(self) -> str:
314
+ """Gets the last response.
315
+
316
+ Returns:
317
+ Response from server.
318
+ """
319
+
320
+ return self.responses[-1]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ents
3
- Version: 2.3.4
3
+ Version: 2.3.6
4
4
  Summary: Python package for Environmental NeTworked Sensor (ENTS)
5
5
  Project-URL: Homepage, https://github.com/jlab-sensing/soil-power-sensor-firmware
6
6
  Project-URL: Issues, https://github.com/jlab-sensing/soil-power-sensor-firmware/issues
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.11
13
13
  Requires-Dist: matplotlib
14
14
  Requires-Dist: pandas
15
- Requires-Dist: protobuf==6.31.1
15
+ Requires-Dist: protobuf==6.33.4
16
16
  Requires-Dist: pyserial
17
17
  Requires-Dist: requests
18
18
  Requires-Dist: scikit-learn
@@ -34,14 +34,14 @@ The soil power sensor protobuf protocol is implemented as a Python package that
34
34
  Use the following to install the `ents` package with gui via `pip`:
35
35
 
36
36
  ```bash
37
- pip install ents[gui]
37
+ pip install ents
38
38
  ```
39
39
 
40
40
  You can also install the package from source with the following:
41
41
 
42
42
  ```bash
43
43
  # install package
44
- pip install .[gui]
44
+ pip install .
45
45
  ```
46
46
 
47
47
  If you are planning to develop the package we recommend you install the package
@@ -51,114 +51,68 @@ reinstall it.
51
51
 
52
52
  ```bash
53
53
  # install development dependencies
54
- pip install -e .[gui,dev]
54
+ pip install -e .[dev]
55
55
  ```
56
56
 
57
- ## Usage
57
+ To install the *deprecated* user config gui, use the following: =
58
+ ```bash
59
+ pip install -e ents[gui]
60
+ ```
58
61
 
59
- The following example code demonstrates decoding the measurement message and encoding a response.
60
62
 
61
- ```python
62
- from ents import encode, decode
63
63
 
64
- # get data encoded by the soil power sensor
65
- data = ...
64
+ ## Simulator (New)
66
65
 
67
- meas_dict = decode(data)
66
+ The webserver `tools/http_decoder.py` can be used to decode uploaded measurements.
67
+
68
+ ### CLI Usage
69
+
70
+ ```
71
+ usage: ents sim_generic [-h] [-v] [--url URL] --sensor SENSOR [SENSOR ...] [--min MIN] [--max MAX] --cell CELL --logger LOGGER [--start START] [--end END] [--freq FREQ] {batch,stream}
72
+
73
+ positional arguments:
74
+ {batch,stream} Upload mode
75
+
76
+ options:
77
+ -h, --help show this help message and exit
78
+ -v, --verbose Print addiitional request information.
79
+ --url URL URL of the dirtviz instance (default: http://localhost:8000)
80
+ --sensor SENSOR [SENSOR ...]
81
+ Type of sensor to simulate
82
+ --min MIN Minimum sensor value (default: -1.0)
83
+ --max MAX Maximum sensor value (default: 1.0)
84
+ --cell CELL Cell Id
85
+ --logger LOGGER Logger Id
86
+
87
+ Batch:
88
+ --start START Start date
89
+ --end END End date
90
+
91
+ Stream:
92
+ --freq FREQ Frequency of uploads (default: 10s)
93
+ ```
94
+
95
+ ### Examples
96
+
97
+ You can find the available sensors in the `sensors.proto` file.
98
+
99
+ Example uploading single measurement
100
+ ```
101
+ ents sim_generic stream --sensor POWER_VOLTAGE --min 20 --max 30 --cell 1 --logger 1
102
+ ```
103
+
104
+ Example uploading multiple measuremnets
105
+ ```
106
+ ents sim_generic stream --sensor TEROS12_VWC_ADJ TEROS12_TEMP TEROS12_EC --min 10 --max 100 --cell 1 --logger 1
107
+ ```
108
+
109
+ Example batch uploads
110
+ ```
111
+ ents sim_generic batch --sensor POWER_CURRENT --cell 1 --logger 1 --start 2026-01-19 --end 2026-01-20 --freq 60
112
+ ```
68
113
 
69
- # process data
70
- ...
71
114
 
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
115
+ ## Simulator (Old)
162
116
 
163
117
  Simulate WiFi sensor uploads without requiring ENTS hardware.
164
118
 
@@ -0,0 +1,30 @@
1
+ ents/__init__.py,sha256=IVe193OL25_F5AMMx272iGabJ1e4jPVNLFAJDktA0oU,706
2
+ ents/cli.py,sha256=pQQTnj0B-OwBnk3d8E4wY03EM9XVDcmolsBvK13nHqg,25029
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=jTSaXUAKHSy_O6g1N7LJQxbZnpXIWZySuNxOKgx5xFo,18479
10
+ ents/config/README.md,sha256=SRtr5YuM64MSZQdy60F8Ib_AI2MR-MbbAdjS3jcjVD4,4447
11
+ ents/config/__init__.py,sha256=3eyHUKg-ZPx82h2CGiNyzaaI7Y7koEOUX9pzrlnVcJw,51
12
+ ents/config/adv_trace.py,sha256=hEDBNbVslJ2lJ-8tfJhPXxQMZN9Nylqv8RK42waRhsM,1290
13
+ ents/config/user_config.py,sha256=evaM3Lb0IU_qFugEeRiXUCMiCUyEbtnRNTXayqE6Qig,39182
14
+ ents/dirtviz/__init__.py,sha256=GLjair60tPxGePVmkifOnfDeMEryDe1HOwVXzOXGrBI,117
15
+ ents/dirtviz/client.py,sha256=LukuhBWFt4n6Z__BbIlWPxaALSrrxaJkp_kS_pvHcxI,5629
16
+ ents/dirtviz/plots.py,sha256=Op_sROjZow5b-eXWz1IQU-ZS8XulCvorbIW4pLWp2lY,982
17
+ ents/proto/__init__.py,sha256=5THafGlxirjEUATIXB2EVpdOY5APEjztzOAHlRGNG2c,720
18
+ ents/proto/decode.py,sha256=_dn9Agv41YgUtMVHZ7l5-Y13ci3hnjyXrRPGKDIHzvo,2964
19
+ ents/proto/encode.py,sha256=kPAD5-bL6zNEjh6z1n1FusdM9z5UretypxHHAiuGGA4,7753
20
+ ents/proto/esp32.py,sha256=4QOHZNH1SqkJg4TLvJgrAbhdJg88ZNtqoMNLBcWUX2U,4657
21
+ ents/proto/sensor.py,sha256=zdZXG-TtX5kpSc6aETOywHApM8xLMY_J2OO8oBlEqU4,7148
22
+ ents/proto/sensor_pb2.py,sha256=Kxe7ZMeaJwQbqv4MHWUK5_4LxTY8Ou1n1bXMDq4KfQ0,3673
23
+ ents/proto/soil_power_sensor_pb2.py,sha256=r7lcuo8A3YW-vsiR7gT0dvG2UMdolpMe3r8kGfKx54U,13442
24
+ ents/simulator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ ents/simulator/node.py,sha256=I3NQXK4xipJKQNmRjsGrt1hUGFiS0US0WgrnXWLHsmI,8726
26
+ ents-2.3.6.dist-info/METADATA,sha256=eVW-dnErw-JoWaAvatBeiH9SMD_HLBpt8mtUgnEuBWM,4956
27
+ ents-2.3.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
28
+ ents-2.3.6.dist-info/entry_points.txt,sha256=1U6ViZDTy4vv1jFp5RisBPC0NFqmtV3f18eUnWKX0Nk,95
29
+ ents-2.3.6.dist-info/licenses/LICENSE,sha256=2WLObzfS99jXXcPS5INqnmDVZ1nlO8Rj8mzQn2xkn14,1086
30
+ ents-2.3.6.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,119 +0,0 @@
1
- import requests
2
- from datetime import datetime, timezone
3
- import pandas as pd
4
- from tabulate import tabulate
5
-
6
-
7
- class DirtVizClient:
8
- BASE_URL = "https://dirtviz.jlab.ucsc.edu/api/"
9
-
10
- def __init__(self):
11
- self.session = requests.Session()
12
-
13
- def cell_from_name(self, name, start=None, end=None):
14
- """Get power data for a specific cell"""
15
-
16
- def get_power_data(self, cell_id, start=None, end=None):
17
- """Get power data for a specific cell"""
18
- endpoint = f"power/{cell_id}"
19
- params = {}
20
-
21
- if start and end:
22
- params = {
23
- "startTime": start.strftime("%a, %d %b %Y %H:%M:%S GMT"),
24
- "endTime": end.strftime("%a, %d %b %Y %H:%M:%S GMT"),
25
- }
26
-
27
- response = self.session.get(f"{self.BASE_URL}{endpoint}", params=params)
28
- response.raise_for_status()
29
- return response.json()
30
-
31
-
32
- def format_data_display(df, cell_id):
33
- """Format the data output with timestamp as first column"""
34
-
35
- # Ensure timestamp exists and is first column
36
- if "timestamp" in df.columns:
37
- cols = ["timestamp"] + [col for col in df.columns if col != "timestamp"]
38
- df = df[cols]
39
-
40
- # Format timestamp nicely
41
- df["timestamp"] = pd.to_datetime(df["timestamp"])
42
- df["timestamp"] = df["timestamp"].dt.strftime("%m-%d-%Y %H:%M:%S")
43
-
44
- # Calculate statistics
45
- stats = {
46
- "Cell ID": cell_id,
47
- "Time Range": (
48
- f"{df['timestamp'].iloc[0]} to {df['timestamp'].iloc[-1]}"
49
- if len(df) > 0
50
- else "N/A"
51
- ),
52
- "Data Points": len(df),
53
- "Avg Voltage (mV)": f"{df['v'].mean():.2f}" if "v" in df.columns else "N/A",
54
- "Max Voltage (mV)": f"{df['v'].max():.2f}" if "v" in df.columns else "N/A",
55
- "Avg Current (µA)": f"{df['i'].mean():.2f}" if "i" in df.columns else "N/A",
56
- "Avg Power (µW)": f"{df['p'].mean():.2f}" if "p" in df.columns else "N/A",
57
- }
58
-
59
- column_rename = {
60
- "timestamp": "Measurement Times",
61
- "v": "Voltage (mV)",
62
- "i": "Current (µA)",
63
- "p": "Power (µW)",
64
- }
65
- # Apply renaming
66
- df = df.rename(columns=column_rename)
67
-
68
- # Display header
69
- print("\n" + "=" * 60)
70
- print(f"CELL {cell_id} POWER DATA SUMMARY".center(60))
71
- for key, value in stats.items():
72
- print(f"• {key:<20}: {value}") # Display the summary information
73
- print("=" * 60 + "\n")
74
-
75
- # Display sample data with timestamp first
76
- if len(df) > 0:
77
- print("DATA BY TIMESTAMPS:")
78
- print(
79
- tabulate(
80
- df,
81
- headers="keys",
82
- tablefmt="grid", # Changed to grid for better column alignment
83
- stralign="center", # Right-align numbers
84
- showindex=False,
85
- numalign="center",
86
- )
87
- )
88
- else:
89
- print("No data available to display")
90
-
91
- print("\n" + "=" * 80)
92
-
93
-
94
- if __name__ == "__main__":
95
- client = DirtVizClient()
96
-
97
- try:
98
- cell_id = 893 # Figure out how to do by name on DirtViz
99
- start = datetime(2025, 8, 12, tzinfo=timezone.utc)
100
- end = datetime.now(timezone.utc)
101
-
102
- print(f"\nFetching power data for cell {cell_id}...")
103
- data = client.get_power_data(cell_id, start, end)
104
-
105
- if data:
106
- df = pd.DataFrame(data)
107
- format_data_display(df, cell_id)
108
-
109
- # Save to CSV with timestamp first
110
- # df.to_csv(f"cell_{cell_id}_power_data.csv", index=False)
111
- # print(f"Data saved to cell_{cell_id}_power_data.csv")
112
- else:
113
- print("No data received for the specified time range.")
114
-
115
- except requests.exceptions.HTTPError as e:
116
- print(f"\nHTTP Error: {e}")
117
- print(f"Response: {e.response.text[:500]}...")
118
- except Exception as e:
119
- print(f"\n⚠️ Unexpected error: {str(e)}")
@@ -1,26 +0,0 @@
1
- ents/__init__.py,sha256=KyqH3dz_BdFHWxD9n4DFItgsrxO7rDtt2IObWkdLsns,582
2
- ents/cli.py,sha256=O071Ot2u7yeMFZyoOY--hp4UepXOHEX2nNiiQ5TxpI4,16573
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/config/README.md,sha256=SRtr5YuM64MSZQdy60F8Ib_AI2MR-MbbAdjS3jcjVD4,4447
11
- ents/config/__init__.py,sha256=3eyHUKg-ZPx82h2CGiNyzaaI7Y7koEOUX9pzrlnVcJw,51
12
- ents/config/adv_trace.py,sha256=hEDBNbVslJ2lJ-8tfJhPXxQMZN9Nylqv8RK42waRhsM,1290
13
- ents/config/user_config.py,sha256=SAudV12wyE-5Z0weZJQzBELRXR3-oevIgbrimUF2TmI,39198
14
- ents/demo/demoPullRequests.py,sha256=YSTVa59puMtER8sjOppiMshUCDjdsIikXLNOM0U6zmU,3864
15
- ents/proto/__init__.py,sha256=5THafGlxirjEUATIXB2EVpdOY5APEjztzOAHlRGNG2c,720
16
- ents/proto/decode.py,sha256=_dn9Agv41YgUtMVHZ7l5-Y13ci3hnjyXrRPGKDIHzvo,2964
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=sNf-97udroXqmimooH0x1IWOTSm-4IoRsFzbyb9aLRg,9491
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.4.dist-info/METADATA,sha256=FMxd6PnezpUeq3ZBVty08iRL-CzgVo5YTugULlzxHZ4,5258
23
- ents-2.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
- ents-2.3.4.dist-info/entry_points.txt,sha256=1U6ViZDTy4vv1jFp5RisBPC0NFqmtV3f18eUnWKX0Nk,95
25
- ents-2.3.4.dist-info/licenses/LICENSE,sha256=2WLObzfS99jXXcPS5INqnmDVZ1nlO8Rj8mzQn2xkn14,1086
26
- ents-2.3.4.dist-info/RECORD,,