From ad16e1ba9e5b76d80ee8f6bea014fa2e39f476c9 Mon Sep 17 00:00:00 2001 From: Radhi Fadlillah Date: Mon, 15 May 2017 11:04:02 +0700 Subject: [PATCH] Initial commit --- LICENSE | 21 +++++++ README.md | 16 +++++ discovery.go | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++ model.go | 10 ++++ 4 files changed, 210 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 discovery.go create mode 100644 model.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2311942 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Muhammad Radhi Fadlillah + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f1dfa8 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Go-ONVIF + +Go-ONVIF is a Go package for communicating with network camera which supports the [ONVIF](http://www.onvif.org/) specifications. ONVIF (Open Network Video Interface) is an open industry forum promoting and developing global standards for interfaces of IP-based physical security products such as network cameras. Recently, almost all network cameras support ONVIF specifications, especially network camera that made in China, which usually can bought with cheap price. + +## Progress + +This package is still in develoment following [guide](https://www.onvif.org/wp-content/uploads/2016/12/ONVIF_WG-APG-Application_Programmers_Guide-1.pdf) from ONVIF, with several features already available : + +- [X] Camera discovery + - [X] Send discovery request + - [X] Read discovery response +- [ ] Initial camera setup and administration + +## License + +Go-ONVIF is distributed using [MIT](http://choosealicense.com/licenses/mit/) license, which means you can use it however you want as long as you preserve copyright and license notices of this package. \ No newline at end of file diff --git a/discovery.go b/discovery.go new file mode 100644 index 0000000..97f8c3b --- /dev/null +++ b/discovery.go @@ -0,0 +1,163 @@ +package onvif + +import ( + "errors" + "net" + "regexp" + "strings" + "time" + + "github.com/clbanning/mxj" + "github.com/satori/go.uuid" +) + +var errWrongDiscoveryResponse = errors.New("Response is not related to discovery request") + +// StartDiscovery send a WS-Discovery message and wait for all matching device to respond +func StartDiscovery() ([]Device, error) { + // Create initial discovery results + discoveryResults := []Device{} + + // Create WS-Discovery message + messageID := "uuid:" + uuid.NewV4().String() + message := ` + + + http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe + ` + messageID + ` + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + urn:schemas-xmlsoap-org:ws:2005:04:discovery + + + + dp0:NetworkVideoTransmitter + + + + ` + + // Clean WS-Discovery message + message = regexp.MustCompile(`\>\s+\<`).ReplaceAllString(message, "><") + message = regexp.MustCompile(`\s+`).ReplaceAllString(message, " ") + + // Create UDP address for local and multicast address + localAddress, err := net.ResolveUDPAddr("udp4", ":0") + if err != nil { + return discoveryResults, err + } + + multicastAddress, err := net.ResolveUDPAddr("udp4", "239.255.255.250:3702") + if err != nil { + return discoveryResults, err + } + + // Create UDP connection to listen for respond from matching device + conn, err := net.ListenUDP("udp", localAddress) + if err != nil { + return discoveryResults, err + } + defer conn.Close() + + // Set connection's timeout + err = conn.SetDeadline(time.Now().Add(1 * time.Second)) + if err != nil { + return discoveryResults, err + } + + // Send WS-Discovery request to multicast address + _, err = conn.WriteToUDP([]byte(message), multicastAddress) + if err != nil { + return discoveryResults, err + } + + // Keep reading UDP message until timeout + for { + // Create buffer and receive UDP response + buffer := make([]byte, 10*1024) + _, _, err = conn.ReadFromUDP(buffer) + + // Check if connection timeout + if err != nil { + if udpErr, ok := err.(net.Error); ok && udpErr.Timeout() { + break + } else { + return discoveryResults, err + } + } + + // Read and parse WS-Discovery response + device, err := readDiscoveryResponse(messageID, buffer) + if err != nil && err != errWrongDiscoveryResponse { + return discoveryResults, err + } + + // Push device to results + discoveryResults = append(discoveryResults, device) + } + + return discoveryResults, nil +} + +// readDiscoveryResponse reads and parses WS-Discovery response +func readDiscoveryResponse(messageID string, buffer []byte) (Device, error) { + // Inital result + result := Device{} + + // Parse XML to map + mapXML, err := mxj.NewMapXml(buffer) + if err != nil { + return result, err + } + + // Check if this response is for our request + responseMessageID, err := mapXML.ValueForPathString("Envelope.Header.RelatesTo") + if err != nil { + return result, err + } + + if responseMessageID != messageID { + return result, errWrongDiscoveryResponse + } + + // Get device's ID and clean it + deviceID, err := mapXML.ValueForPathString("Envelope.Body.ProbeMatches.ProbeMatch.EndpointReference.Address") + if err != nil { + return result, err + } + deviceID = strings.Replace(deviceID, "urn:uuid", "", 1) + + // Get device's name + scopes, err := mapXML.ValueForPathString("Envelope.Body.ProbeMatches.ProbeMatch.Scopes") + if err != nil { + return result, err + } + + deviceName := "" + for _, scope := range strings.Split(scopes, " ") { + if strings.HasPrefix(scope, "onvif://www.onvif.org/name/") { + deviceName = strings.Replace(scope, "onvif://www.onvif.org/name/", "", 1) + deviceName = strings.Replace(deviceName, "_", " ", -1) + break + } + } + + // Get device's XAddrs + xAddrs, err := mapXML.ValueForPathString("Envelope.Body.ProbeMatches.ProbeMatch.XAddrs") + if err != nil { + return result, err + } + + // Finalize result + result.ID = deviceID + result.Name = deviceName + result.XAddrs = strings.Split(xAddrs, " ") + + return result, nil +} diff --git a/model.go b/model.go new file mode 100644 index 0000000..9cb43f1 --- /dev/null +++ b/model.go @@ -0,0 +1,10 @@ +package onvif + +// Device contains data of Onvif device +type Device struct { + ID string + Name string + XAddrs []string + User string + Password string +}