IoT (Internet of Things)
Neurai's DePIN messaging system provides a foundation for building decentralized IoT networks where devices communicate directly without relying on cloud intermediaries. By combining blockchain-based identity with encrypted messaging, IoT devices can coordinate autonomously while maintaining privacy and operational independence.
Decentralized IoT Communication via DePIN Messaging
The DePIN messaging system turns the Neurai blockchain into an access control layer for IoT networks. Instead of managing API keys or configuring cloud services, devices use blockchain addresses as their identity and assets as network membership tokens.
How It Works
DePIN messaging uses the same cryptographic primitives that secure the blockchain itself:
Address-Based Identity: Each device has a Neurai address derived from its private key. This address serves as both identity and encryption target.
Asset-Based Access Control: Devices must hold a specific asset token to participate in a network. The blockchain verifies ownership, eliminating the need for centralized authentication.
ECIES Encryption with AES-256-GCM: Messages are encrypted per-recipient using Elliptic Curve Integrated Encryption Scheme. This provides:
- Perfect forward secrecy (ephemeral keys for each message)
- Authentication (GCM mode prevents tampering)
- Privacy (only the recipient can decrypt)
The encryption process works like this:
- Sender generates an ephemeral keypair for this message
- Derives a shared secret with recipient's public key using ECDH
- Uses KDF-SHA256 to derive an AES-256 key from the shared secret
- Encrypts message with AES-256-GCM (12-byte nonce, 16-byte authentication tag)
- For each recipient, wraps the AES key using their public key
This means a message sent to 10 devices contains one encrypted payload plus 10 encrypted copies of the decryption key—one per recipient. The message pool can't see who the recipients are (only hash160 of addresses), and only devices with the corresponding private keys can decrypt.
Setting Up a Decentralized IoT Network
Let's walk through a practical example: environmental sensors deployed across a city, all communicating through DePIN messaging.
Step 1: Create Network Token
# Create the access token for the sensor network
neurai-cli issue "CITY_SENSORS" 500 "" "" 0 false true
# This costs 1000 XNA to create
# Supply: 500 units (one per sensor)
# Not divisible (0 decimals)
# Reissuable (can mint more sensors later)
Step 2: Configure DePIN Node
Each sensor needs access to a DePIN node (either running locally or connecting to a remote node). Configuration:
# Enable DePIN messaging
depinmsg=1
depinmsgtoken=CITY_SENSORS
depinmsgport=19002
# Required indexes
assetindex=1
pubkeyindex=1
# Message limits
depinmsgsize=2048 # 2KB max per message
depinmsgmaxusers=50 # Up to 50 recipients
depinmsgexpire=168 # Messages expire after 7 days
Step 3: Distribute Tokens to Devices
Each sensor gets one token to join the network:
# Sensor 1 at address NXsensor1...
neurai-cli transfer "CITY_SENSORS" 1 "NXsensor1..."
# Sensor 2 at address NXsensor2...
neurai-cli transfer "CITY_SENSORS" 1 "NXsensor2..."
# Coordinator device
neurai-cli transfer "CITY_SENSORS" 1 "NXcoord..."
Once a device holds the token and has revealed its public key (by making a transaction), it can send and receive encrypted messages.
Device Implementation
Here's how a sensor device sends data using the DepinESP32 library:
#include <DepinESP32.h>
#include <DHT.h>
DepinESP32 depin;
DHT dht(DHTPIN, DHT22);
void setup() {
Serial.begin(115200);
// Initialize DePIN connection
// Args: node IP, port, token name, device mnemonic
depin.begin("192.168.1.100", 19002, "CITY_SENSORS",
"your twelve word mnemonic phrase here...");
dht.begin();
Serial.println("Sensor initialized");
Serial.print("Device address: ");
Serial.println(depin.getAddress());
}
void loop() {
// Read sensor data
float temp = dht.readTemperature();
float humidity = dht.readHumidity();
if (isnan(temp) || isnan(humidity)) {
Serial.println("Sensor read failed");
delay(60000);
return;
}
// Format message
String message = "{\"type\":\"reading\",";
message += "\"temp\":" + String(temp, 1) + ",";
message += "\"humidity\":" + String(humidity, 1) + ",";
message += "\"timestamp\":" + String(millis()) + "}";
// Send to coordinator
bool sent = depin.sendMessage(message, "NXcoord...");
if (sent) {
Serial.println("Data sent: " + message);
} else {
Serial.println("Send failed");
}
// Send every 5 minutes
delay(300000);
}
Coordinator Device (Raspberry Pi or similar):
from depinpy import DepinClient # Hypothetical Python library
import json
import sqlite3
class SensorCoordinator:
def __init__(self, token_name, mnemonic):
self.depin = DepinClient(token_name)
self.depin.set_credentials(mnemonic)
self.db = sqlite3.connect('sensor_data.db')
self.init_db()
def init_db(self):
cursor = self.db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS readings (
sensor_address TEXT,
timestamp INTEGER,
temperature REAL,
humidity REAL
)
''')
self.db.commit()
def poll_messages(self):
# Get new messages from DePIN pool
messages = self.depin.receive_messages()
for msg in messages:
try:
data = json.loads(msg['content'])
if data['type'] == 'reading':
# Store in database
cursor = self.db.cursor()
cursor.execute('''
INSERT INTO readings VALUES (?, ?, ?, ?)
''', (msg['from'], data['timestamp'],
data['temp'], data['humidity']))
self.db.commit()
# Check for alerts
if data['temp'] > 35.0:
alert = f"High temp alert: {data['temp']}°C at {msg['from']}"
# Send alert to maintenance devices
self.depin.send_message(alert, "NXmaint...")
print(f"Stored reading from {msg['from']}: " +
f"{data['temp']}°C, {data['humidity']}%")
except json.JSONDecodeError:
print(f"Invalid message format from {msg['from']}")
def run(self):
print(f"Coordinator running at {self.depin.get_address()}")
while True:
self.poll_messages()
time.sleep(10) # Poll every 10 seconds
# Run coordinator
coordinator = SensorCoordinator("CITY_SENSORS", "coordinator mnemonic phrase...")
coordinator.run()
Privacy Guarantees
The key advantage of this architecture is privacy. Unlike cloud-based IoT platforms where the service provider can see all data:
What's Private:
- Message content (encrypted per recipient)
- Sensor readings (only coordinator can decrypt)
- Device locations (if not included in messages)
- Communication patterns (pool doesn't know which messages a device can decrypt)
What's Public:
- Token ownership (anyone can see which addresses hold CITY_SENSORS)
- Message metadata (sender address, timestamp, message size)
- Transaction history (token transfers are on-chain)
Practical Implication: The city can verify that sensors are legitimate (they hold the token) without needing to trust a central database. Sensors communicate without revealing data to the network infrastructure itself.
Multi-Device Coordination
DePIN messaging supports multi-recipient messages, enabling efficient broadcast communication:
// Send message to multiple devices at once
String alert = "Maintenance scheduled for 2024-06-15";
// Up to 50 recipients per message (configurable)
String recipients[] = {
"NXsensor1...",
"NXsensor2...",
"NXsensor3...",
"NXmaint..."
};
depin.sendMessageToMultiple(alert, recipients, 4);
Each recipient gets an encrypted copy using their public key, but the plaintext message is only encrypted once. This is more efficient than sending individual messages.
Token-Gating for Network Segmentation
You can create hierarchical networks using sub-assets:
# Main network token
neurai-cli issue "CITY_IOT" 1000 "" "" 0 false true
# Sensor sub-network
neurai-cli issue "CITY_IOT/SENSORS" 500 "" "" 0 false true
# Actuator sub-network
neurai-cli issue "CITY_IOT/ACTUATORS" 200 "" "" 0 false true
# Admin devices
neurai-cli issue "CITY_IOT/ADMIN" 10 "" "" 0 false true
Configure different DePIN nodes for each sub-network:
# Sensor network node
depinmsg=1
depinmsgtoken=CITY_IOT/SENSORS
# Admin network node (different port)
depinmsg=1
depinmsgtoken=CITY_IOT/ADMIN
depinmsgport=19003
Now sensors can only communicate with other sensors, while admin devices on the separate network can manage the infrastructure. Devices holding the root CITY_IOT token can participate in all sub-networks.
Real-World Performance
Message Latency:
- LAN: 10-50ms for message delivery
- Internet: 50-200ms depending on distance
- Encryption overhead: ~1-2ms per recipient
Throughput:
- Single device: ~100 messages/minute (limited by signing and encryption)
- Network capacity: No central bottleneck; each node handles its own pool
- Message size: Default 1KB plaintext, configurable up to 10KB encrypted
Reliability:
- Messages are ephemeral (default 7-day expiry)
- No guaranteed delivery (offline devices miss messages)
- Application layer should implement acknowledgments if needed
This makes DePIN messaging suitable for monitoring and coordination, but not for real-time control loops or guaranteed message delivery scenarios.
Low-Power IoT Devices with Gateway Nodes
Many IoT deployments involve extremely power-constrained devices—solar-powered sensors in remote locations, battery-operated environmental monitors, or devices connected via low-bandwidth networks like LoRa or satellite. These devices can't run a full DePIN node or maintain constant internet connectivity.
The solution is a gateway architecture: low-power devices communicate with an intermediate node via LoRa or satellite, and that gateway handles DePIN messaging operations on their behalf.
Gateway Architecture
The gateway acts as a proxy between resource-constrained devices and the Neurai network:
Low-Power Devices Gateway Node Neurai Network
(ESP32 + LoRa) (Raspberry Pi) (DePIN Messaging)
│ │ │
│ LoRa message │ │
│─────────────────────────>│ │
│ │ Encrypt & sign │
│ │ using device key │
│ │ ─────────────────────> │
│ │ │
│ │ <───────────────────────│
│ LoRa response │ Decrypt response │
│<─────────────────────────│ │
Key Properties:
- Device Autonomy: Each ESP32 has its own private key and can sign messages
- Gateway Trust Model: Gateway proxies messages but can't impersonate devices
- Bandwidth Optimization: LoRa/satellite data is expensive; gateway handles DePIN overhead
- Local Processing: Gateway aggregates data, sends summaries, caches responses
Device-Side Implementation (ESP32 + LoRa)
The ESP32 uses the uNeurai library (ultra-lightweight Neurai implementation) to sign messages without running a full node:
#include <SPI.h>
#include <LoRa.h>
#include <uNeurai.h>
// LoRa pins for ESP32
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26
uNeurai neurai;
void setup() {
Serial.begin(115200);
// Initialize LoRa
SPI.begin(SCK, MISO, MOSI, SS);
LoRa.setPins(SS, RST, DIO0);
if (!LoRa.begin(868E6)) { // 868 MHz for Europe
Serial.println("LoRa init failed");
while (1);
}
LoRa.setSpreadingFactor(12); // Max range, low data rate
LoRa.setSignalBandwidth(125E3); // 125 kHz bandwidth
LoRa.setCodingRate4(8); // Max error correction
// Initialize uNeurai with device private key
// This is stored in secure flash memory
String mnemonic = readMnemonicFromSecureStorage();
neurai.begin(mnemonic);
Serial.println("Device ready");
Serial.print("Address: ");
Serial.println(neurai.getAddress());
}
void loop() {
// Read sensor
float soilMoisture = readSoilMoisture();
float batteryVoltage = readBattery();
// Build minimal message payload
// Format: TYPE|VALUE1|VALUE2|TIMESTAMP
String payload = "SM|" + String(soilMoisture, 1) + "|" +
String(batteryVoltage, 2) + "|" +
String(millis());
// Sign message with device private key
String signature = neurai.signMessage(payload);
// Format for LoRa transmission
// ADDR:PAYLOAD:SIG
String loraMessage = neurai.getAddress() + ":" +
payload + ":" + signature;
// Send via LoRa (max 256 bytes)
if (loraMessage.length() < 240) {
LoRa.beginPacket();
LoRa.print(loraMessage);
LoRa.endPacket();
Serial.println("Sent: " + payload);
// Wait for acknowledgment from gateway
waitForAck();
} else {
Serial.println("Message too long!");
}
// Deep sleep to conserve power (wake every 30 minutes)
ESP.deepSleep(30 * 60 * 1000000); // microseconds
}
void waitForAck() {
unsigned long startTime = millis();
while (millis() - startTime < 5000) { // 5 second timeout
int packetSize = LoRa.parsePacket();
if (packetSize) {
String received = "";
while (LoRa.available()) {
received += (char)LoRa.read();
}
if (received.startsWith("ACK:")) {
Serial.println("Gateway acknowledged");
return;
}
}
delay(100);
}
Serial.println("No ACK received");
}
Power Consumption Analysis:
- Deep sleep: 10μA
- LoRa transmit: 100-120mA for ~1 second
- Sensor reading: 50mA for ~0.5 seconds
- Average: With 30-minute intervals, ~150μA average
- Battery life: 5000mAh battery = ~3 years operation
Gateway Implementation (Raspberry Pi)
The gateway receives LoRa messages, verifies signatures, and proxies to DePIN:
import time
import json
from SX127x.LoRa import LoRa
from SX127x.board_config import BOARD
from depinpy import DepinClient
from neurai_crypto import verify_signature
BOARD.setup()
class LoRaGateway:
def __init__(self, token_name, gateway_mnemonic):
# Initialize LoRa
self.lora = LoRa(verbose=False)
self.lora.set_mode(MODE.SLEEP)
self.lora.set_freq(868.0) # 868 MHz
self.lora.set_spreading_factor(12)
self.lora.set_bw(BW.BW125)
# Initialize DePIN client
self.depin = DepinClient(token_name)
self.depin.set_credentials(gateway_mnemonic)
# Device message cache (avoid duplicates)
self.processed_messages = set()
# Local database for device data
self.device_data = {}
print(f"Gateway initialized at {self.depin.get_address()}")
def start(self):
print("Listening for LoRa messages...")
self.lora.set_mode(MODE.RXCONT) # Continuous receive mode
while True:
# Check for LoRa messages
if self.lora.rx_done():
payload = self.lora.read_payload()
self.handle_lora_message(payload)
# Check for DePIN messages (responses from network)
depin_messages = self.depin.receive_messages()
for msg in depin_messages:
self.handle_depin_message(msg)
time.sleep(0.1)
def handle_lora_message(self, payload):
try:
# Parse: ADDR:PAYLOAD:SIGNATURE
message = payload.decode('utf-8')
parts = message.split(':')
if len(parts) != 3:
print("Invalid message format")
return
device_addr, data, signature = parts
# Verify signature using device's public key
if not verify_signature(data, signature, device_addr):
print(f"Invalid signature from {device_addr}")
return
# Check for duplicates
msg_hash = hashlib.sha256(message.encode()).hexdigest()[:16]
if msg_hash in self.processed_messages:
print("Duplicate message ignored")
return
self.processed_messages.add(msg_hash)
# Parse sensor data
# Format: TYPE|VALUE1|VALUE2|TIMESTAMP
data_parts = data.split('|')
sensor_type = data_parts[0]
if sensor_type == "SM": # Soil Moisture
moisture = float(data_parts[1])
battery = float(data_parts[2])
timestamp = int(data_parts[3])
# Store locally
self.device_data[device_addr] = {
'moisture': moisture,
'battery': battery,
'last_seen': time.time()
}
print(f"Device {device_addr[:10]}...: {moisture}% moisture, " +
f"{battery}V battery")
# Send acknowledgment via LoRa
self.send_lora_ack(device_addr)
# Forward to DePIN network (aggregate multiple sensors)
self.forward_to_depin(device_addr, moisture, battery)
except Exception as e:
print(f"Error processing LoRa message: {e}")
def send_lora_ack(self, device_addr):
ack_message = f"ACK:{device_addr[:10]}"
self.lora.set_mode(MODE.STDBY)
self.lora.write_payload(ack_message.encode())
self.lora.set_mode(MODE.TX)
# Wait for transmission to complete
while self.lora.tx_done() == False:
time.sleep(0.01)
self.lora.set_mode(MODE.RXCONT)
def forward_to_depin(self, device_addr, moisture, battery):
# Aggregate data from multiple devices
# Only send to DePIN network every 10 minutes or on alerts
if moisture < 20.0: # Low moisture alert
alert = json.dumps({
'type': 'alert',
'device': device_addr,
'moisture': moisture,
'battery': battery,
'message': 'Low soil moisture detected'
})
# Send to monitoring station via DePIN
self.depin.send_message(alert, "NXmonitor...")
# Low battery warning
if battery < 3.3:
alert = json.dumps({
'type': 'maintenance',
'device': device_addr,
'battery': battery,
'message': 'Device battery low'
})
self.depin.send_message(alert, "NXmaint...")
def handle_depin_message(self, msg):
# Message from DePIN network for a specific device
# Forward via LoRa to the device
try:
data = json.loads(msg['content'])
if 'target_device' in data:
target = data['target_device']
# Build LoRa command message
cmd = f"CMD:{target}:{data['command']}"
self.lora.set_mode(MODE.STDBY)
self.lora.write_payload(cmd.encode())
self.lora.set_mode(MODE.TX)
print(f"Forwarded command to {target} via LoRa")
except json.JSONDecodeError:
print("Invalid DePIN message format")
# Run gateway
gateway = LoRaGateway("FARM_SENSORS", "gateway twelve word mnemonic...")
gateway.start()
Gateway Trust Model
The gateway architecture maintains device autonomy through cryptographic verification:
What the Gateway Can Do:
- Read LoRa messages (they're broadcast in range)
- Forward signed messages to DePIN network
- Cache device data locally
- Send aggregated summaries
What the Gateway Cannot Do:
- Impersonate devices (doesn't have their private keys)
- Modify messages (signature verification would fail)
- Send DePIN messages as a device (requires device's signature)
Security Properties:
- Each device signs its own messages using its private key
- The DePIN network verifies signatures match the claimed sender address
- Gateway is untrusted: even if compromised, it can't forge device messages
- Devices store private keys in secure flash (ESP32 flash encryption)
Message Compression for Low-Bandwidth Links
LoRa and satellite connections have severe bandwidth constraints. The example above uses a compact format:
Original JSON: {"type":"soil_moisture","value":45.2,"battery":3.87,"timestamp":1698765432}
Size: 78 bytes
Compact format: SM|45.2|3.87|1698765432
Size: 24 bytes
Savings: 69% reduction
For satellite links (even more constrained), use binary encoding:
// Binary format: 1 byte type + 4 bytes data + 4 bytes timestamp
struct SensorMessage {
uint8_t type; // 0x01 = soil moisture
uint16_t value; // Fixed-point: value * 100
uint16_t battery; // Fixed-point: voltage * 100
uint32_t timestamp; // Unix timestamp
} __attribute__((packed));
SensorMessage msg;
msg.type = 0x01;
msg.value = soilMoisture * 100; // 45.2 becomes 4520
msg.battery = batteryVoltage * 100;
msg.timestamp = getTimestamp();
// Total size: 11 bytes (vs 78 bytes JSON)
This matters for satellite communications where you might pay per byte or have severe bandwidth limits (1-2 KB/sec).
Satellite and Long-Range LoRa Connectivity
The most challenging IoT deployments involve devices in areas with no cellular coverage or internet infrastructure: offshore sensors, remote environmental monitoring, agricultural fields, or disaster zones. These scenarios require satellite connectivity or very long-range LoRa, combined with extreme bandwidth constraints.
Satellite IoT Architecture
Low Earth Orbit (LEO) satellite networks like Swarm, Iridium, or custom CubeSat constellations provide global coverage with significant limitations:
Typical Satellite Link Characteristics:
- Bandwidth: 1-2 KB/second (Swarm), up to 10 KB/sec (Iridium)
- Latency: 1-2 seconds to satellite, plus ground station processing
- Cost: $0.01-0.05 per message (Swarm) or per KB (Iridium)
- Availability: Pass windows (satellite overhead), typically 4-8 times per day
The key insight: satellite bandwidth is expensive and limited, so you need to maximize information density and minimize message frequency.
Ultra-Lightweight Blockchain Operations
The uNeurai library enables ESP32 devices to perform essential blockchain operations without a full node:
What uNeurai Can Do:
- Generate addresses from mnemonics (BIP39/BIP44 compatible)
- Sign transactions and messages using secp256k1
- Verify signatures
- Encrypt/decrypt messages using ECIES
- Build unsigned transactions for later broadcast
What It Cannot Do:
- Query blockchain state (requires connection to a node)
- Broadcast transactions directly (needs internet or gateway)
- Verify block history (no local blockchain data)
This is perfect for satellite deployments: the device handles cryptographic operations locally, sends signed transactions via satellite to a gateway, and the gateway broadcasts them to the network.
ESP32 Satellite Node Implementation
Here's a complete implementation for an ESP32 connected to a Swarm satellite modem:
#include <uNeurai.h>
#include <SwarmSatellite.h>
// Swarm M138 modem connected via Serial2
SwarmSatellite swarm(Serial2);
uNeurai neurai;
// Sensor configuration
#define TEMP_SENSOR_PIN 34
#define SOLAR_VOLTAGE_PIN 35
void setup() {
Serial.begin(115200);
Serial2.begin(115200); // Swarm modem
// Initialize uNeurai with device mnemonic
String mnemonic = readSecureMnemonic();
neurai.begin(mnemonic);
// Initialize Swarm modem
if (!swarm.begin()) {
Serial.println("Swarm modem init failed");
while(1);
}
// Wait for satellite acquisition
Serial.println("Waiting for satellite...");
while (!swarm.hasSatelliteFix()) {
delay(1000);
}
Serial.println("Satellite connected");
Serial.print("Device: ");
Serial.println(neurai.getAddress());
}
void loop() {
// Read sensors
float temperature = readTemperature();
float solarVoltage = readSolarPanel();
// Build compact message
// Format: binary packed for minimal size
struct __attribute__((packed)) {
uint8_t msgType; // 0x01 = sensor data
char address[20]; // Truncated Neurai address
int16_t temp; // Temperature * 10
uint16_t voltage; // Voltage * 100
uint32_t timestamp; // Unix timestamp
} payload;
payload.msgType = 0x01;
strncpy(payload.address, neurai.getAddress().c_str(), 19);
payload.address[19] = '\0';
payload.temp = (int16_t)(temperature * 10);
payload.voltage = (uint16_t)(solarVoltage * 100);
payload.timestamp = getTimestamp();
// Sign the payload
String payloadStr = String((char*)&payload, sizeof(payload));
String signature = neurai.signMessage(payloadStr);
// Combine payload + signature for satellite transmission
// Signature is ~70 bytes, payload is ~30 bytes = ~100 bytes total
uint8_t satMessage[128];
memcpy(satMessage, &payload, sizeof(payload));
memcpy(satMessage + sizeof(payload), signature.c_str(), signature.length());
int totalSize = sizeof(payload) + signature.length();
Serial.print("Sending ");
Serial.print(totalSize);
Serial.println(" bytes via satellite");
// Send via Swarm satellite
if (swarm.transmit(satMessage, totalSize)) {
Serial.println("Message queued for satellite transmission");
// Wait for confirmation
if (waitForSatelliteConfirmation()) {
Serial.println("Satellite transmission confirmed");
}
} else {
Serial.println("Satellite transmission failed");
}
// Check for incoming messages from satellite
checkIncomingCommands();
// Deep sleep until next transmission window
// Swarm satellites pass every ~90 minutes
ESP.deepSleep(90 * 60 * 1000000);
}
bool waitForSatelliteConfirmation() {
unsigned long startTime = millis();
while (millis() - startTime < 60000) { // 1 minute timeout
if (swarm.messageConfirmed()) {
return true;
}
delay(500);
}
return false;
}
void checkIncomingCommands() {
if (swarm.available()) {
uint8_t incomingData[256];
int len = swarm.receive(incomingData, 256);
if (len > 0) {
// Parse command
// Expected format: CMD|ADDRESS|OPERATION|PARAMS
String command = String((char*)incomingData, len);
if (command.startsWith("CMD")) {
handleCommand(command);
}
}
}
}
void handleCommand(String cmd) {
// Example: CMD|NXdevice123|SET_INTERVAL|3600
// Means: set reporting interval to 3600 seconds (1 hour)
int firstPipe = cmd.indexOf('|');
int secondPipe = cmd.indexOf('|', firstPipe + 1);
int thirdPipe = cmd.indexOf('|', secondPipe + 1);
String targetAddr = cmd.substring(firstPipe + 1, secondPipe);
String operation = cmd.substring(secondPipe + 1, thirdPipe);
String params = cmd.substring(thirdPipe + 1);
// Verify command is for this device
if (targetAddr != neurai.getAddress().substring(0, 11)) {
Serial.println("Command not for this device");
return;
}
if (operation == "SET_INTERVAL") {
int newInterval = params.toInt();
saveSleepInterval(newInterval);
Serial.print("Interval updated to ");
Serial.print(newInterval);
Serial.println(" seconds");
} else if (operation == "REQUEST_STATUS") {
// Send immediate status update
sendStatusUpdate();
}
}
Ground Station / Gateway for Satellite
The ground station receives satellite messages and processes them:
import swarm
import json
from neurai_rpc import NeuraiRPC
from neurai_crypto import verify_signature, recover_address
class SatelliteGateway:
def __init__(self):
# Connect to Swarm ground station
self.swarm = swarm.Swarm('/dev/ttyUSB0')
# Connect to Neurai node
self.neurai = NeuraiRPC(
'http://localhost:8766',
user='rpcuser',
password='rpcpass'
)
# Message processor
self.device_registry = self.load_device_registry()
def load_device_registry(self):
# Load list of authorized device addresses
with open('devices.json', 'r') as f:
return json.load(f)
def start(self):
print("Satellite gateway running...")
while True:
# Check for incoming satellite messages
if self.swarm.has_message():
msg_data = self.swarm.receive_message()
self.process_satellite_message(msg_data)
time.sleep(5)
def process_satellite_message(self, data):
try:
# Parse binary message
# First 30 bytes: payload
# Remaining bytes: signature
payload = data[:30]
signature = data[30:].decode('utf-8')
# Verify signature
sender_address = self.recover_sender(payload, signature)
if not sender_address:
print("Invalid signature")
return
# Check if device is authorized
if sender_address not in self.device_registry:
print(f"Unauthorized device: {sender_address}")
return
# Parse payload
msg_type = payload[0]
device_addr = payload[1:21].decode('utf-8').rstrip('\x00')
temp = int.from_bytes(payload[21:23], 'little', signed=True) / 10.0
voltage = int.from_bytes(payload[23:25], 'little') / 100.0
timestamp = int.from_bytes(payload[25:29], 'little')
print(f"Device {device_addr}: {temp}°C, {voltage}V solar")
# Store in database
self.store_sensor_data(device_addr, temp, voltage, timestamp)
# Forward to DePIN network if configured
self.forward_to_depin(device_addr, temp, voltage)
# Send to cloud dashboard
self.send_to_dashboard(device_addr, temp, voltage, timestamp)
except Exception as e:
print(f"Error processing satellite message: {e}")
def recover_sender(self, payload, signature):
# Recover public key from signature and verify
# Returns sender address if valid, None otherwise
try:
pubkey = recover_public_key(payload, signature)
address = pubkey_to_address(pubkey)
if verify_signature(payload, signature, address):
return address
else:
return None
except:
return None
def send_command_via_satellite(self, device_addr, command, params):
# Build command message
cmd_str = f"CMD|{device_addr}|{command}|{params}"
# Send via satellite downlink
self.swarm.transmit_message(cmd_str.encode())
print(f"Command sent to {device_addr}: {command}")
# Run gateway
gateway = SatelliteGateway()
gateway.start()
Creating Transactions with uNeurai
Devices can build signed transactions offline and send them via satellite to be broadcast:
// Example: Device needs to send data as a transaction to the blockchain
void sendBlockchainTransaction() {
// Build transaction data
String txData = "Sensor reading: 25.3C at " + String(getTimestamp());
// Create OP_RETURN output with data
NeuraiTransaction tx;
// Add input (must have been prepared beforehand with UTXO info)
// This is the limitation: device needs UTXO data from gateway
tx.addInput(saved_utxo_txid, saved_utxo_vout, saved_utxo_amount);
// Add OP_RETURN output with sensor data
tx.addOpReturnOutput(txData);
// Add change output back to device address
String changeAddress = neurai.getAddress();
uint64_t fee = 1000; // 0.00001 NXA
uint64_t changeAmount = saved_utxo_amount - fee;
tx.addOutput(changeAddress, changeAmount);
// Sign transaction
String signedTx = neurai.signTransaction(tx);
// Send via satellite to gateway for broadcast
swarm.transmit(signedTx.c_str(), signedTx.length());
Serial.println("Transaction sent via satellite");
}
The gateway receives the signed transaction and broadcasts it to the Neurai network:
def handle_transaction_message(self, signed_tx_hex):
try:
# Verify transaction is properly signed
# (already signed by device with uNeurai)
# Broadcast to Neurai network
txid = self.neurai.sendrawtransaction(signed_tx_hex)
print(f"Broadcast transaction: {txid}")
# Log transaction for device records
self.log_device_transaction(txid)
except Exception as e:
print(f"Transaction broadcast failed: {e}")
Long-Range LoRa Without Gateway
For areas with no infrastructure but with devices within ~15km of each other, you can build mesh networks using LoRa:
LoRa Range Parameters:
- Spreading Factor 12, 125kHz BW: ~15km line-of-sight
- With external antenna and height: up to 50km possible
- Data rate: 250 bps to 5470 bps depending on spreading factor
Mesh Configuration:
// Each device acts as both sensor and relay
void setupMeshNode() {
LoRa.begin(868E6);
LoRa.setSpreadingFactor(10); // Balance range vs speed
LoRa.enableCrc();
// Set device as mesh node
meshNode.begin(neurai.getAddress());
}
void relayMessage(String message) {
// Simple flooding protocol with TTL
struct MeshMessage {
char source[20];
char destination[20];
uint8_t ttl;
char payload[200];
};
// Parse incoming message
MeshMessage msg;
parseLoRaMessage(message, &msg);
// If not for us and TTL > 0, relay it
if (strcmp(msg.destination, neurai.getAddress().c_str()) != 0 &&
msg.ttl > 0) {
msg.ttl--;
// Re-broadcast
LoRa.beginPacket();
LoRa.write((uint8_t*)&msg, sizeof(msg));
LoRa.endPacket();
Serial.println("Relayed message");
} else if (strcmp(msg.destination, neurai.getAddress().c_str()) == 0) {
// Message is for us
handleIncomingMessage(msg.payload);
}
}
This creates a self-organizing mesh where messages hop between devices until reaching their destination or TTL expires.
Cost Analysis: Satellite vs LoRa Gateway vs Cellular
Satellite (Swarm):
- Hardware: $119 (modem) + $20-40 (ESP32)
- Service: $5/month for 750 messages
- Per-message cost: ~$0.007
- Best for: Remote areas, global coverage needed
LoRa + Gateway:
- Hardware: $15-25 (ESP32 + LoRa) + $50-100 (Raspberry Pi gateway)
- Service: No recurring costs
- Range: 2-15km depending on terrain
- Best for: Localized deployments, cost-sensitive projects
Cellular (NB-IoT):
- Hardware: $20-30 (ESP32 + NB-IoT modem)
- Service: $2-10/month depending on data
- Best for: Areas with cellular coverage, higher data needs
For truly remote IoT deployments, satellite is often the only option. The combination of uNeurai (for cryptographic operations), DePIN messaging (for secure communication), and LEO satellites provides a complete solution for global IoT networks.
Practical Deployment Considerations
The three architectures above—direct DePIN messaging, gateway nodes, and satellite connectivity—can be mixed based on deployment requirements:
Urban Sensor Network: Direct DePIN over WiFi
- Each sensor has internet via WiFi
- Direct encrypted messaging between devices
- Low latency, high throughput
Rural Agricultural Monitoring: LoRa gateway architecture
- Sensors spread across fields (2-10km range)
- One gateway per farm with internet
- Low power, long battery life
Remote Environmental Monitoring: Satellite connectivity
- Sensors in wilderness areas
- No infrastructure available
- Ultra-low power, infrequent updates
Hybrid Network: All three combined
- Critical sensors use satellite as backup
- Most sensors use LoRa to gateways
- Gateways communicate via DePIN messaging
- Fault-tolerant, geographically distributed
The key advantage of Neurai's approach is that all these architectures use the same cryptographic identity system (blockchain addresses), the same access control mechanism (asset tokens), and the same encryption protocol (ECIES with AES-256-GCM). Whether a message travels over WiFi, LoRa, or satellite, the security properties remain identical.
This means you can deploy devices using different connectivity methods in the same network without managing separate authentication systems or encryption keys. The blockchain provides a universal coordination layer that works regardless of the underlying transport.