diff options
author | xengineering <mail2xengineering@protonmail.com> | 2021-06-19 11:08:57 +0200 |
---|---|---|
committer | xengineering <mail2xengineering@protonmail.com> | 2021-06-20 13:57:58 +0200 |
commit | 963b86e49fad07a3722646b9d7f4b1c09d199eb1 (patch) | |
tree | 5ccb6b66bf9ce1c53e3e97fa7057e44b66512295 | |
parent | 198023be0f62ef6bb35cdd5ae78f90ab0a6f187e (diff) | |
download | birdscan-963b86e49fad07a3722646b9d7f4b1c09d199eb1.tar birdscan-963b86e49fad07a3722646b9d7f4b1c09d199eb1.tar.zst birdscan-963b86e49fad07a3722646b9d7f4b1c09d199eb1.zip |
Implement continuous Python Daemon
-rw-r--r-- | python/birdscan/__main__.py | 53 | ||||
-rw-r--r-- | src/camera.go | 63 | ||||
-rw-r--r-- | src/main.go | 38 | ||||
-rw-r--r-- | src/subprocess.go | 102 | ||||
-rw-r--r-- | src/web.go | 2 |
5 files changed, 177 insertions, 81 deletions
diff --git a/python/birdscan/__main__.py b/python/birdscan/__main__.py index 71ba294..862ec1b 100644 --- a/python/birdscan/__main__.py +++ b/python/birdscan/__main__.py @@ -10,25 +10,58 @@ This is the main executable of the birdscan package. import sys import time +import socket +import argparse def main(): """Main function of this module""" - if len(sys.argv) == 2: # check if argument is given (--debug) - time.sleep(1) - sys.stdout.write("ok\n") - else: + # parse command line args + parser = argparse.ArgumentParser() + parser.add_argument("-d", "--debug", action="store_true", help="For use in development environment") + parser.add_argument("-s", "--socket", default="/dev/null", type=str, help="Unix domain socket path for IPC") + args = parser.parse_args() + + # prepare camera if on production system + if not args.debug: from picamera import PiCamera camera = PiCamera() camera.resolution = "3280x2464" camera.start_preview() - # Camera warm-up time - time.sleep(2) - camera.capture("/var/lib/birdscan/{}.jpg".format( - time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())) - ) - sys.stdout.write("ok\n") + time.sleep(2) # camera warm-up time + + # connect to IPC socket + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(args.socket) + sock.send("picamera\n".encode()) # register this service + + # main loop for Python background service + while True: + cmd = read_line(sock) + if cmd == "single_picture": + if args.debug: + time.sleep(1) + sock.send("single_picture_taken\n".encode()) + else: + camera.capture("/var/lib/birdscan/{}.jpg".format( + time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())) + ) + sock.send("single_picture_taken\n".encode()) + elif cmd == "": + pass + else: + sock.send("unknown_command\n".encode()) + +def read_line(socket): + data = b"" + while True: + byte = socket.recv(1) + if byte == b"\n": + break + else: + data += byte + return data.decode() if __name__ == "__main__": diff --git a/src/camera.go b/src/camera.go index 519cc65..fe1140e 100644 --- a/src/camera.go +++ b/src/camera.go @@ -2,14 +2,6 @@ package main -import ( - "log" - "os/exec" - "os" - "path/filepath" - "bufio" -) - type Camera struct { statemachine Machine } @@ -44,63 +36,10 @@ func NewCamera() Camera { func runCameraHooks(last string, next string, m *Machine) { if last == "idle" && next == "single_picture" { - go singlePicture(m) + ipc.WriteLineTo("picamera", "single_picture\n") } } -func singlePicture(m *Machine) { - - // create command - var cmd *exec.Cmd - if !config.Flag.Debug { - cmd = exec.Command("/usr/bin/python3", "/usr/lib/python3.9/site-packages/birdscan/") - } else { // debug mode - pwd,err := os.Getwd() - if err != nil { - log.Fatal(err) - } - repoDir := filepath.Dir(pwd) - log.Printf("Repository path is assumed to be = '%s'", repoDir) - pythonPackage := repoDir + "/python/birdscan" - cmd = exec.Command("/usr/bin/python3", pythonPackage, "--debug") - } - - // connect stdout of python process - stdout,err := cmd.StdoutPipe() - if err != nil { - log.Print(err) - } - defer stdout.Close() - - // run command - err = cmd.Start() - if err != nil { - log.Print(err) - } - - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - text := scanner.Text() - log.Printf("Python returned '%s'", text) - if text == "ok" { - break - } - } - - err = cmd.Wait() // wait until command execution and io is complete - if err != nil { - log.Print(err) - } - - // process result - m.SendEvent("single_picture_taken") -} - func (cam *Camera) run() { cam.statemachine.Run() } - -// read until '\n' -func readLine(buff *[]byte, ) { - -} diff --git a/src/main.go b/src/main.go index 3218df9..dcb201e 100644 --- a/src/main.go +++ b/src/main.go @@ -4,11 +4,19 @@ package main import ( "log" + "os" + "os/signal" + "syscall" ) var ( config RuntimeConfig camera Camera + ipc IpcServer +) + +const ( + IPC_SOCKET = "/tmp/birdscan.sock" // FIXME ) func main() { @@ -19,13 +27,27 @@ func main() { // parse flags and read config config = GetRuntimeConfig() - // create camera - camera = NewCamera() - - // start goroutines - server := NewWebServer() - go server.run() // http server / user interface - - // run camera + // create services + camera = NewCamera() // main service + ipc = NewIpcServer() // inter process communication + web := NewWebServer() // web frontend + + // allow graceful exit + var listener = make(chan os.Signal) + signal.Notify(listener, syscall.SIGTERM) + signal.Notify(listener, syscall.SIGINT) + go func() { + signal := <-listener + log.Printf("Got signal '%+v'", signal) + ipc.Cleanup() + os.Exit(0) + }() + + // start secondary services + go ipc.Run(IPC_SOCKET) + go PicameraSubprocess(IPC_SOCKET) // subprocess to use Python picamera + go web.run() + + // start main service camera.run() } diff --git a/src/subprocess.go b/src/subprocess.go new file mode 100644 index 0000000..d16d4fe --- /dev/null +++ b/src/subprocess.go @@ -0,0 +1,102 @@ +// vim: shiftwidth=4 tabstop=4 noexpandtab + +package main + +import ( + "log" + "os" + "os/exec" + "path/filepath" + "net" + "net/textproto" + "bufio" +) + +type IpcServer struct { + connections map[string]net.Conn + listener net.Listener +} + +func NewIpcServer() IpcServer { + return IpcServer{ + connections: make(map[string]net.Conn), + } +} + +func (ipc *IpcServer) Run(ipcSocket string) { + log.Println("Running IPC server") + var err error + ipc.listener,err = net.Listen("unix", ipcSocket) + if err != nil { + log.Fatal(err) + } + for { + connection,err := ipc.listener.Accept() + if err != nil { + log.Print(err) + } + bufioreader := bufio.NewReader(connection) + tpreader := textproto.NewReader(bufioreader) + line,err := tpreader.ReadLine() + log.Printf("IPC: '%s'", line) + ipc.connections[line] = connection + go ipc.HandleConnection(line) + } +} + +func (ipc *IpcServer) HandleConnection(connection string) { + for { + msg := ipc.ReadLineFrom(connection) + if msg != "" { + log.Printf("Got IPC message '%s' from '%s'", msg, connection) + if connection == "picamera" { + camera.statemachine.SendEvent(msg) + } + } + } +} + +func (ipc *IpcServer) WriteLineTo(connection string, line string) { + conn := ipc.connections[connection] + _,err := conn.Write([]byte(line)) + if err != nil { + log.Print(err) + } +} + +func (ipc *IpcServer) ReadLineFrom(connection string) string { + conn := ipc.connections[connection] + bufioreader := bufio.NewReader(conn) + tpreader := textproto.NewReader(bufioreader) + line,_ := tpreader.ReadLine() + return line +} + +func (ipc *IpcServer) Cleanup() { + log.Println("Releasing IPC socket ...") + for _,connection := range ipc.connections { + connection.Close() + } + ipc.listener.Close() +} + +func PicameraSubprocess(ipcSocket string) { + var cmd *exec.Cmd + if config.Flag.Debug { + pwd,err := os.Getwd() + if err != nil { + log.Fatal(err) + } + repoDir := filepath.Dir(pwd) + pythonPackage := repoDir + "/python/birdscan" + cmd = exec.Command("python3", pythonPackage, "--debug", "-s", ipcSocket) + } else { + cmd = exec.Command("python3", "/usr/lib/python3.9/site-packages/birdscan/", "-s", ipcSocket) + } + log.Println("Starting picamera service") + err := cmd.Run() + log.Println("picamera service terminated!") + if err != nil { + log.Print(err) + } +} @@ -43,7 +43,7 @@ func (server *WebServer) run() { router.Post("/api/reboot", rebootHandler) router.Post("/api/poweroff", poweroffHandler) - log.Println("Binding to 'http://" + server.config.BindAddress + ":" + server.config.BindPort + "'") + log.Println("Web service binding to 'http://" + server.config.BindAddress + ":" + server.config.BindPort + "'") log.Fatal(http.ListenAndServe(server.config.BindAddress + ":" + server.config.BindPort, router)) } |