// vim: shiftwidth=4 tabstop=4 noexpandtab package main import ( "log" "time" ) // This struct represents the statemachine. 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 } // This will run the statemachine (blocking). func (m *Machine) Run() { var event string for { event = <-m.api // read api (blocking) m.processEvent(event) } } // A simple Getter to retrieve the current state. func (m Machine) GetState() string { if m.current == "" { return m.initial } return m.current } // This function blocks until the current state differs // from the 'known' state. It then returns the current // state. func (m *Machine) GetStateOnChange(known string) string { listener := make(chan string) var current string m.registerListener(&listener) defer m.deregisterListener(&listener) for { if m.GetState() != known { current = m.GetState() break } current = <-listener if current != known { break } } return current } // Use this function to send an event to the statemachine. func (m *Machine) SendEvent(event string) { m.api <- event // blocks until m.run() goroutine handles this event } 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 } // In this function one can implement callback functions // which will be triggered on certain transitions. func (m *Machine) runHooks(last string, next string) { for _,listener := range(m.state_listeners) { *listener <- next } 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") }() } }