A device is a runtime abstraction for a physical hardware component. It knows how to connect to hardware, read measurements, and evaluate commands, much like an operating-system file handle. An Hdl definition is translated into a Device value, but treating every Hdl script as one device creates a design flaw: declarative language definitions and runtime hardware instances are different concerns. section 3.3.3 section 5.3.1
A functional interface keeps the Device implementation hidden behind abstract types and operations. Construction recursively traverses component definitions, while functions such as addSensor, addController, getDevicePart, and updateDevicePart manipulate the opaque value. This boundary lets the interpreter depend on the interface rather than on storage details. section 3.3.3 section 4.1.3
A practical Device design separates several responsibilities:
- Store parts addressed by component indexes, with sensor and controller implementations represented separately. section 3.3.3 section 4.1.3
- Attach vendor-specific contexts when constructing the runtime value; these contexts may contain network connections, internal state, or actors. section 4.1.3
- Expose component capabilities through APIs: sensors can reset, read measurements, and set callbacks; controllers can reboot, stop, and evaluate commands. section 3.3.3
- Treat lifetime explicitly: a runtime instance may outlive a script, or be disposed when its scope ends; controller presence can determine device visibility. section 5.3.1
Building a device from a hardware definition
A boosters definition can list temperature sensors, pressure sensors, and a controller. makeDevice walks that list, validates each component passport against available vendor components, and inserts the resulting implementation under its component index. The resulting Device can then expose a component for measurement or command evaluation without revealing how vendor handlers are stored. section 4.1.3 section 3.3.3
Listing 3.12 Hardware runtime
module Andromeda.Hardware.Device (
Device, #1
DevicePart,
makeDevice, #2
blankDevice,
addSensor,
addController,
getDevicePart,
updateDevicePart
) where
import Andromeda.Hardware.Common
import Andromeda.Hardware.Hdl
data DevicePart #3
= SensorImpl ComponentPassport
| ControllerImpl ComponentPassport
type DeviceParts = Map ComponentIndex DevicePart #4
data Device = DeviceImpl DeviceParts #5
makeDevice :: Hdl -> Device #6
blankDevice :: Device
addSensor :: ComponentIndex -> Parameter
-> ComponentPassport -> Device -> Device
addController :: ComponentIndex -> ComponentPassport
-> Device -> Device
getDevicePart :: ComponentIndex -> Device
-> Maybe DevicePart
updateDevicePart :: ComponentIndex -> DevicePart
-> Device -> Maybe Device
Inspect the runtime module boundary, including the opaque Device type, DevicePart representation, construction functions, and component access operations. section 3.3.3
Listing 3.13 Connecting the real API
import Andromeda.Vendors.AAA #1
( t25SensorName, p02SensorName
, t25Handler, p02Handler) #2
addSensor :: ComponentIndex -> Parameter #3
-> ComponentPassport -> Device -> Device
addSensor
idx _
def@(ComponentPassport _ cName _ _) #4
(DeviceImpl components) = let #5
handler = getHandler cName #6
sensor = SensorImpl def handler #7
components' = Map.insert idx sensor components #8
in DeviceImpl components' #9
getHandler :: ComponentName -> SensorAPI #10
getHandler cName | cName == t25SensorName = t25Handler
getHandler cName | cName == p02SensorName = p02Handler
getHandler _ | otherwise = error "unknown component"
Inspect how a sensor component is connected to a real vendor handler while the Device machinery remains separate from vendor-specific APIs. section 3.3.3
