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.
- edge_sync_sdk-0.1.0/.gitignore +45 -0
- edge_sync_sdk-0.1.0/LICENSE +21 -0
- edge_sync_sdk-0.1.0/PKG-INFO +395 -0
- edge_sync_sdk-0.1.0/README.md +346 -0
- edge_sync_sdk-0.1.0/edge_sync/__init__.py +7 -0
- edge_sync_sdk-0.1.0/edge_sync/client.py +287 -0
- edge_sync_sdk-0.1.0/edge_sync/integrations/__init__.py +6 -0
- edge_sync_sdk-0.1.0/edge_sync/integrations/raspberry_pi.py +122 -0
- edge_sync_sdk-0.1.0/edge_sync/integrations/solar_race.py +139 -0
- edge_sync_sdk-0.1.0/edge_sync/queue.py +81 -0
- edge_sync_sdk-0.1.0/edge_sync/sync_policy.py +49 -0
- edge_sync_sdk-0.1.0/pyproject.toml +50 -0
|
@@ -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
|
+

|
|
118
|
+
|
|
119
|
+
**Alerts**
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
**Rules editor**
|
|
124
|
+
|
|
125
|
+

|
|
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.
|