summaryrefslogtreecommitdiff
path: root/src/state.go
blob: 9a08738cb31bf598f12b2be59e1b4e2d3d9c252b (plain)
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),
	}
}