1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
// vim: shiftwidth=4 tabstop=4 noexpandtab
package main
import (
"log"
"time"
)
type Machine struct {
name string
initial string
current string
states StateMap
api chan string
state_listeners []*(chan string)
}
type StateMap map[string]MachineState
type MachineState struct {
on TransitionMap
}
type TransitionMap map[string]MachineTransition
type MachineTransition struct {
to string
}
func (m Machine) GetState() string {
if m.current == "" {
return m.initial
}
return m.current
}
func (m *Machine) GetStateOnChange(known string) string {
listener := make(chan string)
var current string
m.RegisterListener(&listener)
for {
if m.GetState() != known {
current = m.GetState()
break
}
current = <-listener
if current != known {
break
}
}
m.DeregisterListener(&listener)
return current
}
func (m *Machine) RegisterListener(listener *(chan string)) {
m.state_listeners = append(m.state_listeners, listener)
}
func (m *Machine) DeregisterListener(listener *(chan string)) {
wasRemoved := false
if len(m.state_listeners) == 0 {
log.Println("Not able to unregister listener from empty state_listener slice")
}
for index,item := range(m.state_listeners) {
if item == listener {
if index == len(m.state_listeners) - 1 {
m.state_listeners = m.state_listeners[: len(m.state_listeners) - 1]
} else {
m.state_listeners[index] = m.state_listeners[len(m.state_listeners) - 1]
m.state_listeners = m.state_listeners[: len(m.state_listeners) - 1]
}
wasRemoved = true
}
}
if !wasRemoved {
log.Println("Could not find listener in slice state_listeners")
}
}
func (m *Machine) processEvent(event string) string {
current := m.GetState()
next := m.states[current].on[event].to
if next != "" {
log.Printf("State machine '%s' changes from '%s' to '%s'\n", m.name, current, next)
m.current = next
m.runHooks(current, next)
return next
}
return current
}
func (m *Machine) SendEvent(event string) {
m.api <- event // blocks until m.run() goroutine handles this event
}
func (m *Machine) run() {
var event string
for {
event = <-m.api // read api (blocking)
m.processEvent(event)
}
}
func (m *Machine) runHooks(last string, next string) {
log.Println("Send state to listeners ...")
for _,listener := range(m.state_listeners) {
*listener <- next
}
log.Println("State sent to listeners")
if last == "idle" && next == "single_picture" {
// TODO implement launch of python subprocess here
go func() {
time.Sleep(1 * time.Second)
m.SendEvent("single_picture_taken")
}()
}
}
func newCamStateMachine() Machine {
return Machine{
name: "camera",
initial: "idle",
states: StateMap{
"idle": MachineState{
on: TransitionMap{
"take_single_picture": MachineTransition{
to: "single_picture",
},
},
},
"single_picture": MachineState{
on: TransitionMap{
"single_picture_taken": MachineTransition{
to: "idle",
},
},
},
},
api: make(chan string),
state_listeners: make([]*(chan string), 0),
}
}
|