diff --git a/src/config/config.default.ts b/src/config/config.default.ts index 057b3c2..6437c99 100644 --- a/src/config/config.default.ts +++ b/src/config/config.default.ts @@ -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], diff --git a/src/core/probe.ts b/src/core/probe.ts index efb6748..76b7180 100644 --- a/src/core/probe.ts +++ b/src/core/probe.ts @@ -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' @@ -42,12 +42,12 @@ export const probe = (messagesToSend: Strings = []) => (mapFn: (msg: readonly TimestampedMessage[]) => StringDictionary) => reader>(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), diff --git a/src/index.ts b/src/index.ts index 1133945..6c94402 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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...') diff --git a/src/onvif/onvif-probe.spec.ts b/src/onvif/onvif-probe.spec.ts index d7a4545..f6b5783 100644 --- a/src/onvif/onvif-probe.spec.ts +++ b/src/onvif/onvif-probe.spec.ts @@ -4,45 +4,48 @@ import { initSocketStream } from '../core/probe' import { DOMParser } from 'xmldom' import { onvifProbe } from './onvif-probe' -const wsXmlResponse = 'uuid:2709d68a-7dc1-61c2-a205-X3018101811662uuid:NetworkVideoTransmitterhttp://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymoushttp://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymoushttp://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatchesurn:uuid:2419d68a-2dd2-21b2-a205-X2018101811779ttltdn:4655721b-4e0e-4296-ba0b-3180423b5b0convif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Model/631GA onvif://www.onvif.org/Name/IPCAM onvif://www.onvif.org/location/country/chinahttp://192.168.1.1:80/onvif/device_service1' +const ipcam = 'uuid:8eceb0ca-564e-4436-bec5-e63ea243c529uuid:NetworkVideoTransmitterhttp://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymoushttp://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymoushttp://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatchesurn:uuid:8eceb0ca-564e-4436-bec5-e63ea243c529ttltdn:4655721b-4e0e-4296-ba0b-3180423b5b0convif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Model/631GA onvif://www.onvif.org/Name/IPCAM onvif://www.onvif.org/location/country/chinahttp://192.168.1.1:80/onvif/device_service1' +const amcrest = 'uuid:8eceb0ca-564e-4436-bec5-e63ea243c529urn:schemas-xmlsoap-org:ws:2005:04:discoveryhttp://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatchesuuid:Deviceuuid:8eceb0ca-564e-4436-bec5-e63ea243c529dn:NetworkVideoTransmitter tds:Deviceonvif://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_identifierhttp://192.168.1.235/onvif/device_service1' -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', @@ -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() + }) + }) }) \ No newline at end of file diff --git a/src/ws-discovery/ws-probe.ts b/src/ws-discovery/ws-probe.ts index a093e46..b1b8252 100644 --- a/src/ws-discovery/ws-probe.ts +++ b/src/ws-discovery/ws-probe.ts @@ -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:.*? a[0].replace('<', '').split(':').pop()) .filter(key => !acc[key]) .map(key => ({ ...acc, [key]: item.msg }))