// 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), } }