edge-sync-sdk 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,45 @@
1
+ # Virtual environments
2
+ .venv/
3
+ venv/
4
+ env/
5
+
6
+ # Python bytecode / caches
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.egg-info/
10
+
11
+ # Build artifacts (wheel/sdist)
12
+ dist/
13
+ build/
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+
18
+ # SQLite databases (local queue + server store) and WAL sidecar files
19
+ *.db
20
+ *.db-wal
21
+ *.db-shm
22
+ *.db-journal
23
+
24
+ # Dashboard CSV exports
25
+ telemetry.csv
26
+
27
+ # Real SDK config with a live API key (keep telemetry.example.json as the template)
28
+ telemetry.json
29
+
30
+ # Editor / OS noise
31
+ .vscode/
32
+ .idea/
33
+ .DS_Store
34
+ Thumbs.db
35
+
36
+ # Reference context only — not part of the SDK, never commit
37
+ SolarRace_OS/
38
+ Pit_Dashboard/
39
+
40
+ # Promo video — hosted in a GitHub issue, not tracked in the repo
41
+ promo/
42
+
43
+ # Local assistant project-notes — kept on disk, not published
44
+ CLAUDE.md
45
+ claude.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lee Rosenblit
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.
@@ -0,0 +1,395 @@
1
+ Metadata-Version: 2.4
2
+ Name: edge-sync-sdk
3
+ Version: 0.1.0
4
+ Summary: Resilient telemetry & edge-sync SDK for the Raspberry Pi — buffer sensor data locally and sync it reliably across network dropouts, in order, with no duplicates.
5
+ Project-URL: Homepage, https://github.com/leerosenblit/Telemetry-Edge-Sync-SDK
6
+ Project-URL: Repository, https://github.com/leerosenblit/Telemetry-Edge-Sync-SDK
7
+ Project-URL: Issues, https://github.com/leerosenblit/Telemetry-Edge-Sync-SDK/issues
8
+ Author-email: Lee Rosenblit <Lee.Rosenblit@au10tix.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Lee Rosenblit
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: buffering,edge,iot,offline-first,raspberry-pi,rest,sqlite,sync,telemetry
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Operating System :: POSIX :: Linux
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.9
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Classifier: Topic :: System :: Monitoring
45
+ Classifier: Topic :: System :: Networking
46
+ Requires-Python: >=3.9
47
+ Requires-Dist: httpx>=0.24
48
+ Description-Content-Type: text/markdown
49
+
50
+ # Telemetry & Edge-Sync SDK
51
+
52
+ > A resilient telemetry & edge-sync SDK for the **Raspberry Pi** — buffer sensor data
53
+ > locally, sync it reliably across network dropouts, in correct time order, with no
54
+ > duplicates. Works with **any sensors**; ships with a solar-race-car integration as a
55
+ > real, proven example.
56
+
57
+ ## Description
58
+
59
+ Lots of Raspberry Pi projects stream sensor data over a flaky link — a field weather
60
+ station, a robot, an environmental or industrial monitor, or a solar race car threading
61
+ through tunnels and dead zones. A naive "push to the cloud" loses every sample sent
62
+ during an outage.
63
+
64
+ This SDK is the fix. On the device it **buffers telemetry to a local SQLite queue
65
+ first**, batches it, and syncs it to a REST server — surviving network dropouts *and*
66
+ reboots. A web portal visualizes it live. Your code integrates in **three lines**
67
+ (`init` / `track` / `force_flush`); whoever watches the dashboard writes **zero**.
68
+
69
+ The pipeline is **sensor-agnostic** — it only moves `metric / value / timestamp`. Two
70
+ ready integrations show the range: a hardware-free **Raspberry Pi system-metrics** one
71
+ that runs on any Pi out of the box, and the **flagship solar race car** (BMS / motor /
72
+ battery-temp decoded from CAN).
73
+
74
+ > **The one guarantee:** no data is lost when the device loses signal, and it arrives in
75
+ > correct chronological order, with no duplicates.
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ pip install edge-sync-sdk
81
+ ```
82
+
83
+ ```python
84
+ from edge_sync import init, track, force_flush
85
+
86
+ init("https://your-server.example.com", api_key="<key>", device_id="pi-01")
87
+ track("cpu_temp_C", 54.2) # any metric — non-blocking, persisted to disk
88
+ force_flush() # drain the queue before exit
89
+ ```
90
+
91
+ Requires Python 3.9+. The only dependency is [`httpx`](https://www.python-httpx.org/).
92
+ (To run the **server + dashboard** locally, clone the repo and `pip install -r
93
+ requirements.txt` — see [Quick start](#quick-start).)
94
+
95
+ ## Features
96
+
97
+ - **Durable offline queue** — every reading is written to on-device SQLite *before* any
98
+ send, so dropouts and reboots lose nothing.
99
+ - **Smart batching** — many samples per request (by count or time): fewer round-trips,
100
+ less radio/battery cost.
101
+ - **Auto-sync recovery** — backlog drains **oldest-first** the moment the link returns.
102
+ - **Ordered, idempotent ingestion** — points carry a client-assigned id; the server
103
+ upserts on it, so retries never duplicate. Stored by **device time**, not arrival time.
104
+ - **Network-aware sync policy** — batch size & frequency adapt to link type and battery.
105
+ - **Server-side alert rule engine** — threshold rules over your signals, editable
106
+ live from the portal.
107
+ - **API keys** — generate/revoke keys from the dashboard; the SDK can `auto_init()` from
108
+ config or environment.
109
+ - **Pit-wall portal** — live charts, device health, alerts, rules editor, device setup.
110
+ - **Your own backend** — plain REST to your own server, no vendor lock-in and no cloud
111
+ credentials on the car.
112
+
113
+ ## Screenshots
114
+
115
+ **Overview — live charts**
116
+
117
+ ![Overview](docs/media/overview.png)
118
+
119
+ **Alerts**
120
+
121
+ ![Alerts](docs/media/alerts.png)
122
+
123
+ **Rules editor**
124
+
125
+ ![Rules](docs/media/rules.png)
126
+
127
+ ## Video
128
+
129
+ ▶ **[Watch the demo walkthrough ](https://github.com/leerosenblit/Telemetry-Edge-Sync-SDK/issues/1)**
130
+
131
+ <!-- To embed an inline PLAYER instead of a link: in issue #1, right-click the video ->
132
+ "Copy link address" to get its asset URL (https://github.com/.../assets/.../….mp4),
133
+ then paste that URL on its own line here, replacing the link line above. -->
134
+
135
+ ---
136
+
137
+ ## Data model (JSON)
138
+
139
+ A **telemetry point** the SDK produces:
140
+
141
+ ```json
142
+ { "id": "solar-car-01-000042-1718900000123", "metric": "battery_temp_C", "value": 46.2, "ts": 1718900000123 }
143
+ ```
144
+
145
+ `id` = `device-sequence-timestamp` (client-assigned → enables idempotent ingestion).
146
+ `ts` is the **device** timestamp in epoch ms (the source of truth for ordering).
147
+
148
+ A **batch** the SDK POSTs (static metadata once, dynamic points many):
149
+
150
+ ```json
151
+ {
152
+ "device_id": "solar-car-01",
153
+ "metadata": { "fw": "RaceOS-2.0", "type": "solar-car", "network": "lte" },
154
+ "points": [
155
+ { "id": "solar-car-01-000001-1718900000100", "metric": "bms_voltage_V", "value": 108.4, "ts": 1718900000100 },
156
+ { "id": "solar-car-01-000002-1718900000101", "metric": "bms_soc_percent", "value": 76.0, "ts": 1718900000101 }
157
+ ]
158
+ }
159
+ ```
160
+
161
+ ## Database
162
+
163
+ Server storage is SQLite. The core table (full schema + ERD in
164
+ [docs/diagrams.md](docs/diagrams.md)):
165
+
166
+ ```sql
167
+ CREATE TABLE telemetry (
168
+ id TEXT PRIMARY KEY, -- client-assigned -> idempotent upsert
169
+ device_id TEXT NOT NULL,
170
+ metric TEXT NOT NULL,
171
+ value REAL NOT NULL,
172
+ device_ts INTEGER NOT NULL, -- order + late-arrival by device time
173
+ received_ts INTEGER NOT NULL -- server arrival (diagnostics only)
174
+ );
175
+ -- sample row:
176
+ -- ('solar-car-01-000042-...', 'solar-car-01', 'battery_temp_C', 46.2, 1718900000123, 1718900000130)
177
+ ```
178
+
179
+ Other tables: `device_meta` (latest metadata per device), `alerts` (rule breaches),
180
+ `rules` (editable alert rules), `api_keys` (issued keys). On the car, the SDK keeps a
181
+ durable `outbox` table.
182
+
183
+ ---
184
+
185
+ ## Public functions
186
+
187
+ What the car's software calls. Full reference + examples in
188
+ [docs/sdk-reference.md](docs/sdk-reference.md).
189
+
190
+ | Function | Purpose |
191
+ |---|---|
192
+ | `init(server_url, api_key, device_id, **opts)` | Configure the SDK (target, auth, identity, options). |
193
+ | `auto_init(config_path=None)` | Configure from a `telemetry.json` file or `TELEMETRY_*` env vars — no hand-coding. |
194
+ | `track(metric, value, ts=None)` | Record one data point. Non-blocking, durable. Returns the point id. |
195
+ | `force_flush(timeout=15.0)` | Block until the queue drains (e.g. before shutdown). |
196
+ | `Client(...)` | The SDK object behind the module API (use directly for multiple devices/instances). |
197
+ | `Client.set_link(network=, battery=)` | Update link conditions at runtime; the batcher adapts immediately. |
198
+ | `track_vehicle_state(vehicle_state, client=None, ts=None)` | Hand a SolarRace-OS dict to the SDK — tracks every numeric signal. |
199
+
200
+ ```python
201
+ from edge_sync.client import init, track, force_flush
202
+ init("https://your-server.example.com", api_key="<key>", device_id="pi-01")
203
+ track("cpu_temp_C", 54.2) # any metric — a sensor reading, a system stat, …
204
+ force_flush()
205
+ ```
206
+
207
+ ### Integrations
208
+
209
+ Device-specific bridges live in `edge_sync/integrations/` — write a small one for your
210
+ project, or just call `track()` directly. Two ship with the SDK:
211
+
212
+ | Integration | What it does |
213
+ |---|---|
214
+ | `raspberry_pi` | Streams the Pi's own system metrics (CPU temp, load, memory, uptime) — **no hardware needed**, runs on any Pi. |
215
+ | `solar_race` (flagship) | Hands a solar car's decoded `vehicle_state` (BMS / motor / battery-temp) to the SDK in one call. |
216
+
217
+ ## Internal functions
218
+
219
+ The implementation behind the guarantee (see [docs/implementation.md](docs/implementation.md)).
220
+
221
+ | Where | Function | Role |
222
+ |---|---|---|
223
+ | `edge_sync/queue.py` | `Queue.enqueue / fetch_unsent / mark_sent` | Durable SQLite outbox; fetch oldest-first; mark sent only after ack. |
224
+ | `edge_sync/client.py` | `_run` | Background batcher loop: flush on timer/nudge, retry with exponential backoff. |
225
+ | `edge_sync/client.py` | `_send_batch / _http_send` | Package points into a batch and POST with `X-API-Key`. |
226
+ | `edge_sync/client.py` | `_apply_policy / _next_id` | Apply network policy; mint the client-assigned point id. |
227
+ | `edge_sync/sync_policy.py` | `plan(network, battery)` | Map link conditions → `(batch_size, flush_interval, allow_send)`. |
228
+ | `server/main.py` | `ingest / _valid_key / load_rules` | Auth, idempotent upsert, alert-rule evaluation. |
229
+
230
+ ---
231
+
232
+ ## Diagrams
233
+
234
+ (Also in [docs/diagrams.md](docs/diagrams.md). Mermaid renders on GitHub.)
235
+
236
+ ### System architecture
237
+
238
+ ```mermaid
239
+ flowchart LR
240
+ subgraph CAR["Solar car — Raspberry Pi"]
241
+ OS["SolarRace-OS<br/>decodes CAN → vehicle_state"] --> BRIDGE["track_vehicle_state()"]
242
+ subgraph SDK["Edge-Sync SDK"]
243
+ BRIDGE --> Q[("SQLite outbox")] --> BATCH["Batcher"] --> SEND["Sender (retry)"]
244
+ end
245
+ end
246
+ SEND -- "POST /api/v1/telemetry (X-API-Key)" --> API
247
+ subgraph SERVER["REST API server (FastAPI)"]
248
+ API["Ingest: auth · upsert · rules"] --> DB[("SQLite")]
249
+ end
250
+ DB --> READ["GET /metrics · /alerts · /devices"] -- "poll 1.5s" --> PORTAL["Pit-wall portal"]
251
+ ```
252
+
253
+ ### Entity-relationship diagram
254
+
255
+ ```mermaid
256
+ erDiagram
257
+ OUTBOX ||..|| TELEMETRY : "syncs (by point id)"
258
+ TELEMETRY ||--o{ ALERTS : "breach raises"
259
+ RULES ||--o{ ALERTS : "evaluated into"
260
+ DEVICE_META ||--o{ TELEMETRY : "describes (device_id)"
261
+ API_KEYS ||..o{ TELEMETRY : "authenticates ingest"
262
+ ```
263
+
264
+ > Full table-by-table ERD with columns is in [docs/diagrams.md](docs/diagrams.md).
265
+
266
+ ### Sequence (track → chart, with offline recovery)
267
+
268
+ ```mermaid
269
+ sequenceDiagram
270
+ autonumber
271
+ participant Pi as Car (SolarRace-OS)
272
+ participant SDK as Edge-Sync SDK
273
+ participant API as REST server
274
+ participant UI as Pit portal
275
+ Pi->>SDK: track_vehicle_state(vehicle_state, ts)
276
+ SDK->>SDK: enqueue to SQLite (durable)
277
+ loop batcher
278
+ alt network up
279
+ SDK->>API: POST /telemetry (batch)
280
+ API->>API: validate key · upsert · rules
281
+ API-->>SDK: 200 {accepted, alerts}
282
+ SDK->>SDK: mark_sent
283
+ else network down
284
+ SDK--xAPI: fails → backoff, keep buffering
285
+ end
286
+ end
287
+ UI->>API: GET /metrics (poll)
288
+ API-->>UI: points (device-time order) + alerts
289
+ ```
290
+
291
+ ### State (the batcher)
292
+
293
+ ```mermaid
294
+ stateDiagram-v2
295
+ [*] --> Idle
296
+ Idle --> Draining: timer / full / force_flush
297
+ Draining --> Sending: unsent exist
298
+ Draining --> Idle: empty
299
+ Sending --> Idle: 200 OK (mark_sent)
300
+ Sending --> Backoff: send failed
301
+ Backoff --> Sending: wait (1s→2s→4s…)
302
+ Idle --> BufferOnly: network = offline
303
+ BufferOnly --> Idle: link restored
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Quick start
309
+
310
+ ```bash
311
+ pip install -r requirements.txt
312
+ ```
313
+
314
+ **Run the server only** (no simulator — use this with a real Pi, and it's exactly what the
315
+ cloud runs):
316
+
317
+ ```bash
318
+ uvicorn server.main:app --host 0.0.0.0 --port 8000
319
+ ```
320
+
321
+ **Or run the all-in-one local demo** (server **+** a simulated solar car + opens the portal —
322
+ for trying it out with no hardware):
323
+
324
+ ```bash
325
+ python scripts/run.py
326
+ ```
327
+
328
+ - **Portal:** http://127.0.0.1:8000/ · **API docs (Swagger):** http://127.0.0.1:8000/docs
329
+ - In the portal's **Setup** tab, generate an API key — it shows a ready-to-paste
330
+ `auto_init()` snippet for your device. Full walkthrough: [docs/getting-started.md](docs/getting-started.md).
331
+ - **On a real Pi**, stream its own system metrics (no hardware) with
332
+ `python examples/pi_metrics.py`, or wire your sensors with three `track()` calls.
333
+ - To run the server **publicly** (so a remote Pi can reach it), deploy it with the
334
+ included [`render.yaml`](render.yaml) / [`Procfile`](Procfile).
335
+
336
+ Resilience demo: while data is streaming, stop and restart the server — the device keeps
337
+ buffering and drains the backlog **in order**, nothing lost.
338
+
339
+ ## REST API
340
+
341
+ | Method & path | Role |
342
+ |---|---|
343
+ | `POST /api/v1/telemetry` | Ingest a batch (auth `X-API-Key`). Idempotent upsert by point id. |
344
+ | `GET /api/v1/metrics?device=&metric=&from=&to=` | Read points, ordered by device time. |
345
+ | `GET /api/v1/devices` | Per-device health + latest metadata. |
346
+ | `GET /api/v1/alerts?device=&limit=` | Recent alerts. |
347
+ | `GET/POST/PATCH/DELETE /api/v1/rules` | Manage alert rules. |
348
+ | `GET/POST/DELETE /api/v1/keys` | Issue / list / revoke API keys. |
349
+
350
+ Full reference: [docs/rest-api.md](docs/rest-api.md).
351
+
352
+ ## Documentation
353
+
354
+ Full docs live in **[`docs/`](docs/)** (the permalink once pushed to GitHub):
355
+ [index](docs/index.md) · [use cases](docs/use-cases.md) · [features](docs/features.md) ·
356
+ [getting started](docs/getting-started.md) · [SDK reference](docs/sdk-reference.md) ·
357
+ [user init & API keys](docs/user-init.md) · [dashboard](docs/dashboard.md) ·
358
+ [implementation](docs/implementation.md) · [REST API](docs/rest-api.md) ·
359
+ [diagrams](docs/diagrams.md).
360
+
361
+ ## Deploying to the cloud
362
+
363
+ To run the server publicly so a remote Pi (on cellular) can reach it, deploy to Render (or
364
+ any Procfile host) using the included [`render.yaml`](render.yaml) / [`Procfile`](Procfile).
365
+
366
+ ## Project layout
367
+
368
+ ```
369
+ edge_sync/ edge SDK: client (queue→batch→retry), durable queue, sync policy, auto_init
370
+ integrations/ device bridges — raspberry_pi.py (system metrics) + solar_race.py (flagship)
371
+ server/ FastAPI REST API + alert rule engine + API keys, serves the portal
372
+ dashboard/ single-page command-center portal (no build step, no CDN)
373
+ docs/ the documentation set (architecture, API reference, diagrams, …)
374
+ examples/ runnable demo.py + pi_metrics.py + the telemetry.json config template
375
+ scripts/ run.py — one-command launcher (server + simulated solar car)
376
+ tests/ test suite + shared conftest fixtures
377
+ pyproject.toml · requirements.txt project config + dependencies
378
+ Procfile · render.yaml · runtime.txt cloud-deploy config (Render)
379
+ ```
380
+
381
+ ## Testing
382
+
383
+ ```bash
384
+ pytest
385
+ ```
386
+
387
+ Covers the core guarantees: no-loss across a network drop, idempotency on lost
388
+ acknowledgements, data survival across a process restart, the alert rule engine, the
389
+ network-aware sync policy, API-key validation, and `auto_init`.
390
+
391
+ ## Design & scope
392
+
393
+ - [docs/architecture.md](docs/architecture.md) — full design and key engineering decisions.
394
+ - [docs/future-work.md](docs/future-work.md) — what's deferred (TSDB, message broker, Protobuf,
395
+ WebSocket push, multi-car fleet) and why, with the path to add each.