Just had a few spare minutes to adapt the Door Lock System to use enum-style coding for events, states, outputs and ouput_signals. Also brought out the four outputs to separate ports.
[{"id":"49fe1f86ac046e65","type":"function","z":"bda7a9600da379e9","name":"FSM with enum-style for events and outputs","func":"// =========================\n// === ENUM DEFINITIONS ===\n// =========================\nconst States = Object.freeze({\n LOCKED: \"LOCKED\",\n UNLOCKED: \"UNLOCKED\",\n ALARMED: \"ALARMED\"\n});\n\nconst Events = Object.freeze({\n ENTER_CODE: \"ENTER_CODE\",\n LOCK: \"LOCK\",\n FORCE_ENTRY: \"FORCE_ENTRY\",\n RESET_ALARM: \"RESET_ALARM\"\n});\n\nconst Outputs = Object.freeze({\n LOCKED_MSG: \"Door is now LOCKED\",\n UNLOCKED_MSG: \"Door is now UNLOCKED\",\n ALARMED_MSG: \"ALARM! Forced entry detected\",\n NO_CHANGE: \"No transition\"\n});\n\n// ===============================\n// === OUTPUT VALUES PER STATE ===\n// ===============================\nconst OutputSignals = Object.freeze({\n [States.LOCKED]: Object.freeze({ door_lock: 1, solenoid: 0, led: \"red\" }),\n [States.UNLOCKED]: Object.freeze({ door_lock: 0, solenoid: 1, led: \"green\" }),\n [States.ALARMED]: Object.freeze({ door_lock: 1, solenoid: 0, led: \"flashing_red\" })\n});\n\n// ===============================\n// === TRANSITION TABLE FORMAT ===\n// ===============================\nconst transitions = {\n [States.LOCKED]: {\n [Events.ENTER_CODE]: (msg) =>\n msg.code === correctCode\n ? { nextState: States.UNLOCKED, output: Outputs.UNLOCKED_MSG }\n : { nextState: States.LOCKED, output: Outputs.NO_CHANGE },\n [Events.FORCE_ENTRY]: () =>\n ({ nextState: States.ALARMED, output: Outputs.ALARMED_MSG })\n },\n\n [States.UNLOCKED]: {\n [Events.LOCK]: () =>\n ({ nextState: States.LOCKED, output: Outputs.LOCKED_MSG }),\n [Events.FORCE_ENTRY]: () =>\n ({ nextState: States.ALARMED, output: Outputs.ALARMED_MSG })\n },\n\n [States.ALARMED]: {\n [Events.RESET_ALARM]: () =>\n ({ nextState: States.LOCKED, output: Outputs.LOCKED_MSG })\n }\n};\n\n// =====================================\n// === GET CURRENT STATE FROM CONTEXT ===\n// =====================================\nlet currentState = context.get(\"state\") || States.LOCKED;\n\n// ==========================\n// === EXTRACT EVENT INPUT ===\n// ==========================\nlet event = msg.payload;\nif (typeof event === \"object\") {\n event = event.event;\n msg.code = msg.payload.code;\n}\n\n// Simulated correct code\nconst correctCode = \"1234\";\n\n// =====================================\n// === FSM TRANSITION LOGIC ===\n// =====================================\nlet result = transitions[currentState]?.[event]?.(msg);\nif (result) {\n currentState = result.nextState;\n} else {\n result = {\n nextState: currentState,\n output: Outputs.NO_CHANGE\n };\n}\n\n// Save state and update status\ncontext.set(\"state\", currentState);\nnode.status({ text: currentState });\n\n// ============================\n// === CREATE OUTPUT MESSAGES ===\n// ============================\nconst signal = OutputSignals[currentState];\n\nconst msg1 = { payload: signal.door_lock }; // door_lock output\nconst msg2 = { payload: signal.solenoid }; // solenoid output\nconst msg3 = { payload: signal.led }; // LED output\nconst msg4 = { // state_info output\n payload: {\n state: currentState,\n message: result.output\n }\n};\n\nreturn [msg1, msg2, msg3, msg4];\n\n","outputs":4,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":1020,"wires":[["b3cde1bcdc1090bf"],["cb7209dad7722f12"],["dbc7bacb1b54e030"],["34427a6c26214076"]]},{"id":"b3cde1bcdc1090bf","type":"debug","z":"bda7a9600da379e9","name":"door_lock","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":960,"wires":[]},{"id":"cb7209dad7722f12","type":"debug","z":"bda7a9600da379e9","name":"solenoid","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1000,"wires":[]},{"id":"dbc7bacb1b54e030","type":"debug","z":"bda7a9600da379e9","name":"led","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":930,"y":1040,"wires":[]},{"id":"e3e12730f4902d43","type":"inject","z":"bda7a9600da379e9","name":"Initally set door to LOCKED state","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"LOCKED","payloadType":"str","x":300,"y":880,"wires":[["49fe1f86ac046e65"]]},{"id":"34427a6c26214076","type":"debug","z":"bda7a9600da379e9","name":"system message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":970,"y":1080,"wires":[]},{"id":"92473381988995f2","type":"comment","z":"bda7a9600da379e9","name":"FSM with enum-style coding for states, events, outputs and output signals.","info":"","x":760,"y":920,"wires":[]},{"id":"560d450b17d9cccb","type":"group","z":"bda7a9600da379e9","name":"","style":{"fill":"#e3f3d3","label":true},"nodes":["a732bf62d25fc6d5","8f99d0e505064c3c","b18e34c862e100f3","ff39c2b3479cf250","d2bce1b0083cfed4","d6507a2d52ddfdcf"],"x":154,"y":919,"w":272,"h":302},{"id":"a732bf62d25fc6d5","type":"inject","z":"bda7a9600da379e9","g":"560d450b17d9cccb","name":"UNLOCK correct code","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"event\":\"ENTER_CODE\",\"code\":\"1234\"}","payloadType":"json","x":300,"y":1000,"wires":[["49fe1f86ac046e65"]]},{"id":"8f99d0e505064c3c","type":"inject","z":"bda7a9600da379e9","g":"560d450b17d9cccb","name":"LOCK","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"LOCK","payloadType":"str","x":250,"y":1080,"wires":[["49fe1f86ac046e65"]]},{"id":"b18e34c862e100f3","type":"inject","z":"bda7a9600da379e9","g":"560d450b17d9cccb","name":"RESET_ALARM","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"RESET_ALARM","payloadType":"str","x":280,"y":1180,"wires":[["49fe1f86ac046e65"]]},{"id":"ff39c2b3479cf250","type":"comment","z":"bda7a9600da379e9","g":"560d450b17d9cccb","name":"Simulated inputs","info":"","x":260,"y":960,"wires":[]},{"id":"d2bce1b0083cfed4","type":"inject","z":"bda7a9600da379e9","g":"560d450b17d9cccb","name":"FORCE_ENTRY","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"FORCE_ENTRY","payloadType":"str","x":280,"y":1140,"wires":[["49fe1f86ac046e65"]]},{"id":"d6507a2d52ddfdcf","type":"inject","z":"bda7a9600da379e9","g":"560d450b17d9cccb","name":"UNLOCK incorrect code","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"event\":\"ENTER_CODE\",\"code\":\"1233\"}","payloadType":"json","x":300,"y":1040,"wires":[["49fe1f86ac046e65"]]}]
Note: The nested objects have been made fully immutable using Object.freeze()
.
Output Port |
Label |
Example Payload |
Output 1 |
door_lock |
1 or 0 |
Output 2 |
solenoid |
1 or 0 |
Output 3 |
LED |
red, green, flashing_red |
Output 4 |
state_info |
{ state: LOCKED, message: Door is now LOCKED } |
Note: The script will accept a command as msg.payload string or an object as examples below...
Action |
Payload Format |
When Allowed |
Unlock Door |
{ event: ENTER_CODE, code: 1234 } |
When state == LOCKED |
Lock Door |
LOCK or { event: LOCK } |
When state == UNLOCKED |
Force Entry |
FORCE_ENTRY or { event: FORCE_ENTRY } |
When state == LOCKED or UNLOCKED |
Reset Alarm |
RESET_ALARM or { event: RESET_ALARM } |
When state == ALARMED |