summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxengineering <mail2xengineering@protonmail.com>2021-06-13 12:54:28 +0200
committerxengineering <mail2xengineering@protonmail.com>2021-06-13 13:01:26 +0200
commit9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55 (patch)
treee3856fbbd1bc7c429472d950e7f1d46e5cf2fae3
parentc015034ba2e0bfd8464ae444792552a4b354eb0d (diff)
downloadbirdscan-9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55.tar
birdscan-9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55.tar.zst
birdscan-9d5a2f9870e52bfc0fe6db8c27981f29d91dcb55.zip
Implement working State Machine
-rw-r--r--src/main.go25
-rw-r--r--src/state.go94
-rw-r--r--src/web.go17
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),
+ }
+}
+
diff --git a/src/web.go b/src/web.go
index 23bf1ff..6515f47 100644
--- a/src/web.go
+++ b/src/web.go
@@ -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))
}