Lab
State machine
type Phase = 'available' | 'plugged' | 'charging' | 'faulted';
type ConnectorEvent =
| { type: 'plug' | 'unplug' | 'start' | 'stop' | 'fault' | 'reset' }
| { type: 'meter'; wh: number };
type Connector = { plugged: boolean; charging: boolean; faulted: boolean; energy: number };
type Decision = { connector: Connector; reason: string; effect: string };
const newConnector = (): Connector =>
({ plugged: false, charging: false, faulted: false, energy: 0 });
// The phase the station's screen shows, worked out from the flags.
function phaseOf(c: Connector): Phase {
if (c.faulted) return 'faulted';
if (c.charging) return 'charging';
return c.plugged ? 'plugged' : 'available';
}
// What the station's log writes after each event. The checker prints it.
const describe = (c: Connector) => `${phaseOf(c)} ${c.energy}Wh`;
const accept = (c: Connector, effect = 'none'): Decision =>
({ connector: c, reason: 'accepted', effect });
const refuse = (c: Connector): Decision =>
({ connector: c, reason: 'not-allowed', effect: 'none' });
// Each event sets the flags it knows about.
function transition(c: Connector, event: ConnectorEvent): Decision {
switch (event.type) {
case 'plug':
return accept({ ...c, plugged: true });
case 'unplug': {
const effect = c.charging ? 'open-relay' : 'none';
return accept({ ...c, plugged: false, charging: false }, effect);
}
case 'start':
if (!c.plugged) return refuse(c);
return accept({ ...c, charging: true, energy: 0 }, 'close-relay');
case 'stop':
if (!c.charging) return refuse(c);
return accept({ ...c, charging: false }, 'open-relay');
case 'meter':
return accept({ ...c, energy: c.energy + event.wh });
case 'fault': {
const effect = c.charging ? 'open-relay' : 'none';
return accept({ ...c, faulted: true, charging: false }, effect);
}
case 'reset':
return accept({ ...c, faulted: false });
}
}