Using strong-typed codecs and interfaces
This example covers some more complicated examples using typedState and shows how IDE hinting can use this information for a better development experience.
We'll be using a MSGID
enum for variable management, custom types, and the ElectricUIDeveloperState
enforced types. The basics are covered in the typedState and structures guides:
Forcing types by msgID
As a quick recap,
- Declare enum members with the message ID strings,
- Export custom types for any structured packets
- Add entries to the
ElectricUIDeveloperState
using the ID and the type
In the typedState.ts
file,
export enum MSGID { NICKNAME = 'name', FAN = 'fan', SUPERVISOR = 'sup',}declare global { interface ElectricUIDeveloperState { [MSGID.NICKNAME]: string [MSGID.SUPERVISOR]: SupervisorState [MSGID.FAN]: | [FanSetting] | [FanSetting, FanSetting] // if aux fan is supported [MSGID.FAN_SPEED]: number | number[] [MSGID.FAN_CURVE]: never // commented out in firmware }}export type SupervisorState = { control_state: string fan_count: number system_ok: boolean}export type FanSetting = { rpm: number setpoint: number state: number}
For projects with more than a handful of message identifiers, autocomplete suggestions are possible, instead of manually typing strings each time!
In conjunction, these basic language features allow for IDE completion suggestions with correct type information for the variable being used.
Multi-typed variables
In situations where firmware might have compile-time options, or varies the number of array elements based on hardware configuration, it's useful to allow a set of types for a given message identifier.
This example allows one, two or three element arrays of FanSetting
typed objects,
[MSGID.FAN]: | [FanSetting] | [FanSetting, FanSetting] | [FanSetting, FanSetting, FanSetting]
The matching codec might look like this:
decode(payload: Buffer): FanSetting[] { const reader = SmartBuffer.fromBuffer(payload) const fanConfigs: FanSetting[] = [] while (reader.remaining() > 0) { const fan: FanSetting = { rpm: reader.readUInt8(), setpoint: reader.readUInt8(), state: reader.readUInt8() === 0x01 ? true : false, } fanConfigs.push(fan) } return fanConfigs}
Handling dynamic arrays is covered in the Array example linked below, but the basic case is just accessing a hard-coded element.
If you attempt to access a non-existent 5th element of the array, the IDE will warn with a red underline and a type error is thrown at runtime.
<Printer accessor={state => state[MSGID.FAN][0].state}/>
The never
type
The never
type is used for any messageID which we know 'exists', but shouldn't be supported at all.
Unfortunately, the IDE completion isn't able to red-underline these, but it will throw a run-time error if it's encountered.