Embedded IoT control engine for BeagleBone, replacing a C/C++ system.
Version: 2.8.10 · Production: live on BeagleBone (BACnet + MQTT + LocalIo drivers + HVAC control loop) · Tests: 2,677 passing, 0 clippy warnings · Dependencies: 0 C/C++ (pure Rust, no FFI)
Sandstar is a channel-based hardware I/O abstraction layer for HVAC control. It reads sensors, applies conversions via lookup tables, runs control logic (PID loops, sequencers), drives actuators, speaks to industrial-control peers over BACnet/IP and MQTT, and exposes everything through Project Haystack REST + WebSocket APIs. The Sedona VM runs in-process as pure Rust bytecode.
- Target: BeagleBone Black (ARM Cortex-A8, 512 MB RAM, Debian Linux)
- Replaces: ~27,000 lines C/C++ engine + 6,839 lines C SVM + 500,000 lines POCO framework
- Result: ~40,000 lines pure Rust, zero C sources, statically-linked ARM binary (no
.sodependencies) - Observability: Prometheus
/metricsendpoint with 18 counters + 5 gauges - Clustering: roxWarp gossip protocol with mDNS peer discovery (opt-in)
crates/
sandstar-engine/ # Core: channels, tables, conversions, filters, polls, watches, PID, sequencer
sandstar-hal/ # HAL trait definitions + MockHal for testing
sandstar-hal-linux/ # Linux sysfs drivers: GPIO, ADC, I2C, PWM, UART
sandstar-ipc/ # IPC wire protocol (length-prefixed bincode over TCP/Unix socket)
sandstar-server/ # HTTP/WS server, REST, SOX/DASP, driver framework, roxWarp, control engine
sandstar-cli/ # CLI (clap): status, channels, read, write, reload, history, convert-sax
sandstar-svm/ # Pure Rust Sedona VM (240 opcodes, 131 native methods, no C/FFI)
All crates share the workspace version and edition = "2021".
# Demo mode (5 channels, MockHal)
cargo run -p sandstar-server
# With real config
SANDSTAR_CONFIG_DIR="/path/to/EacIo" cargo run -p sandstar-server
# CLI
cargo run -p sandstar-cli -- status
cargo run -p sandstar-cli -- channels
cargo run -p sandstar-cli -- read 1113# Development (Windows/Linux/macOS)
cargo build --workspace
cargo test --workspace
# ARM cross-compile for BeagleBone (Zig CC toolchain; no Docker required)
export CC_armv7_unknown_linux_gnueabihf="C:\\czb\\zigcc-arm-cc.bat"
export AR_armv7_unknown_linux_gnueabihf="C:\\czb\\zigar-arm.bat"
cargo arm-build # alias defined in .cargo/config.toml
cargo arm-deb --no-strip # packages as target/debian/sandstar_<ver>-1_armhf.deb
# With TLS support
cargo build -p sandstar-server --features tlsSee docs/Progress/DEPLOYMENT_CHECKLIST.md for the full rollout runbook.
Set via --config-dir or SANDSTAR_CONFIG_DIR; contains:
points.csv— channel definitions (channel number, I/O type, conversion params)tables.csv— lookup table mappings (tag to file path)database.zinc— Haystack point database with tagscontrol.toml— PID loops, sequencers, components (optional)
SANDSTAR_BACNET_CONFIGS— JSON array ofBacnetConfig; seedocs/Progress/BACNET_SETUP.mdSANDSTAR_MQTT_CONFIGS— JSON array ofMqttConfig; seedocs/Progress/MQTT_SETUP.md
| Flag | Env Var | Default | Description |
|---|---|---|---|
--config-dir, -c |
SANDSTAR_CONFIG_DIR |
(demo mode) | Config directory path |
--http-port |
SANDSTAR_HTTP_PORT |
8085 | HTTP listen port |
--http-bind |
SANDSTAR_HTTP_BIND |
127.0.0.1 | HTTP bind address |
--poll-interval-ms, -p |
SANDSTAR_POLL_INTERVAL_MS |
1000 | Poll cycle interval |
--auth-token |
SANDSTAR_AUTH_TOKEN |
(none) | Bearer token for POST endpoints |
--auth-user |
SANDSTAR_AUTH_USER |
(none) | SCRAM-SHA-256 username |
--auth-pass |
SANDSTAR_AUTH_PASS |
(none) | SCRAM-SHA-256 password |
--rate-limit |
SANDSTAR_RATE_LIMIT |
100 | Max requests/sec (0 = unlimited) |
--read-only |
false | Reject output writes (validation mode) | |
--no-control |
false | Disable PID/sequencer engine | |
--cluster |
false | Enable roxWarp gossip clustering (see --cluster-config) |
|
--cluster-config |
(none) | Path to ClusterConfig JSON (peers, mTLS, mDNS) |
|
--tls-cert |
SANDSTAR_TLS_CERT |
(none) | TLS certificate (PEM) |
--tls-key |
SANDSTAR_TLS_KEY |
(none) | TLS private key (PEM) |
--sedona |
false | Enable Sedona VM | |
--scode-path |
SANDSTAR_SCODE_PATH |
(none) | Sedona scode image path |
--log-level |
RUST_LOG |
info | Log level filter |
--log-file |
SANDSTAR_LOG_FILE |
(none) | Log file path |
--socket, -s |
SANDSTAR_SOCKET |
(platform default) | IPC socket (UDS on Linux, TCP on Windows) |
--no-rest |
false | Disable REST API | |
--no-pid-file |
false | Skip PID file creation |
Core Haystack + Sandstar endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/about |
GET | Server metadata |
/api/ops |
GET | Available operations |
/api/read |
GET | Read channels by id or filter (supports dynamic tag filters) |
/api/status |
GET | Engine status |
/api/channels |
GET | List all channels |
/api/polls |
GET | List poll groups |
/api/tables |
GET | List lookup tables |
/api/pointWrite |
GET/POST | Read priority array / write value at priority level |
/api/watchSub |
POST | Subscribe to channel changes |
/api/watchPoll |
POST | Poll for changes |
/api/watchUnsub |
POST | Unsubscribe |
/api/history/{channel} |
GET | Historical data |
/api/pollNow |
POST | Trigger immediate poll |
/api/reload |
POST | Reload configuration |
/api/auth |
POST | SCRAM-SHA-256 authentication |
/api/metrics |
GET | Internal metrics as JSON |
/metrics |
GET | Prometheus exposition format (scraper-friendly) |
/api/ws |
GET | Haystack WebSocket upgrade (sub-second COV push via driver CovEvent bridge) |
/health |
GET | Health check |
Driver framework (Phase 12.0G — runtime driver lifecycle, auth-gated):
| Endpoint | Method | Description |
|---|---|---|
/api/drivers |
GET | List registered drivers |
/api/drivers |
POST | Create a driver from {driver_type, config} JSON |
/api/drivers/{id} |
DELETE | Deregister + close a driver |
/api/drivers/{id}/status |
GET | Driver + per-point status |
/api/drivers/{id}/learn |
GET | Discover points |
/api/drivers/{id}/open |
POST | Bring the driver up |
/api/drivers/{id}/close |
POST | Shut down without removing |
/api/drivers/{id}/ping |
POST | Health check |
/api/drivers/{id}/write |
POST | Batch write values |
/api/drivers/{id}/message |
POST | Driver-specific custom message (12.0E on_receive) |
/api/syncCur |
POST | Batch read across drivers |
Dynamic slots / tags (Phase 13):
| Endpoint | Method | Description |
|---|---|---|
/api/tags/{comp_id} |
GET/PUT/DELETE | Read / merge+replace / delete dynamic tags on a component |
Visual-editor SOX REST (Phase 14.0A — 11 endpoints):
| Endpoint | Method | Description |
|---|---|---|
/api/sox/tree |
GET | Full component tree |
/api/sox/comp/{id} |
GET | Component detail |
/api/sox/palette |
GET | Available component types from manifest |
/api/sox/names |
GET | Interned name table stats |
/api/sox/comp |
POST | Add a component |
/api/sox/comp/{id} |
DELETE | Remove a component |
/api/sox/comp/{id}/name |
PUT | Rename |
/api/sox/comp/{id}/slot/{idx} |
PUT | Write slot value |
/api/sox/comp/{id}/invoke/{slot} |
POST | Invoke an action slot |
/api/sox/comp/{id}/pos |
PUT | Update editor position |
/api/sox/comp/{id}/reorder |
PUT | Reorder children |
/api/sox/link |
POST/DELETE | Add / remove a link between slots |
Cluster + SOX WebSocket:
| Endpoint | Method | Description |
|---|---|---|
/api/rows |
GET | RoWS (SOX-over-WebSocket) session — tree, slot, link, readTags/setTags/deleteTag |
/roxwarp |
GET | roxWarp cluster peer WebSocket (when --cluster is set) |
All endpoints support JSON by default; Accept: text/zinc returns Haystack Zinc. Per-endpoint details in docs/Progress/DRIVER_REST_API.md.
Built-in, Sedona-free control primitives:
- PID controllers with anti-windup, max delta, direct/reverse action
- Lead sequencers with N-stage hysteresis
- 35 built-in components: arithmetic (Add, Sub, Mul, Div, Min, Max, Avg), logic (And, Or, Not, Mux), timing (Delay, Ramp, Tpd, Ewma), HVAC (Deadband, Economizer, Tstat), scheduling (WeeklySchedule, HolidaySchedule), constants
- TOML configuration (
control.toml) with[[loop]],[[sequencer]],[[component]]sections - Channel-to-logic bridge — rename a
ConstFloatto"chXXXX"(e.g.ch1713) to proxy live sensor data into the component graph
Convert legacy Sedona .sax files:
cargo run -p sandstar-cli -- convert-sax app.sax --output control.tomlNetwork drivers (Project B / Project C / Phase 12):
- BACnet/IP — discovery, ReadProperty, WriteProperty, RPM batching, SubscribeCOV with renewal, BBMD foreign-device registration
- MQTT v3.1.1 — subscribe with value caching, publish on write (plain
f64or{"value":N}envelope) - LocalIoDriver — engine-channel façade so local GPIO/ADC/I2C/PWM points are queryable through the same
/api/driverssurface
All async drivers share a generic loader (Phase 12.0A), PointStatus::Remote* per-point error reporting (12.0B), SyncContext/WriteContext callback API (12.0C), CovEvent broadcast channel (12.0D), WebSocket push bridge (12.0D.WS), custom on_receive messaging (12.0E), and runtime REST lifecycle (12.0G).
Multi-device gossip protocol for distributed state:
- Binary Trio diff over WebSocket (optionally WSS + mTLS)
- Version vectors with LWW conflict resolution
- mDNS peer discovery (opt-in,
ClusterConfig.enable_mdns) - Static peer list (back-compat; both can coexist)
- Fantom pod for SkySpark — external project (not in this repo)
Protocol spec in docs/research/16_ROXWARP_PROTOCOL.md.
- Bind address: 127.0.0.1 by default (loopback only)
- Bearer-token auth: protects POST endpoints (
--auth-token) - SCRAM-SHA-256: RFC 5802 challenge-response auth (
--auth-user/--auth-pass) - TLS: optional via rustls (
--tls-cert/--tls-key) - mTLS for cluster peers (
ClusterConfig.{cert_path, key_path, ca_path}) - Rate limiting: 100 req/s default, configurable (
--rate-limit) - CORS: restricted method/header whitelist
- Filter depth limit: max 32 nested expressions
- Watch cap: max 64 subscriptions, 256 channels per watch
- Path sanitization: config paths canonicalized before use
- Vulnerability reporting: see
SECURITY.md
sandstar-engine.service— production servicesandstar-rust-validate.service— read-only validation alongside another engine
| Script | Purpose |
|---|---|
tools/installSandstarRust.sh |
Deploy .deb to a named device |
tools/bacnet_sim.py |
Hand-crafted BACnet/IP simulator for driver validation |
tools/ws_latency_test.py |
Live validator for the 12.0D.WS push bridge |
tools/connections/*.sh |
expect-based SSH connection scripts per device |
# All tests
cargo test --workspace
# Individual crates
cargo test -p sandstar-engine
cargo test -p sandstar-server
# etc.
# Ignored (multicast / live-hardware) tests
cargo test -- --ignored2,677 tests across the workspace. Platform-specific tests (sysfs GPIO, I2C, PWM) are gated behind #[cfg(target_os = "linux")]. Live-network tests (mDNS multicast) are marked #[ignore] and require a multicast-capable host.
docs/Progress/OVERVIEW.md— project status + doc mapdocs/Progress/ROADMAP_v2.md— full roadmap, master status tracker, research-doc coveragedocs/Progress/DRIVER_REST_API.md— operator + integrator guide for the REST surfacedocs/Progress/BACNET_SETUP.md,docs/Progress/MQTT_SETUP.md— driver enablementCHANGELOG.md— per-version highlightsdocs/research/— 20 research documents covering the original migration analysis
Licensed under the Academic Free License version 3.0. Full text in LICENSE.