mirror of
https://github.com/cedricve/go-onvif.git
synced 2025-09-27 12:52:15 +08:00
Initial commit
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
16
README.md
Normal file
16
README.md
Normal file
@@ -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.
|
163
discovery.go
Normal file
163
discovery.go
Normal file
@@ -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 := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<s:Envelope
|
||||||
|
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
|
||||||
|
xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||||
|
<s:Header>
|
||||||
|
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action>
|
||||||
|
<a:MessageID>` + messageID + `</a:MessageID>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<a:To s:mustUnderstand="1">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To>
|
||||||
|
</s:Header>
|
||||||
|
<s:Body>
|
||||||
|
<Probe
|
||||||
|
xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery">
|
||||||
|
<d:Types
|
||||||
|
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
|
||||||
|
xmlns:dp0="http://www.onvif.org/ver10/network/wsdl">dp0:NetworkVideoTransmitter
|
||||||
|
</d:Types>
|
||||||
|
</Probe>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>`
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
Reference in New Issue
Block a user