Edge SDK

You describe your device. Then we run it.

The Edge SDK is where a manufacturer's devices integrate with WattStack. Devices declare themselves in code which compiles securely and then feeds device capabilities up the stack. Edge SDK access and the Edge evaluation kit are available to partner manufacturers to bench test integration alongside support and guidance from our engineers.

Evaluate Edge

How the SDK works.

Device logic, sandboxed

You write your driver in C using the guided Edge SDK and it executes in the OEM sandbox, a dedicated processor on the Edge. Connectivity, protocol handling, scheduling, clock sync, offline behaviour and telemetry caching are all handled for you, and we insulate your code from every one of them. The security boundary is no longer the device team's to worry about and maintain, it's ours.

The device descriptor

Your code generates a structured, generic description of your device's capabilities via Edge SDK, so the App, Control Room and the API can present and control your device with zero per-device code anywhere else in the chain.

Evaluation hardware

The Edge evaluation kit reads and writes over your device's native protocol, so a manufacturer can prove the mapping on the bench and then pilot rapidly in real current generation devices before committing to designing-in the Edge.

Shipping and updating your driver

A driver ships as one signed blob. You sign it your side, we sign our side, and the Edge verifies both signatures before any of your code executes, and, for security reasons, an older driver cannot replace a newer one. A signed revoke retires a version in the field. Firmware delivery runs through codified Control Room processes that include field trials and staged rollout control.

You write C. The capabilities feed the stack.

Our documentation guides you through declaring your device in C, registering its callbacks, and then handing the tree to the SDK. The Edge compiles it at run time and the device publishes it at boot, or whenever its shape changes. Everything above in the stack, the App, Control Room, an optimiser, a third-party integration, gets a full view of the capabilities of the device.

driver.c
/* ids: one header, used by the build and the collectors */
#define HP_CTRL_FLOW_TEMP 10
#define HP_TELEM_POWER    12

/* The device, declared once. Compiled to the descriptor at run time. */
static const ws_control_point_t s_controls[] = {
  { .id = HP_CTRL_FLOW_TEMP, .name = "Flow Temperature",
    .type = WS_DATA_INT32, .unit = WS_UNIT_DEGC,
    .audience = WS_AUDIENCE_USER,
    .role = { WS_QUANTITY_TEMPERATURE } },
};

static const ws_telemetry_point_t s_telemetry[] = {
  { .id = HP_TELEM_POWER, .name = "Power Draw",
    .type = WS_DATA_FLOAT, .unit = WS_UNIT_WATT,
    .provenance = WS_PROVENANCE_MEASURED,
    .role = { WS_QUANTITY_POWER } },
};

static const ws_function_t s_functions[] = {
  { .id = 1, .name = "Heat Pump", .type = WS_FUNCTION_HEAT_PUMP,
    .power_role = WS_POWER_SINK, .fuel = WS_FUEL_ELECTRIC,
    .controls  = s_controls,  .n_controls  = 1,
    .telemetry = s_telemetry, .n_telemetry = 1 },
};

static const ws_device_descriptor_t s_descriptor = {
  .display_name = "Air Source Heat Pump",
  .manufacturer = "Example Energy", .model = "ASHP-9000",
  .functions = s_functions, .n_functions = 1,
};

/* Platform writes a control. Drive your own bus. */
static ws_error_t on_control(
        const ws_control_value_t *controls,
        uint8_t n, void *ctx)
{
    for (uint8_t i = 0; i < n; i++)
        if (controls[i].control_id == HP_CTRL_FLOW_TEMP)
            hp_set_flow_setpoint(controls[i].int_value);
    return WS_OK;
}

/* Platform polls. Answer from cache, no I/O. */
static void on_telemetry(
        const uint16_t *ids,
        uint8_t n, void *ctx)
{
    ws_telem_value_t out = {
        .telem_id  = HP_TELEM_POWER,
        .n_values  = 1,
        .values    = { s_cache.power_w },
    };
    ws_push_telemetry(&out, 1);
}

/* Your task, your bus. The SDK owns cadence, you own the wire. */
static void hp_poll_task(void *arg)
{
    for (;;) {
        s_cache.power_w = hp_read_power();
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
void oem_main(void)
{
    hp_modbus_init();
    xTaskCreate(hp_poll_task, "hp_poll", 1024, NULL, 2, NULL);

    ws_register_control_callback(on_control, NULL);
    ws_register_telemetry_callback(on_telemetry, NULL);

    /* compiled and published at boot */
    ws_register_descriptor(&s_descriptor);
}

Write C for execution in the sandbox.

descriptor.json
{
  "display_name": "Air Source Heat Pump",
  "manufacturer": "Example Energy",
  "model": "ASHP-9000",
  "functions": [{
    "id": 1,
    "name": "Heat Pump",
    "function_type": "heat_pump",
    "power_role": "sink",
    "fuel": "electric",
    "controls": [{
      "id": 10,
      "name": "Flow Temperature",
      "type": "int",
      "audience": "user",
      "unit": "degc",
      "role": { "quantity": "temperature" }
    }],
    "telemetry": [{
      "id": 12,
      "name": "Power Draw",
      "type": "int",
      "unit": "watt",
      "provenance": "measured",
      "role": { "quantity": "power" }
    }]
  }]
}

Descriptor is generated at runtime.

Bring your device onto the stack.

Edge SDK access is available to our partner manufacturers. Tell us what you are building.