///|
/// Errors raised by device adapters and the simulator registry.
pub(all) suberror AdapterError {
EmptyNodeId
EmptyAdapterId
EmptyUnit
EmptyMetric
EmptyDeviceId
EmptyAction
EmptyActionSet
DuplicateAdapterId(String)
UnknownSensor(String)
UnknownActuator(String)
ActionNotAllowed(String)
InvalidTimestamp
ReadUnavailable(String)
} derive(Debug, Eq)
///|
/// A deterministic reading produced by a sensor adapter.
pub(all) struct SensorReading {
sensor_id : String
value : Int32
unit : String
captured_at : Timestamp
} derive(Debug, Eq)
///|
/// A simulated sensor adapter with a fixed increment per read.
pub(all) struct SensorAdapter {
id : String
unit : String
mut value : Int32
step : Int32
} derive(Debug, Eq)
///|
/// Creates a deterministic sensor adapter.
pub fn SensorAdapter::new(
id~ : String,
unit~ : String,
initial_value~ : Int32,
step~ : Int32,
) -> SensorAdapter raise AdapterError {
if id == "" {
raise AdapterError::EmptyAdapterId
}
if unit == "" {
raise AdapterError::EmptyUnit
}
{ id, unit, value: initial_value, step, }
}
///|
/// Reads the current value and advances the deterministic simulator state.
pub fn SensorAdapter::read(
self : SensorAdapter,
at~ : Timestamp,
) -> SensorReading raise AdapterError {
ensure_adapter_timestamp(at)
let reading = {
sensor_id: self.id,
value: self.value,
unit: self.unit,
captured_at: at,
}
self.value = self.value + self.step
reading
}
///|
/// The result of an actuator operation.
pub(all) struct ActuatorResult {
actuator_id : String
action : String
state : String
executed_at : Timestamp
} derive(Debug, Eq)
///|
/// A simulated actuator adapter with an explicit action allowlist.
pub(all) struct ActuatorAdapter {
id : String
allowed_actions : Array[String]
mut state : String
} derive(Debug, Eq)
///|
/// Creates an actuator adapter with a non-empty action allowlist.
pub fn ActuatorAdapter::new(
id~ : String,
allowed_actions~ : Array[String],
initial_state~ : String,
) -> ActuatorAdapter raise AdapterError {
if id == "" {
raise AdapterError::EmptyAdapterId
}
if allowed_actions.length() == 0 {
raise AdapterError::EmptyActionSet
}
for action in allowed_actions {
if action == "" {
raise AdapterError::EmptyAction
}
}
{ id, allowed_actions: copy_strings(allowed_actions), state: initial_state, }
}
///|
/// Applies an allowed action and returns the new actuator state.
pub fn ActuatorAdapter::execute(
self : ActuatorAdapter,
action~ : String,
at~ : Timestamp,
) -> ActuatorResult raise AdapterError {
ensure_adapter_timestamp(at)
if !contains_string(self.allowed_actions, action) {
raise AdapterError::ActionNotAllowed(action)
}
self.state = action
{ actuator_id: self.id, action, state: self.state, executed_at: at, }
}
///|
/// A simulator snapshot containing all adapter state.
pub(all) struct DeviceSnapshot {
node_id : NodeId
sensors : Array[SensorAdapter]
actuators : Array[ActuatorAdapter]
} derive(Debug, Eq)
///|
/// A node-local simulator that exposes sensor and actuator adapter APIs.
pub(all) struct DeviceSimulator {
node_id : NodeId
mut sensors : Array[SensorAdapter]
mut actuators : Array[ActuatorAdapter]
} derive(Debug, Eq)
///|
/// Creates a simulator and rejects duplicate adapter identities.
pub fn DeviceSimulator::new(
node_id~ : NodeId,
sensors~ : Array[SensorAdapter],
actuators~ : Array[ActuatorAdapter],
) -> DeviceSimulator raise AdapterError {
if node_id.value == "" {
raise AdapterError::EmptyNodeId
}
let ids : Array[String] = []
for sensor in sensors {
if contains_string(ids, sensor.id) {
raise AdapterError::DuplicateAdapterId(sensor.id)
}
ids.push(sensor.id)
}
for actuator in actuators {
if contains_string(ids, actuator.id) {
raise AdapterError::DuplicateAdapterId(actuator.id)
}
ids.push(actuator.id)
}
{
node_id,
sensors: copy_sensors(sensors),
actuators: copy_actuators(actuators),
}
}
///|
/// Reads one registered sensor.
pub fn DeviceSimulator::read_sensor(
self : DeviceSimulator,
sensor_id~ : String,
at~ : Timestamp,
) -> SensorReading raise AdapterError {
for index in 0.. DeviceObservation raise AdapterError {
let reading = self.read_sensor(sensor_id~, at~)
DeviceObservation::new(
node_id=self.node_id,
device_id=reading.sensor_id,
health=NodeHealth::Healthy,
metric="value",
value=reading.value,
unit=reading.unit,
captured_at=reading.captured_at,
)
}
///|
/// Reads all registered sensors in registration order.
pub fn DeviceSimulator::read_all(
self : DeviceSimulator,
at~ : Timestamp,
) -> Array[SensorReading] raise AdapterError {
let readings : Array[SensorReading] = []
for index in 0.. ActuatorResult raise AdapterError {
for index in 0.. DeviceSnapshot {
{
node_id: self.node_id,
sensors: copy_sensors(self.sensors),
actuators: copy_actuators(self.actuators),
}
}
///|
/// Restores a simulator from a previously captured snapshot.
pub fn DeviceSimulator::restore(snapshot~ : DeviceSnapshot) -> DeviceSimulator {
{
node_id: snapshot.node_id,
sensors: copy_sensors(snapshot.sensors),
actuators: copy_actuators(snapshot.actuators),
}
}
///|
fn ensure_adapter_timestamp(at : Timestamp) -> Unit raise AdapterError {
if at.unix_seconds < 0 {
raise AdapterError::InvalidTimestamp
}
}
///|
fn contains_string(values : Array[String], value : String) -> Bool {
for item in values {
if item == value {
return true
}
}
false
}
///|
fn copy_strings(values : Array[String]) -> Array[String] {
let copy : Array[String] = []
for value in values {
copy.push(value)
}
copy
}
///|
fn copy_sensors(values : Array[SensorAdapter]) -> Array[SensorAdapter] {
let copy : Array[SensorAdapter] = []
for sensor in values {
copy.push({
id: sensor.id,
unit: sensor.unit,
value: sensor.value,
step: sensor.step,
})
}
copy
}
///|
fn copy_actuators(values : Array[ActuatorAdapter]) -> Array[ActuatorAdapter] {
let copy : Array[ActuatorAdapter] = []
for actuator in values {
copy.push({
id: actuator.id,
allowed_actions: copy_strings(actuator.allowed_actions),
state: actuator.state,
})
}
copy
}