Created
November 13, 2023 06:07
-
-
Save jorticus/7869a8e22403e4babb103a48fcab466b to your computer and use it in GitHub Desktop.
dnsmasq rtmp device discovery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# | |
# Dynamically populate NGINX config with network cameras | |
# detected via DHCP leases table and DNSMasq | |
# | |
import os | |
import sys | |
import time | |
DHCP_LEASES_FILE = '/tmp/dhcp.leases' # OpenWRT DNSMASQ | |
NGINX_CAMERAS_FILE = '/etc/nginx/cameras.conf' # This file will be included by nginx.conf | |
LOGIN_DETAILS = { | |
'default': {'user': 'admin', 'password': 'password'} | |
# Add MAC address for cameras with specific login details | |
} | |
SUPPORTED_CAMERAS = [ | |
{ | |
'name': 'Reolink', | |
'mac_prefix': 'ec:71:db:', | |
'stream_hi': 'rtmp://{host}/bcs/channel0_main.bcs?channel=0&stream=0&user={user}&password={password}', | |
'stream_lo': 'rtmp://{host}/bcs/channel0_sub.bcs?channel=0&stream=1&user={user}&password={password}', | |
'snapshot': 'http://{host}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=abc123&user={user}&password={password}', | |
}, | |
] | |
def reload_nginx(): | |
# Restart NGINX to start streaming the new cameras | |
print() | |
RESTART_COMMAND = 'docker container restart nginx' | |
print(RESTART_COMMAND) | |
os.system(RESTART_COMMAND) | |
def get_formatted(d, prop, **args): | |
val = d.get(prop) | |
if val: | |
return val.format(**args) | |
return None | |
def get_definition_for_mac(mac): | |
mac_prefix = mac[:9] | |
for cam_definition in SUPPORTED_CAMERAS: | |
if mac_prefix == cam_definition['mac_prefix']: | |
return cam_definition | |
return None | |
def make_camera_for_mac(mac, ip, name): | |
definition = get_definition_for_mac(mac) | |
if definition: | |
print(f'Found camera {mac} @ {ip} ({name})') | |
# Obtain login details for this specific camera, if available, otherwise use default | |
login = LOGIN_DETAILS.get(mac, LOGIN_DETAILS['default']) | |
# Obtain the streaming URLs | |
fmt_args = dict(host=ip, user=login['user'], password=login['password']) | |
stream_hi = get_formatted(definition, 'stream_hi', **fmt_args) | |
stream_lo = get_formatted(definition, 'stream_lo', **fmt_args) | |
snapshot = get_formatted(definition, 'snapshot', **fmt_args) | |
cam = { | |
'mac': mac, | |
'ip': ip, | |
'name': name, | |
'stream_hi': stream_hi, | |
'stream_lo': stream_lo, | |
'snapshot': snapshot | |
} | |
print(f" stream hi: {cam['stream_hi']}") | |
print(f" stream lo: {cam['stream_lo']}") | |
print() | |
return cam | |
return None | |
def get_cameras_from_current_leases(leases_file): | |
cameras = [] | |
try: | |
if os.path.isfile(leases_file): | |
print(f'Read {leases_file}') | |
with open(leases_file, 'r') as f: | |
leases = f.readlines() | |
# Scan the DHCP leases file to find any connected cameras | |
for lease in leases: | |
try: | |
# Example lease: | |
# '0 ec:71:db:ee:30:9b 192.168.4.251 Camera1 01:ec:71:db:ee:30:9b' | |
expiry, mac, ip, name, _ = lease.split(' ', maxsplit=5) | |
camera = make_camera_for_mac(mac, ip, name) | |
if camera: | |
cameras.append(camera) | |
except Exception as e: | |
print(e) | |
continue | |
except Exception as e: | |
print(e) | |
return cameras | |
def get_camera_from_args(action, mac, ip, name): | |
if action == 'add': | |
try: | |
return make_camera_for_mac(mac, ip, name) | |
except Exception as e: | |
print(e) | |
return None | |
def write_nginx_conf(conf_path, cameras): | |
print() | |
print(f'Update {conf_path}:') | |
# Each line creates a new RTMP stream within nginx-rtmp. | |
# 'pull' means the stream is pulling data from a remote source (as opposed to being pushed/published into nginx by a remote device) | |
# 'static' means the stream will start pulling data from the camera immediately. | |
lines = [ | |
f"pull {cam['stream_hi']} name={cam['name']} static;" | |
for cam in cameras | |
] | |
for ln in lines: | |
print(ln) | |
with open(NGINX_CAMERAS_FILE, 'w') as f: | |
f.writelines(lines) | |
if __name__ == "__main__": | |
# Hack: Let dnsmasq update the DHCP leases before continuing | |
if len(sys.argv) > 1: | |
time.sleep(2.0) | |
cameras = get_cameras_from_current_leases(DHCP_LEASES_FILE) | |
# # Optional: If called from dnsmasq (dhcp-script), add additional camera using arguments. | |
# if len(sys.argv) > 1: | |
# camera = get_camera_from_args(*sys.argv) | |
# if camera: | |
# cameras.append(camera) | |
write_nginx_conf(NGINX_CAMERAS_FILE, cameras) | |
reload_nginx() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment