The add-camera form asked for an IP address, and a shop owner does not know their camera's IP address - it is on a sticker under the camera or in a menu that differs by make. That field is where onboarding stopped for anyone who was not an installer. behavision/discover.py: one ONVIF WS-Discovery multicast (names the camera and often its make) merged with a TCP sweep of port 554 across the local /24 (misses nothing that streams). Stdlib only, ~4 s on the office network, both cameras found. The add-camera sheet leads with 'Find cameras on this network'; picking a row fills the address and, when the make is recognisable, the stream path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
45 lines
2.0 KiB
Python
45 lines
2.0 KiB
Python
"""Discovery parsing, with no network: the two shapes cameras actually send."""
|
|
from behavision.discover import parse_probe_match, guess_make, local_networks
|
|
|
|
HIK = ('<?xml version="1.0"?><env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">'
|
|
'<env:Body><d:ProbeMatches xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"><d:ProbeMatch>'
|
|
'<d:Scopes>onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/name/HIKVISION%20DS-2CD2043G2 '
|
|
'onvif://www.onvif.org/hardware/DS-2CD2043G2-I onvif://www.onvif.org/location/city/hangzhou</d:Scopes>'
|
|
'<d:XAddrs>http://192.168.1.122/onvif/device_service</d:XAddrs>'
|
|
'</d:ProbeMatch></d:ProbeMatches></env:Body></env:Envelope>')
|
|
|
|
BARE = ('<SOAP-ENV:Envelope><SOAP-ENV:Body><wsdd:ProbeMatches><wsdd:ProbeMatch>'
|
|
'<wsdd:XAddrs>http://10.0.0.9:8080/onvif/device_service http://[fe80::1]/onvif</wsdd:XAddrs>'
|
|
'</wsdd:ProbeMatch></wsdd:ProbeMatches></SOAP-ENV:Body></SOAP-ENV:Envelope>')
|
|
|
|
|
|
def test_hikvision_probe_match_is_named_and_recognised():
|
|
f = parse_probe_match(HIK, "192.168.1.122")
|
|
assert f.host == "192.168.1.122"
|
|
assert f.onvif and not f.rtsp
|
|
assert "HIKVISION DS-2CD2043G2" in f.name and "DS-2CD2043G2-I" in f.name
|
|
assert f.make == "hikvision"
|
|
|
|
|
|
def test_a_nameless_match_still_yields_its_address():
|
|
f = parse_probe_match(BARE, "10.0.0.9")
|
|
assert f.host == "10.0.0.9" and f.name == "" and f.make == ""
|
|
assert f.onvif_url.startswith("http://10.0.0.9:8080")
|
|
|
|
|
|
def test_garbage_is_not_a_camera():
|
|
assert parse_probe_match("<html>not soap</html>", "1.2.3.4") is None
|
|
|
|
|
|
def test_make_guesses_the_common_indian_retail_brands():
|
|
assert guess_make("CP PLUS CP-UNC-TA21L3") == "cpplus"
|
|
assert guess_make("Dahua IPC-HDW1230") == "dahua"
|
|
assert guess_make("TP-LINK Tapo C200") == "tplink"
|
|
assert guess_make("Something Else") == ""
|
|
|
|
|
|
def test_local_networks_are_slash_24_and_never_loopback():
|
|
for n in local_networks():
|
|
assert n.prefixlen == 24
|
|
assert not n.network_address.is_loopback
|