Hardware/Firmware Guide
This document describes basic requirements for a device to be compatible with the Sourceful Energy Network (SEN). The focus is for firmware compatibility. Document is under development.
While there are many types of devices that can be connected to the SEN they are all regarded as gateways in the SEN, the gateway is then responsible for collecting data and controlling one or several energy resources. Some gateways are more special, e.g., a p1 meter and some are more generic in nature, e.g., the Sourceful Energy Gateway.
The firmware for the Sourceful Energy Gateway is open source and can serve as a generic reference implementation: https://github.com/srcfl/srcful-gateway/
Basics
A gateway needs to be identifiable and provide a public key. Gateway id and public key needs to be registered with SEN before the gateway is usable. Internally a gateway needs to maintain the private key and sign data.
All hardware devices will need to be audited and tested before granted access to the network and public release.
Examples
ESP32
There is an ESP32 example project under development at: https://github.com/srcfl/srcful-esp32-example
This example shows:
- Handling of cryptographic keys in software
- Construction and signing of data JWTs
- Construction and signing of Inception message
- BLE message protocol
- Important REST endpoints for onboarding (under construction)
- Safe storage of private key (TODO)
Sourceful Energy Gateway Firmware
While in development the full firmware of the Sourceful Energy Gateway is open source and can work as a reference implementation. This firmware is suitable for micro computers such as raspberry pi 4+ that can run containerized applications.
You find the project here: https://github.com/srcfl/srcful-gateway
Cryptographic Implementation
The SEN relies on cryptographic signatures to validate the source of data, ownership of gateways etc.
The SEN uses ECDSA (Elliptic Curve Digital Signature Algorithm) for cryptographic operations, implemented in two ways:
- Hardware-based using ATECC608A/B secure elements
- Software-based, e.g., using Python's
ecdsa
library
Core Specifications
- Algorithm: ECDSA
- Curve: SECP256r1
- Key Sizes:
- Private Key: 32 bytes (256 bits)
- Public Key: 64 bytes (x,y coordinates, 32 bytes each)
Software Implementation
If software keys are used it is imperative that the private key is stored in a secure way. The private key must not be stored in plain text in files, code nor as environment variables.
Python Example
import json
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec, utils
def base64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
# Generate a new private key
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# Get the private key number
private_numbers = private_key.private_numbers()
private_key_hex = f"{private_numbers.private_value:064x}"
print(f"Private key: {private_key_hex}")
# Get the public key coordinates
public_numbers = public_key.public_numbers()
public_key_hex = f"{public_numbers.x:064x}{public_numbers.y:064x}"
print(f"Public key: {public_key_hex}")
# Create a JWT
header = {
"alg": "ES256",
"typ": "JWT",
"device": "test123",
"opr": "testing"
}
payload = {
"message": "Hello, World!",
"timestamp": 1234567890
}
# Encode header and payload
header_b64 = base64url_encode(json.dumps(header).encode())
payload_b64 = base64url_encode(json.dumps(payload).encode())
# Create message to sign
message = f"{header_b64}.{payload_b64}".encode()
# Hash the message
digest = hashes.Hash(hashes.SHA256())
digest.update(message)
message_hash = digest.finalize()
# Sign the message
signature = private_key.sign(
message_hash,
ec.ECDSA(utils.Prehashed(hashes.SHA256()))
)
# Convert DER signature to raw R,S format
r, s = utils.decode_dss_signature(signature)
signature_bytes = r.to_bytes(32, byteorder='big') + s.to_bytes(32, byteorder='big')
signature_b64 = base64url_encode(signature_bytes)
# Create final JWT
jwt = f"{header_b64}.{payload_b64}.{signature_b64}"
print(f"\nGenerated JWT:\n{jwt}")
SEN Onboarding
Gateway id and public key needs to be registered with SEN before the gateway is usable. Basically, the id and public key is submitted to the network and then registered. In the future, this procedure will be automated through the developer platform and developer APIs.
Before a gateway is usable on the SEN, it must be tied to a wallet public key (inception), e.g., the user's wallet, and finally be given a correct physical location.
Inception
The gateway must accept the wallet public key and add its own id and a cryptographic signature. The gateway then initializes the device in the SEN API.
When the gateway is tied to a wallet, further calls to the SEN API will fail.
mutation {
gatewayInception {
initialize(gatewayInitialization:{idAndWallet:"gateway_id:wallet_public_key", signature:"signature_of_idAndWallet"}) {
initialized
}
}
}
Location
The location for a gateway is important from a standpoint of validity. The Network relies on accurate positions of resources, to e.g., know that a resource is part of a particular area.
Setting the location for a gateway is based on a signature from the owning wallet (see Inception above). The gateway hardware/keys are not part of this, and this part can thus be handled in different ways. The Sourceful Energy App offers setting the location of a device for all gateways a wallet has been paired with.
User Experience
The user experience of onboarding can vary depending on the level of integration desired. Basically, it is up to each gateway firmware to provide the means for:
- Network connectivity - most devices would need some kind of Network access, e.g., WiFi credentials
- Inception/Initialisation
- Location (Optional)
Local Interface
In the simplest form, this is handled via the gateway itself, via a local interface, e.g., an internal webpage. This may be sufficient for a deeply technical product, possibly with other complex configuration of connected devices etc. The main caveat for a unified user experience in this case is the integration of the wallet key. There are, however, modules for most popular web frameworks that can handle this seamlessly. In the simplest case, the user will need to copy paste the wallet public key into the local interface. Depending on needs, the local interface can be as advanced as needed and provide a full user experience by utilizing the data egress of the SEN Network to do visualizations etc.
Bespoke App
For a more non-technical user experience, a bespoke App that communicates with the gateway may be developed.
Sourceful Energy App
The onboarding may be integrated into the Sourceful Energy App (SEA) for a seamless experience into the Network. Using the SEA, the user can always set location of an owned gateway. The SEA also offers Network connectivity, inception integration of the gateway, and a walletless mode (for users that do not have a separate crypto wallet) but this requires additional functionality in the gateway firmware. There are two basic ways to connect the client and the gateway for onboarding, soft ap or BLE. We recoomend ble for a smoother user experience and compatibility with web apps.
Wifi provisioning is an optional step and is placed last. The SEA does not require the gateway to have internet connectivity to be onboarded to the network per se (but it would obviously need to send data to the network at some point to be useful). Low spec devices can therefore do the onboarding using e.g. BLE only, no messages needs to be sent from the gateway to the backend during this process. The advantage is that the gateway does not need to maintain e.g. BLE stack and SSL Certificates at the same time. In essence the gateway can become operational after it has a wifi connection and BLE has been stoped.
Onboarding via BLE looks like this:
sequenceDiagram
participant U as User
participant C as Client
participant G as Gateway
participant B as Backend
U->>G: Enable Config Mode
U->>C: Onboard Gateway
C->>G: Connect via BLE
C->>G: GET api/crypto
G-->>C: serial & public key
C->>U: Display serial & public key
U->>C: Claim gateway
C->>B: Check gateway status (serial)
B-->>C: Return gateway info (exists, wallet, location)
U->>C: Request gateway registration
C->>G: POST api/initialize (wallet public key)
G-->>C: Return wallet, ID, gateway signature
C->>B: Initialize gateway (wallet, ID, signature)
B-->>C: Confirmation
U->>C: Provide gateway location
C->>U: Request location message signature
U-->>C: location message wallet signature
C->>B: Set gateway location (location message)
B-->>C: Location confirmed
Note over U,B: WiFi Provisioning Process
U->>C: Complete onboarding
C->>G: POST api/ble/stop
G-->>C: Status OK
Note over G: Optionally Disable BLE & exit config mode
C->>G: BLE Disconnect
C->>U: Request credentials signature
U-->>C: Sign gateway credentials
Network Connectivity
Wifi provisioning is done using a few endpoints GET api/wifi
, POST api/wifi
and 'GET api/wifi/scan`. We reccomend that the gateway performs a network scan when entering its configuration mode. There is no call to scan per unless the user initiates it.
The wifi provisioning process looks like this:
sequenceDiagram
participant U as User
participant C as Client
participant G as Gateway
C->>G: GET api/wifi
G-->>C: SSIDs list & current connection status
C->>U: Display available networks
opt User requests refresh
U->>C: Scan for networks
C->>G: GET api/wifi/scan
G-->>C: Scanning...
Note over G: Perform WiFi scan
G-->>C: Available networks
C->>U: Display updated networks
end
U->>C: Select network & provide password
C->>G: POST api/wifi (ssid, password)
Note over G: Attempt connection
G-->>C: Connection status
C->>G: GET api/wifi
G-->>C: Connected: selected SSID
C->>U: Display connection success
Inception
The gateway should expose a REST endpoint that accepts the wallet public key. This endpoint should be accessible via BLE or local Network access via mDNS.
POST api/initialize
{
"wallet":"public_key"
}
SEN Data Ingress
Gateways send data to the SEN using a signed JSON Web Token (JWT) format. You can read more about the format specification here: https://jwt.io/introduction. Frequency of sending is 10 seconds, but may be more seldom. You may send several data packages in the same payload of the JWT but the data needs to be from the same energy resource for each JWT. E.g. if a gateway is connected to both an inverter and a meter the data from the inverter needs to be sent in one JWT (with optionally many datapoints) and data from the meter in another JWT (with optionally many datapoints).
In particular, data is ingested using signed JWTs (not encrypted) via HTTPS. I.e., standard safe HTTPS transport is used to perform end-to-end encryption of the data.
Header
The header consists of the standard fields plus extra protocol-specific fields.
{
"alg": "ES256",
"typ": "JWT",
"device": "device_id",
"opr": "testing",
"licence": "device licence key",
"developer": "developer licence key",
"model": "model string",
"dtype": "payload datatype specifics",
"sn": "data serial number"
}
Energy Meter Header Example
{
"alg": "ES256",
"typ": "JWT",
"device": "0123752ceb741f6eee",
"opr": "production",
"model": "p1homewizard",
"dtype": "p1_telnet_json",
"sn": "LGF5E360"
}
One note is that technically the ES256R
algorithm/curve is used, that is ECDSA using SECP256r1 curve and SHA-256 and not ECDSA using P-256 curve and SHA-256.
Payload
The body consists of the data the device sends, this data consists of one or more timestamped JSON objects. This is not generally standardized at this point. Timestamps are always in UTC.
Energy Meter Payload Example
{
"1739352916813": {
"serial_number": "LGF5E360",
"rows": [
"0-0:1.0.0(250212103510W)",
"1-0:1.8.0(00010968.132*kWh)",
"1-0:2.8.0(00000000.000*kWh)",
"1-0:3.8.0(00000005.151*kVArh)",
"1-0:4.8.0(00002109.781*kVArh)",
"1-0:1.7.0(0000.253*kW)",
"1-0:2.7.0(0000.000*kW)",
"1-0:3.7.0(0000.000*kVAr)",
"1-0:4.7.0(0000.084*kVAr)",
"1-0:21.7.0(0000.064*kW)",
"1-0:22.7.0(0000.000*kW)",
"1-0:41.7.0(0000.161*kW)",
"1-0:42.7.0(0000.000*kW)",
"1-0:61.7.0(0000.028*kW)",
"1-0:62.7.0(0000.000*kW)",
"1-0:23.7.0(0000.000*kVAr)",
"1-0:24.7.0(0000.000*kVAr)",
"1-0:43.7.0(0000.000*kVAr)",
"1-0:44.7.0(0000.052*kVAr)",
"1-0:63.7.0(0000.000*kVAr)",
"1-0:64.7.0(0000.032*kVAr)",
"1-0:32.7.0(233.6*V)",
"1-0:52.7.0(233.0*V)",
"1-0:72.7.0(232.9*V)",
"1-0:31.7.0(000.2*A)",
"1-0:51.7.0(000.7*A)",
"1-0:71.7.0(000.1*A)",
"!0E78"
],
"checksum": "0E78"
}
}
Signature
The signature is the signature generated by the device private key that can be verified by the public key.
Full Signed JWT Energy Meter Example
eyJhbGciOiAiRVMyNTYiLCAidHlwIjogIkpXVCIsICJkZXZpY2UiOiAiMDEyMzc1MmNlYjc0MWY2ZWVlIiwgIm9wciI6ICJwcm9kdWN0aW9uIiwgIm1vZGVsIjogInAxaG9tZXdpemFyZCIsICJkdHlwZSI6ICJwMV90ZWxuZXRfanNvbiIsICJzbiI6ICJMR0Y1RTM2MCJ9.eyIxNzM5MzUyOTE2ODEzIjogeyJzZXJpYWxfbnVtYmVyIjogIkxHRjVFMzYwIiwgInJvd3MiOiBbIjAtMDoxLjAuMCgyNTAyMTIxMDM1MTBXKSIsICIxLTA6MS44LjAoMDAwMTA5NjguMTMyKmtXaCkiLCAiMS0wOjIuOC4wKDAwMDAwMDAwLjAwMCprV2gpIiwgIjEtMDozLjguMCgwMDAwMDAwNS4xNTEqa1ZBcmgpIiwgIjEtMDo0LjguMCgwMDAwMjEwOS43ODEqa1ZBcmgpIiwgIjEtMDoxLjcuMCgwMDAwLjI1MyprVykiLCAiMS0wOjIuNy4wKDAwMDAuMDAwKmtXKSIsICIxLTA6My43LjAoMDAwMC4wMDAqa1ZBcikiLCAiMS0wOjQuNy4wKDAwMDAuMDg0KmtWQXIpIiwgIjEtMDoyMS43LjAoMDAwMC4wNjQqa1cpIiwgIjEtMDoyMi43LjAoMDAwMC4wMDAqa1cpIiwgIjEtMDo0MS43LjAoMDAwMC4xNjEqa1cpIiwgIjEtMDo0Mi43LjAoMDAwMC4wMDAqa1cpIiwgIjEtMDo2MS43LjAoMDAwMC4wMjgqa1cpIiwgIjEtMDo2Mi43LjAoMDAwMC4wMDAqa1cpIiwgIjEtMDoyMy43LjAoMDAwMC4wMDAqa1ZBcikiLCAiMS0wOjI0LjcuMCgwMDAwLjAwMCprVkFyKSIsICIxLTA6NDMuNy4wKDAwMDAuMDAwKmtWQXIpIiwgIjEtMDo0NC43LjAoM