diff options
author | xengineering <mail2xengineering@protonmail.com> | 2021-06-13 12:54:28 +0200 |
---|---|---|
committer | xengineering <mail2xengineering@protonmail.com> | 2021-06-13 13:01:26 +0200 |
commit | 9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55 (patch) | |
tree | e3856fbbd1bc7c429472d950e7f1d46e5cf2fae3 | |
parent | c015034ba2e0bfd8464ae444792552a4b354eb0d (diff) | |
download | birdscan-9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55.tar birdscan-9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55.tar.zst birdscan-9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55.zip |
Implement working State Machine
-rw-r--r-- | src/main.go | 25 | ||||
-rw-r--r-- | src/state.go | 94 | ||||
-rw-r--r-- | src/web.go | 17 |
3 files changed, 128 insertions, 8 deletions
diff --git a/src/main.go b/src/main.go index dfcf706..af82e8f 100644 --- a/src/main.go +++ b/src/main.go @@ -5,25 +5,40 @@ package main import ( "flag" "log" - "time" "os" "io/ioutil" "encoding/json" ) +var ( + camera Machine +) + type config struct { WebConfig webConfig `json:"webserver"` } func main() { + + // read command line arguments configPath := readFlags() + + // set up log and print startup message log.SetFlags(0) // disable timestamp because systemd takes care of that log.Println("Starting birdscan") + + // read config file cfg := readConfig(configPath) - go runServer(&cfg.WebConfig) - for { - time.Sleep(1 * time.Second) - } + + // setup camera state machine + camera = newCamStateMachine() + + // start goroutines + server := NewWebServer(&cfg.WebConfig) + go server.run() // http server + + // run camera state machine + camera.run() } func readFlags() string { diff --git a/src/state.go b/src/state.go new file mode 100644 index 0000000..9c6ab66 --- /dev/null +++ b/src/state.go @@ -0,0 +1,94 @@ +// 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 +} + +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) 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) { + if 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), + } +} + @@ -15,12 +15,22 @@ const ( APP_DATA = "/usr/share/birdscan" ) +type WebServer struct { + config *webConfig +} + type webConfig struct { BindAddress string `json:"bind_address"` BindPort string `json:"bind_port"` } -func runServer(cfg *webConfig) { +func NewWebServer(cfg *webConfig) WebServer { + server := WebServer{} + server.config = cfg + return server +} + +func (server *WebServer) run() { router := chi.NewRouter() router.Use(middleware.Logger) @@ -29,8 +39,8 @@ func runServer(cfg *webConfig) { router.Get("/js/birdscan.js", jsHandler) router.Post("/api/single_picture", singlePictureHandler) - log.Println("Binding to 'http://" + cfg.BindAddress + ":" + cfg.BindPort + "'") - log.Fatal(http.ListenAndServe(cfg.BindAddress + ":" + cfg.BindPort, router)) + log.Println("Binding to 'http://" + server.config.BindAddress + ":" + server.config.BindPort + "'") + log.Fatal(http.ListenAndServe(server.config.BindAddress + ":" + server.config.BindPort, router)) } func indexHandler(w http.ResponseWriter, r *http.Request) { @@ -47,6 +57,7 @@ func jsHandler(w http.ResponseWriter, r *http.Request) { func singlePictureHandler(w http.ResponseWriter, r *http.Request) { log.Println("Received request for a single picture") + camera.SendEvent("take_single_picture") fmt.Fprintf(w, http.StatusText(http.StatusOK)) } |