From d54730323f57a021679d353a9987d83622e78669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20K=C3=B6ritz?= Date: Wed, 28 Aug 2019 18:00:57 +0200 Subject: [PATCH] Added waiting for response to recording commands --- Camera.go | 47 +++++++++++- camera.go | 212 ---------------------------------------------------- listener.go | 106 -------------------------- main.go | 169 ----------------------------------------- 4 files changed, 43 insertions(+), 491 deletions(-) delete mode 100644 camera.go delete mode 100644 listener.go delete mode 100644 main.go diff --git a/Camera.go b/Camera.go index 0b9de53..e529adf 100644 --- a/Camera.go +++ b/Camera.go @@ -39,6 +39,7 @@ const ( TAKE_PICTURE = 0xA038 PICTURE_SAVED = 0xA039 CONTROL_RECORDING = 0xA03A + RECORD_COMMAND_ACCEPT = 0xA03B ) const ( @@ -331,8 +332,27 @@ func (c *Camera) StartRecording() error { if !c.isLoggedIn { return errors.New("Camera Login required") } - c.Log("Starting to record video") - return c.SendPacket(CreatePacket(CreateCommandHeader(CONTROL_RECORDING), []byte{0x01, 0x00, 0x00, 0x00})) + + recordCommandAccept := make(chan bool, 1) + + c.Handle(RECORD_COMMAND_ACCEPT, func(c *Camera, m *Message) (bool, error) { + c.Log("Started to record video") + recordCommandAccept <- true + return RemoveHandler, nil + }) + + c.Log("Requesting camera to start recording") + err := c.SendPacket(CreatePacket(CreateCommandHeader(CONTROL_RECORDING), []byte{0x01, 0x00, 0x00, 0x00})) + if err != nil { + return err + } + + select { + case <-recordCommandAccept: + return nil + case <-time.After(5 * time.Second): + return errors.New("CONTROL_RECORDING request timed out") + } } // StopRecording stops recording video to SD-Card @@ -340,8 +360,27 @@ func (c *Camera) StopRecording() error { if !c.isLoggedIn { return errors.New("Camera Login required") } - c.Log("Stopping to record video") - return c.SendPacket(CreatePacket(CreateCommandHeader(CONTROL_RECORDING), []byte{0x00, 0x00, 0x00, 0x00})) + + recordCommandAccept := make(chan bool, 1) + + c.Handle(RECORD_COMMAND_ACCEPT, func(c *Camera, m *Message) (bool, error) { + c.Log("Stopping to record video") + recordCommandAccept <- true + return RemoveHandler, nil + }) + + c.Log("Requesting camera to stop recording") + err := c.SendPacket(CreatePacket(CreateCommandHeader(CONTROL_RECORDING), []byte{0x00, 0x00, 0x00, 0x00})) + if err != nil { + return err + } + + select { + case <-recordCommandAccept: + return nil + case <-time.After(5 * time.Second): + return errors.New("CONTROL_RECORDING request timed out") + } } // Disconnect from the camera diff --git a/camera.go b/camera.go deleted file mode 100644 index 582bdcb..0000000 --- a/camera.go +++ /dev/null @@ -1,212 +0,0 @@ -package main - -import ( - "encoding/binary" - "encoding/hex" - "fmt" - "io" - "log" - "net" - "strconv" - "strings" -) - -// Camera contains all information and features on a single IP Camera -type Camera struct { - IPAddress string - Port int - connected bool - disconnect bool - Verbose bool - connection net.Conn - receivedMessages chan Header - StoredFiles []StoredFile - fileList string -} - -// StoredFile is a file stored on the cameras sd-card -type StoredFile struct { - Path string - Size uint64 -} - -// Connect to the camera and start responding to keepalive packets -func (c *Camera) Connect(username, password string) { - if c.Verbose { - log.Printf("Connecting to %s:%d using username=%s, password=%s\n", c.IPAddress, c.Port, username, password) - } - conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.IPAddress, c.Port)) - c.StoredFiles = make([]StoredFile, 0) - - if err != nil { - log.Printf("ERROR: %s\n", err) - } - c.receivedMessages = make(chan Header, 0) - c.connection = conn - - go cameraMessageHandler(c, conn) - - login(c, conn, username, password) -} - -func cameraMessageHandler(c *Camera, conn net.Conn) { - - header := Header{} - var payload []byte - for { - err := binary.Read(conn, binary.BigEndian, &header) - if err != nil { - break - } - - if header.Magic != 0xABCD { - log.Printf("Received message with invalid magic (%x).", header.Magic) - break - } - - //log.Printf("Received Header: %+v\n", header) - - if header.Length > 0 { - payload = make([]byte, header.Length) - bytesRead, err := io.ReadFull(conn, payload) - if err != nil { - log.Printf("Read Error: %s, %d bytes\n", err, bytesRead) - break - } - } else { - payload = []byte{} - } - - switch header.MessageType { - case 0x0111: // Login Accept - if c.Verbose { - log.Printf("Login Accepted") - } - requestFirmwareInfo(conn) - c.connected = true - case 0x0112: // Alive Request - sendAliveResponse(header, conn) - case 0xA026: // List of files - numParts := binary.LittleEndian.Uint32(payload[:4]) - currentPart := binary.LittleEndian.Uint32(payload[4:8]) - - c.fileList += string(payload[8:]) - if currentPart+1 >= numParts { - c.StoredFiles = parseFileList(c.fileList) - for _, file := range c.StoredFiles { - fmt.Printf("%s\t%d\n", file.Path, file.Size) - } - c.receivedMessages <- CreateCommandHeader(0xA027) // Dummy header to represent end of list - } - - case 0xA035: - if c.Verbose { - log.Printf("Received Firmware Info: %s\n", string(payload)) - } - c.StartPreviewStream() - case 0xA039: - if c.Verbose { - log.Printf("Took a still image and saved to SD-Card") - } - default: - log.Printf("Received Unknown Message: %+v\n", header) - log.Printf("Payload:\n%s\n", hex.Dump(payload)) - } - - select { - case c.receivedMessages <- header: - default: - } - - if c.disconnect { - break - } - } - c.connected = false -} - -func sendAliveResponse(request Header, conn net.Conn) { - request.MessageType = 0x0113 // Alive Response - response := CreatePacket(request, []byte{}) - conn.Write(response) -} - -func login(c *Camera, conn net.Conn, username, password string) { - login := CreateLoginPacket(username, password) - conn.Write(login) -} - -// RequestFileList instructs the camera to send a list of files from the camera -func (c *Camera) RequestFileList() { - c.fileList = "" - header := CreateCommandHeader(0xA025) - request := CreatePacket(header, []byte{0x01, 0x00, 0x00, 0x00}) - c.connection.Write(request) -} - -func parseFileList(input string) []StoredFile { - files := strings.Split(input, ";") - stored := make([]StoredFile, len(files)-1) - for i, file := range files { - parts := strings.Split(file, ":") - if len(parts) == 2 { - size, err := strconv.ParseUint(parts[1], 10, 64) - - if err == nil && size > 0 && len(parts[0]) > 0 { - stored[i] = StoredFile{ - Path: parts[0], - Size: size, - } - } - } - } - return stored -} - -func requestFirmwareInfo(conn net.Conn) { - conn.Write(CreateCommandPacket(0x0000A034)) -} - -// SendPacket sends a raw packet to the camera -func (c *Camera) SendPacket(packet []byte) { - c.connection.Write(packet) -} - -// TakePicture instructs the camera to take a still image -func (c *Camera) TakePicture() { - c.connection.Write(CreateCommandPacket(0x0000A038)) -} - -// StartPreviewStream starts streaming video to this host -func (c *Camera) StartPreviewStream() { - if c.Verbose { - log.Printf("Starting Preview Stream\n") - } - c.connection.Write(CreateCommandPacket(0x000001FF)) -} - -// StartRecording starts recording video to SD-Card -func (c *Camera) StartRecording() { - c.connection.Write(CreatePacket(CreateCommandHeader(0xA03A), []byte{0x01, 0x00, 0x00, 0x00})) -} - -// StopRecording stops recording video to SD-Card -func (c *Camera) StopRecording() { - c.connection.Write(CreatePacket(CreateCommandHeader(0xA03A), []byte{0x00, 0x00, 0x00, 0x00})) -} - -// Disconnect from the camera -func (c *Camera) Disconnect() { - c.disconnect = true - c.connected = false -} - -// WaitForMessage waits for a message of a specific type to arrive -func (c *Camera) WaitForMessage(packetType uint32) { - for { - header := <-c.receivedMessages - if header.MessageType == packetType { - return - } - } -} diff --git a/listener.go b/listener.go deleted file mode 100644 index de42b2f..0000000 --- a/listener.go +++ /dev/null @@ -1,106 +0,0 @@ -package main - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "io" - "log" - "net" -) - -// RTPRelay holds information on the relaying stream listener -type RTPRelay struct { - close bool - targetIP net.IP - targetPort int -} - -// CreateRTPRelay creates a UDP listener that handles live data -// from the camera and forwards it as an RTP stream -func CreateRTPRelay(targetAddress net.IP, targetPort int) RTPRelay { - conn, err := net.ListenPacket("udp", ":6669") - - if err != nil { - log.Printf("ERROR: %s\n", err) - } - - relay := RTPRelay{ - targetIP: targetAddress, - targetPort: targetPort, - } - if err != nil { - log.Printf("ERROR: %s\n", err) - } - - go handleCameraStream(relay, conn) - - return relay -} - -func handleCameraStream(relay RTPRelay, conn net.PacketConn) { - buffer := make([]byte, 2048) - header := StreamHeader{} - var payload []byte - - rtpTarget := net.UDPAddr{ - IP: relay.targetIP, - Port: relay.targetPort, - } - rtpSource, _ := net.ResolveUDPAddr("udp", "127.0.0.1:5000") - rtpConn, _ := net.DialUDP("udp", rtpSource, &rtpTarget) - - var sequenceNumber uint16 - var elapsed uint32 - - frameBuffer := []byte{} - - for { - conn.ReadFrom(buffer) - packetReader := bytes.NewReader(buffer) - binary.Read(packetReader, binary.BigEndian, &header) - - if header.Magic != 0xBCDE { - log.Printf("Received message with invalid magic (%x).", header.Magic) - break - } - if header.Length > 0 { - payload = make([]byte, header.Length) - bytesRead, err := io.ReadFull(packetReader, payload) - if err != nil { - log.Printf("Read Error: %s, %d bytes\n", err, bytesRead) - break - } - } else { - payload = []byte{} - } - - switch header.MessageType { - case 0x0001: // H.264 Data - frameBuffer = append(frameBuffer, payload...) - case 0x0002: // Time - packet := bytes.Buffer{} - packet.Write([]byte{0x80, 0x63}) - binary.Write(&packet, binary.BigEndian, sequenceNumber) - binary.Write(&packet, binary.BigEndian, (uint32)(elapsed*90)) - binary.Write(&packet, binary.BigEndian, (uint64(0))) - packet.Write(frameBuffer) - rtpConn.Write(packet.Bytes()) - frameBuffer = []byte{} - sequenceNumber++ - elapsed = binary.LittleEndian.Uint32(payload[12:]) - //log.Printf("Elapsed: %d (%x)\n", elapsed, payload[12:]) - default: - log.Printf("Received Unknown Message: %+v\n", header) - log.Printf("Payload:\n%s\n", hex.Dump(payload)) - } - if relay.close { - break - } - } -} - -// Stop stops listening for packets -func (r *RTPRelay) Stop() { - r.close = true -} diff --git a/main.go b/main.go deleted file mode 100644 index dffcf45..0000000 --- a/main.go +++ /dev/null @@ -1,169 +0,0 @@ -package main - -import ( - "bufio" - "encoding/binary" - "encoding/hex" - "io" - "log" - "net/http" - "os" - "path/filepath" - - "github.com/jonas-koeritz/libipcamera/ipcamera" - "github.com/spf13/cobra" -) - -func main() { - var username string - var password string - var port int16 - - var rootCmd = &cobra.Command{ - Use: "ipcamera [Cameras IP Address]", - Short: "ipcamera is a tool to stream the video preview of cheap action cameras without the mobile application", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - listener := ipcamera.CreateStreamListener() - camera := ipcamera.Camera{IPAddress: args[0], Port: (int)(port), Verbose: true} - - log.Printf("Using Camera: %+v\n", camera) - - camera.Connect(username, password) - bufio.NewReader(os.Stdin).ReadBytes('\n') - listener.Close() - }, - } - - rootCmd.PersistentFlags().Int16VarP(&port, "port", "P", 6666, "Specify an alternative camera port to connect to") - rootCmd.PersistentFlags().StringVarP(&username, "username", "u", "admin", "Specify the camera username") - rootCmd.PersistentFlags().StringVarP(&password, "password", "p", "12345", "Specify the camera password") - - var ls = &cobra.Command{ - Use: "ls [Cameras IP Address]", - Short: "List files stored on the cameras SD-Card", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - camera := ipcamera.Camera{IPAddress: args[0], Port: (int)(port), Verbose: false} - - camera.Connect(username, password) - camera.RequestFileList() - camera.WaitForMessage(0xA027) - //camera.StoredFiles - }, - } - - var still = &cobra.Command{ - Use: "still [Cameras IP Address]", - Short: "Take a still image and save to SD-Card", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - camera := ipcamera.Camera{IPAddress: args[0], Port: (int)(port), Verbose: true} - camera.Connect(username, password) - camera.TakePicture() - camera.WaitForMessage(0xA039) - }, - } - - var record = &cobra.Command{ - Use: "record [Cameras IP Address]", - Short: "Start recording video to SD-Card", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - camera := ipcamera.Camera{IPAddress: args[0], Port: (int)(port), Verbose: true} - camera.Connect(username, password) - camera.StartRecording() - camera.WaitForMessage(0xA03B) - }, - } - - var stop = &cobra.Command{ - Use: "stop [Cameras IP Address]", - Short: "Stop recording video to SD-Card", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - camera := ipcamera.Camera{IPAddress: args[0], Port: (int)(port), Verbose: true} - camera.Connect(username, password) - camera.StopRecording() - camera.WaitForMessage(0xA03B) - }, - } - - var cmd = &cobra.Command{ - Use: "cmd [RAW Command] [Cameras IP Address]", - Short: "Send a raw command to the camera", - Args: cobra.ExactArgs(2), - Run: func(cmd *cobra.Command, args []string) { - camera := ipcamera.Camera{IPAddress: args[1], Port: (int)(port), Verbose: true} - - camera.Connect(username, password) - command, err := hex.DecodeString(args[0]) - if err != nil { - log.Printf("ERROR: %s\n", err) - return - } - - if len(command) >= 2 { - header := ipcamera.CreateCommandHeader(uint32(binary.BigEndian.Uint16(command[:2]))) - payload := command[2:] - log.Printf("Waiting for Login to finish") - camera.WaitForMessage(0x0111) - packet := ipcamera.CreatePacket(header, payload) - log.Printf("Sending Command: %X\n", packet) - camera.SendPacket(packet) - } - - bufio.NewReader(os.Stdin).ReadBytes('\n') - }, - } - - var fetch = &cobra.Command{ - Use: "fetch [Cameras IP Address]", - Short: "List files stored on the cameras SD-Card", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - camera := ipcamera.Camera{IPAddress: args[0], Port: (int)(port), Verbose: false} - - camera.Connect(username, password) - camera.RequestFileList() - camera.WaitForMessage(0xA027) - newestFile := camera.StoredFiles[len(camera.StoredFiles)-1].Path - url := "http://" + args[0] + newestFile - log.Printf("Downloading latest File: %s\n", url) - downloadFile(filepath.Base(newestFile), url) - }, - } - - rootCmd.AddCommand(ls) - rootCmd.AddCommand(cmd) - rootCmd.AddCommand(still) - rootCmd.AddCommand(stop) - rootCmd.AddCommand(fetch) - rootCmd.AddCommand(record) - - if err := rootCmd.Execute(); err != nil { - log.Println(err) - os.Exit(1) - } -} - -func downloadFile(filepath string, url string) error { - - // Get the data - resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - - // Create the file - out, err := os.Create(filepath) - if err != nil { - return err - } - defer out.Close() - - // Write the body to file - _, err = io.Copy(out, resp.Body) - return err -}