|
1 | 1 | package myapp |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "fmt" |
5 | 4 | "time" |
6 | 5 |
|
7 | 6 | "ergo.services/ergo/act" |
8 | 7 | "ergo.services/ergo/gen" |
9 | 8 | ) |
10 | 9 |
|
11 | | -// Order with the states: new, processing, shipped, delivered, canceled |
| 10 | +// Code Lock, inspired by https://www.erlang.org/doc/system/statem.html#example-revisited |
12 | 11 |
|
13 | | -type OrderData struct { |
14 | | - items []string |
15 | | - processed time.Time |
16 | | - shipped time.Time |
17 | | - delivered time.Time |
18 | | - canceled time.Time |
| 12 | +type CodeLockData struct { |
| 13 | + buttons []int |
| 14 | + code [4]int |
19 | 15 | } |
20 | 16 |
|
21 | | -type Order struct { |
22 | | - act.StateMachine[OrderData] |
| 17 | +type CodeLock struct { |
| 18 | + act.StateMachine[CodeLockData] |
23 | 19 | } |
24 | 20 |
|
25 | | -func factoryOrder() gen.ProcessBehavior { |
26 | | - return &Order{} |
| 21 | +func factoryCodeLock() gen.ProcessBehavior { |
| 22 | + return &CodeLock{} |
27 | 23 | } |
28 | 24 |
|
29 | | -func (order *Order) Init(args ...any) (act.StateMachineSpec[OrderData], error) { |
30 | | - spec := act.NewStateMachineSpec(gen.Atom("new"), |
31 | | - // new |
32 | | - act.WithStateCallback(gen.Atom("new"), process), |
33 | | - act.WithStateCallback(gen.Atom("new"), cancel), |
34 | | - // processing |
35 | | - act.WithStateCallback(gen.Atom("processing"), ship), |
36 | | - act.WithStateCallback(gen.Atom("processing"), cancel), |
37 | | - // shipped |
38 | | - act.WithStateCallback(gen.Atom("shipped"), deliver), |
| 25 | +type ButtonPress struct { |
| 26 | + Button int |
| 27 | +} |
| 28 | + |
| 29 | +type Lock struct{} |
| 30 | + |
| 31 | +type ResetCode struct{} |
| 32 | + |
| 33 | +type CodeLength struct{} |
| 34 | + |
| 35 | +func (order *CodeLock) Init(args ...any) (act.StateMachineSpec[CodeLockData], error) { |
| 36 | + spec := act.NewStateMachineSpec( |
| 37 | + // The initial state. |
| 38 | + gen.Atom("locked"), |
| 39 | + |
| 40 | + // The initial data. |
| 41 | + act.WithData(CodeLockData{}), |
| 42 | + |
| 43 | + // Register a function that is called on every state change. |
| 44 | + act.WithStateEnterCallback(enterState), |
| 45 | + |
| 46 | + // Register a handler for the ButtonPress message |
| 47 | + act.WithStateMessageHandler(gen.Atom("open"), handleButtonPress), |
| 48 | + |
| 49 | + // Register the handler for the Lock message. |
| 50 | + act.WithStateMessageHandler(gen.Atom("open"), handleLock), |
| 51 | + |
| 52 | + // Register the handler for the ResetCode message. |
| 53 | + act.WithStateMessageHandler(gen.Atom("open"), handleResetCode), |
| 54 | + |
| 55 | + // Register handler for retrieving the length of the code (it would |
| 56 | + // be nice to have an all-state handler for this) |
| 57 | + act.WithStateCallHandler(gen.Atom("open"), handleCodeLength), |
| 58 | + act.WithStateCallHandler(gen.Atom("locked"), handleCodeLength), |
39 | 59 | ) |
40 | 60 |
|
41 | 61 | return spec, nil |
42 | 62 | } |
43 | 63 |
|
44 | | -type Process struct{} |
45 | | - |
46 | | -type Ship struct { |
47 | | - priority bool |
| 64 | +func enterState(oldState gen.Atom, newState gen.Atom, data CodeLockData, proc gen.Process) (gen.Atom, CodeLockData, error) { |
| 65 | + proc.Log().Info("state changed to %s", newState) |
| 66 | + return newState, data, nil |
48 | 67 | } |
49 | 68 |
|
50 | | -type Deliver struct{} |
| 69 | +func handleButtonPress(state gen.Atom, data CodeLockData, msg ButtonPress, proc gen.Process) (gen.Atom, CodeLockData, []act.Action, error) { |
| 70 | + data.buttons = append(data.buttons, msg.Button) |
| 71 | + if len(data.buttons) < len(data.code) { |
| 72 | + // code incomplete |
| 73 | + // reset code after 30 seconds of inactivity |
| 74 | + reset := act.MessageTimeout{ |
| 75 | + Duration: 30 * time.Second, |
| 76 | + Message: ResetCode{}, |
| 77 | + } |
| 78 | + return state, data, []act.Action{reset}, nil |
| 79 | + } |
| 80 | + for i, b := range data.buttons { |
| 81 | + if b != data.code[i] { |
| 82 | + // incorrect code |
| 83 | + // reset buttons |
| 84 | + data.buttons = nil |
| 85 | + return state, data, nil, nil |
| 86 | + } |
| 87 | + } |
| 88 | + // code is correct |
| 89 | + // reset buttons |
| 90 | + // automatically lock after 10 seconds |
| 91 | + data.buttons = nil |
| 92 | + lockAfter10Seconds := act.StateTimeout{ |
| 93 | + Duration: 10 * time.Second, |
| 94 | + Message: Lock{}, |
| 95 | + } |
| 96 | + return gen.Atom("open"), data, []act.Action{lockAfter10Seconds}, nil |
| 97 | +} |
51 | 98 |
|
52 | | -type Cancel struct { |
53 | | - reason string |
| 99 | +func handleLock(state gen.Atom, data CodeLockData, msg Lock, proc gen.Process) (gen.Atom, CodeLockData, []act.Action, error) { |
| 100 | + doLock() |
| 101 | + return gen.Atom("locked"), data, nil, nil |
54 | 102 | } |
55 | 103 |
|
56 | | -func process(sm *act.StateMachine[OrderData], message Process) error { |
57 | | - data := sm.Data() |
58 | | - if len(data.items) < 1 { |
59 | | - return fmt.Errorf("can't process order as there are no items added yet") |
60 | | - } |
61 | | - sm.Log().Info("processing order...") |
62 | | - data.processed = time.Now() |
63 | | - sm.SetData(data) |
64 | | - sm.SetCurrentState(gen.Atom("processing")) |
65 | | - return nil |
| 104 | +func handleResetCode(state gen.Atom, data CodeLockData, msg ResetCode, proc gen.Process) (gen.Atom, CodeLockData, []act.Action, error) { |
| 105 | + data.buttons = nil |
| 106 | + return state, data, nil, nil |
66 | 107 | } |
67 | 108 |
|
68 | | -func ship(sm *act.StateMachine[OrderData], message Ship) error { |
69 | | - data := sm.Data() |
70 | | - sm.Log().Info("shiping order...") |
71 | | - data.shipped = time.Now() |
72 | | - sm.SetData(data) |
73 | | - sm.SetCurrentState(gen.Atom("shipped")) |
74 | | - return nil |
| 109 | +func handleCodeLength(state gen.Atom, data CodeLockData, msg Lock, proc gen.Process) (gen.Atom, CodeLockData, int, []act.Action, error) { |
| 110 | + doLock() |
| 111 | + return state, data, len(data.code), nil, nil |
75 | 112 | } |
76 | 113 |
|
77 | | -func deliver(sm *act.StateMachine[OrderData], message Deliver) error { |
78 | | - data := sm.Data() |
79 | | - sm.Log().Info("delivering order...") |
80 | | - data.delivered = time.Now() |
81 | | - sm.SetData(data) |
82 | | - sm.SetCurrentState(gen.Atom("delivered")) |
83 | | - return nil |
| 114 | +func doLock() { |
| 115 | + // interact with the hardware |
84 | 116 | } |
85 | 117 |
|
86 | | -func cancel(sm *act.StateMachine[OrderData], message Cancel) error { |
87 | | - data := sm.Data() |
88 | | - sm.Log().Info("canceling order...") |
89 | | - data.canceled = time.Now() |
90 | | - sm.SetData(data) |
91 | | - sm.SetCurrentState(gen.Atom("canceled")) |
92 | | - return nil |
| 118 | +func doUnlock() { |
| 119 | + // interact with the hardware |
93 | 120 | } |
0 commit comments