Skip to content

Commit

Permalink
feat: improved default config and onvif xml parsing (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickmichalina authored Oct 2, 2019
1 parent d4916b5 commit 4252917
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 30 deletions.
6 changes: 3 additions & 3 deletions src/config/config.default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { DOMParser } from 'xmldom'
export const DEFAULT_CONFIG: IProbeConfig = {
DOM_PARSER: new DOMParser(),
MULTICAST_ADDRESS: '239.255.255.250',
FALLOUT_MS: 5000,
PROBE_SAMPLE_TIME_MS: 0,
PROBE_NETWORK_TIMEOUT_MS: 0,
PROBE_SAMPLE_TIME_MS: 6000,
PROBE_NETWORK_TIMEOUT_MS: 12000,
FALLOUT_MS: 24000,
ONVIF_DEVICES: ['NetworkVideoTransmitter', 'Device', 'NetworkVideoDisplay'],
PORTS: {
UPNP: [1900],
Expand Down
10 changes: 5 additions & 5 deletions src/core/probe.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { map, filter, scan, distinctUntilChanged, takeUntil, mapTo } from 'rxjs/operators'
import { map, filter, scan, distinctUntilChanged, takeUntil, mapTo, tap } from 'rxjs/operators'
import { ISocketStream, socketStream } from '../core/socket-stream'
import { reader, IResult } from 'typescript-monads'
import { interval, Observable } from 'rxjs'
import { interval, Observable, timer } from 'rxjs'
import { IProbeConfig } from '../config/config.interface'
import { Strings, Numbers } from '../core/interfaces'

Expand Down Expand Up @@ -42,12 +42,12 @@ export const probe =
(messagesToSend: Strings = []) =>
(mapFn: (msg: readonly TimestampedMessage[]) => StringDictionary) =>
reader<IProbeConfig, Observable<Strings>>(cfg => {
interval(cfg.PROBE_SAMPLE_TIME_MS).pipe(
timer(0, cfg.PROBE_SAMPLE_TIME_MS).pipe(
mapTo(flattenBuffersWithInfo(ports)(address)(messagesToSend.map(mapStringToBuffer))),
takeUntil(socket.close$))
.subscribe(bfrPorts => bfrPorts.forEach(mdl => socket.socket.send(mdl.buffer, 0, mdl.buffer.length, mdl.port, mdl.address)))
.subscribe(bfrPorts => bfrPorts.forEach(mdl => socket.socket.send(mdl.buffer, 0, mdl.buffer.length, mdl.port, mdl.address)))

return socket.messages$.pipe(
return socket.messages$.pipe(
filterOkResults,
timestamp,
accumulateFreshMessages(cfg.FALLOUT_MS),
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { onvifProbe } from './onvif/onvif-probe'
import { initSocketStream } from './core/probe'
import { DEFAULT_CONFIG } from './config/config.default'
import { map } from 'rxjs/operators'

export * from './config/config.interface'

export const probe = initSocketStream.flatMap(onvifProbe)
export const devices$ = () => probe.run(DEFAULT_CONFIG)

export const cli = () => devices$()
export const cli = () => devices$().pipe(map(a => a.map(b => b.device)))
.subscribe(v => {
console.log('\n')
console.log('Scanning for networked cameras...')
Expand Down
71 changes: 51 additions & 20 deletions src/onvif/onvif-probe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,48 @@ import { initSocketStream } from '../core/probe'
import { DOMParser } from 'xmldom'
import { onvifProbe } from './onvif-probe'

const wsXmlResponse = '<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsdd="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:vfdis="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding" xmlns:vfdis2="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding" xmlns:tdn="http://www.onvif.org/ver10/network/wsdl"><SOAP-ENV:Header><wsa:MessageID>uuid:2709d68a-7dc1-61c2-a205-X3018101811662</wsa:MessageID><wsa:RelatesTo>uuid:NetworkVideoTransmitter</wsa:RelatesTo><wsa:ReplyTo SOAP-ENV:mustUnderstand="true"><wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address></wsa:ReplyTo><wsa:To SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><wsdd:ProbeMatches><wsdd:ProbeMatch><wsa:EndpointReference><wsa:Address>urn:uuid:2419d68a-2dd2-21b2-a205-X2018101811779</wsa:Address><wsa:ReferenceProperties></wsa:ReferenceProperties><wsa:ReferenceParameters></wsa:ReferenceParameters><wsa:PortType>ttl</wsa:PortType></wsa:EndpointReference><wsdd:Types>tdn:4655721b-4e0e-4296-ba0b-3180423b5b0c</wsdd:Types><wsdd:Scopes>onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Model/631GA onvif://www.onvif.org/Name/IPCAM onvif://www.onvif.org/location/country/china</wsdd:Scopes><wsdd:XAddrs>http://192.168.1.1:80/onvif/device_service</wsdd:XAddrs><wsdd:MetadataVersion>1</wsdd:MetadataVersion></wsdd:ProbeMatch></wsdd:ProbeMatches></SOAP-ENV:Body></SOAP-ENV:Envelope>'
const ipcam = '<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsdd="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:vfdis="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding" xmlns:vfdis2="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding" xmlns:tdn="http://www.onvif.org/ver10/network/wsdl"><SOAP-ENV:Header><wsa:MessageID>uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</wsa:MessageID><wsa:RelatesTo>uuid:NetworkVideoTransmitter</wsa:RelatesTo><wsa:ReplyTo SOAP-ENV:mustUnderstand="true"><wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address></wsa:ReplyTo><wsa:To SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><wsdd:ProbeMatches><wsdd:ProbeMatch><wsa:EndpointReference><wsa:Address>urn:uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</wsa:Address><wsa:ReferenceProperties></wsa:ReferenceProperties><wsa:ReferenceParameters></wsa:ReferenceParameters><wsa:PortType>ttl</wsa:PortType></wsa:EndpointReference><wsdd:Types>tdn:4655721b-4e0e-4296-ba0b-3180423b5b0c</wsdd:Types><wsdd:Scopes>onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Model/631GA onvif://www.onvif.org/Name/IPCAM onvif://www.onvif.org/location/country/china</wsdd:Scopes><wsdd:XAddrs>http://192.168.1.1:80/onvif/device_service</wsdd:XAddrs><wsdd:MetadataVersion>1</wsdd:MetadataVersion></wsdd:ProbeMatch></wsdd:ProbeMatches></SOAP-ENV:Body></SOAP-ENV:Envelope>'
const amcrest = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?><s:Envelope xmlns:sc="http://www.w3.org/2003/05/soap-encoding" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:dn="http://www.onvif.org/ver10/network/wsdl" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"><s:Header><a:MessageID>uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</a:MessageID><a:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To><a:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</a:Action><a:RelatesTo>uuid:Device</a:RelatesTo></s:Header><s:Body><d:ProbeMatches><d:ProbeMatch><a:EndpointReference><a:Address>uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</a:Address></a:EndpointReference><d:Types>dn:NetworkVideoTransmitter tds:Device</d:Types><d:Scopes>onvif://www.onvif.org/location/country/china onvif://www.onvif.org/name/Amcrest onvif://www.onvif.org/hardware/IP2M-841B onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/type/Network_Video_Transmitter onvif://www.onvif.org/extension/unique_identifier</d:Scopes><d:XAddrs>http://192.168.1.235/onvif/device_service</d:XAddrs><d:MetadataVersion>1</d:MetadataVersion></d:ProbeMatch></d:ProbeMatches></s:Body></s:Envelope>'

const initTestServer = (port: number) => {
const initTestServer = (port: number) => (toSend: string) => {
const server = createSocket('udp4')
server.on('error', _ => server.close())
server.on('message', (msg, rinfo) => {
const buf = Buffer.from(wsXmlResponse)
const buf = Buffer.from(toSend)
server.send(buf, 0, buf.length, rinfo.port, rinfo.address)
})
server.bind(port)
return server
}

const config = (port: number) => {
return {
PORTS: { UPNP: [], WS_DISCOVERY: [port] },
MULTICAST_ADDRESS: '0.0.0.0',
FALLOUT_MS: 24000,
PROBE_SAMPLE_TIME_MS: 6000,
PROBE_NETWORK_TIMEOUT_MS: 12000,
ONVIF_DEVICES: ['NetworkVideoTransmitter', 'Device', 'NetworkVideoDisplay'],
DOM_PARSER: new DOMParser()
}
}

describe('onvif-probe', () => {
it('should work', done => {
const port = 41240
const config: IProbeConfig = {
PORTS: { UPNP: [], WS_DISCOVERY: [port] },
MULTICAST_ADDRESS: '0.0.0.0',
FALLOUT_MS: 5000,
PROBE_SAMPLE_TIME_MS: 50,
PROBE_NETWORK_TIMEOUT_MS: 3000,
ONVIF_DEVICES: ['NetworkVideoTransmitter', 'Device', 'NetworkVideoDisplay'],
DOM_PARSER: new DOMParser()
}
it('should handle IPCAM - 631GA', done => {
// jest.setTimeout(10000000)
const port = 41241

initTestServer(port)
initSocketStream.flatMap(onvifProbe).run(config)
initTestServer(port)(ipcam)
initSocketStream.flatMap(onvifProbe).run(config(port))
.subscribe(res => {
const firstResult = res[0]
const device = firstResult.device
expect(device).toEqual({
expect(res[0].device).toEqual({
name: 'IPCAM',
hardware: '631GA',
location: 'china',
deviceServiceUri: 'http://192.168.1.1:80/onvif/device_service',
ip: '192.168.1.1',
metadataVersion: '1',
urn: '2419d68a-2dd2-21b2-a205-X2018101811779',
urn: '8eceb0ca-564e-4436-bec5-e63ea243c529',
scopes: [
'onvif://www.onvif.org/Profile/Streaming',
'onvif://www.onvif.org/Model/631GA',
Expand All @@ -55,4 +58,32 @@ describe('onvif-probe', () => {
done()
})
})

it('should handle AMCREST - IP2M-841B', done => {
const port = 41242

initTestServer(port)(amcrest)
initSocketStream.flatMap(onvifProbe).run(config(port))
.subscribe(res => {
expect(res[0].device).toEqual({
name: 'Amcrest',
hardware: 'IP2M-841B',
location: 'china',
deviceServiceUri: 'http://192.168.1.235/onvif/device_service',
ip: '192.168.1.235',
metadataVersion: '1',
urn: '8eceb0ca-564e-4436-bec5-e63ea243c529',
scopes: [
'onvif://www.onvif.org/location/country/china',
'onvif://www.onvif.org/name/Amcrest',
'onvif://www.onvif.org/hardware/IP2M-841B',
'onvif://www.onvif.org/Profile/Streaming',
'onvif://www.onvif.org/type/Network_Video_Transmitter',
'onvif://www.onvif.org/extension/unique_identifier'],
profiles: ['Streaming'],
xaddrs: ['http://192.168.1.235/onvif/device_service']
})
done()
})
})
})
2 changes: 1 addition & 1 deletion src/ws-discovery/ws-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const mapDevicesToPayloads = (devices: readonly string[]) => devices.map(mapDevi
const wsDiscoveryParseToDict =
(msg: TimestampMessages) =>
msg.reduce((acc, item) =>
maybe(item.msg.match(/urn:uuid:.*?</g))
maybe(item.msg.match(/uuid:.*?</g))
.flatMapAuto(a => a[0].replace('<', '').split(':').pop())
.filter(key => !acc[key])
.map(key => ({ ...acc, [key]: item.msg }))
Expand Down

0 comments on commit 4252917

Please sign in to comment.