Skip to content

Commit b57f6ae

Browse files
authored
Merge pull request #5 from bms231/AddingStaticIPFunctionality
Wyze no longer returns local IP for pan cam v4, but it is required fo…
2 parents f0bb190 + d993331 commit b57f6ae

5 files changed

Lines changed: 83 additions & 2 deletions

File tree

app/wyzebridge/config.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os, json
2+
from typing import Dict
13
from os import environ, getenv
24

35
from wyzebridge.build_config import BUILD_STR
@@ -23,6 +25,7 @@
2325

2426
# TODO: change TOKEN_PATH to /config for all:
2527
TOKEN_PATH: str = "/config/" if HASS_TOKEN else "/tokens/"
28+
IP_OVERRIDES_FILE = os.path.join(TOKEN_PATH, "ip_overrides.json")
2629
IMG_PATH: str = f'/{env_bool("IMG_DIR", r"/media/wyze/img").strip("/")}/'
2730

2831
LATITUDE: float = float(getenv("LATITUDE", "0"))
@@ -93,3 +96,52 @@
9396
print(f"\n[!] WARNING: In {BUILD_STR}, {key} is deprecated! Please use {new_key} instead\n")
9497
environ.pop(key, None)
9598
environ[new_key] = value
99+
100+
101+
def _parse_overrides_text(text: str) -> Dict[str, str]:
102+
"""Accept nickname:ip or nickname=ip, separated by newlines/commas/semicolons."""
103+
mapping: Dict[str, str] = {}
104+
if not text:
105+
return mapping
106+
text = text.replace(",", "\n").replace(";", "\n")
107+
for line in text.splitlines():
108+
line = line.strip()
109+
if not line:
110+
continue
111+
if ":" in line:
112+
k, v = line.split(":", 1)
113+
elif "=" in line:
114+
k, v = line.split("=", 1)
115+
else:
116+
continue
117+
k, v = k.strip(), v.strip()
118+
if k and v:
119+
mapping[k] = v
120+
return mapping
121+
122+
def load_ip_overrides() -> Dict[str, str]:
123+
"""
124+
Return merged {nickname: ip} from file + env (env wins).
125+
- File path: /config/ip_overrides.json (if present)
126+
- Env var : WB_IP_OVERRIDES (from HA add-on Configuration)
127+
"""
128+
file_map: Dict[str, str] = {}
129+
try:
130+
with open(IP_OVERRIDES_FILE, "r", encoding="utf-8") as f:
131+
raw = json.load(f)
132+
file_map = {str(k).strip(): str(v).strip() for k, v in raw.items()}
133+
except Exception:
134+
file_map = {}
135+
136+
env_map = _parse_overrides_text(os.getenv("WB_IP_OVERRIDES", ""))
137+
return {**file_map, **env_map}
138+
139+
def save_ip_overrides(mapping: Dict[str, str]) -> bool:
140+
"""Persist overrides from any UI path that writes to disk."""
141+
try:
142+
os.makedirs(TOKEN_PATH, exist_ok=True)
143+
with open(IP_OVERRIDES_FILE, "w", encoding="utf-8") as f:
144+
json.dump(mapping, f, indent=2, ensure_ascii=False)
145+
return True
146+
except Exception:
147+
return False

app/wyzecam/api.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from hashlib import md5
88
from os import getenv
99
from typing import Any, Optional
10+
from wyzebridge.config import load_ip_overrides
1011

1112
from requests import PreparedRequest, Response, get, post
1213

@@ -225,6 +226,12 @@ def get_homepage_object_list(auth_info: WyzeCredential) -> dict[str, Any]:
225226

226227
def get_camera_list(auth_info: WyzeCredential) -> list[WyzeCamera]:
227228
"""Return a list of all cameras on the account."""
229+
#ip_overrides = load_ip_overrides()
230+
# load + normalize overrides once
231+
_norm = lambda s: str(s).strip().casefold()
232+
_ip_overrides_raw = load_ip_overrides()
233+
_ip_overrides = {_norm(k): str(v).strip() for k, v in _ip_overrides_raw.items()}
234+
228235
data = get_homepage_object_list(auth_info)
229236
result = []
230237
for device in data["device_list"]:
@@ -239,6 +246,7 @@ def get_camera_list(auth_info: WyzeCredential) -> list[WyzeCamera]:
239246
mac: Optional[str] = device.get("mac")
240247
product_model: Optional[str] = device.get("product_model")
241248
nickname: Optional[str] = device.get("nickname")
249+
nn = _norm(nickname) if nickname else ""
242250
timezone_name: Optional[str] = device.get("timezone_name")
243251
firmware_ver: Optional[str] = device.get("firmware_ver")
244252
dtls: Optional[int] = device_params.get("dtls")
@@ -249,8 +257,22 @@ def get_camera_list(auth_info: WyzeCredential) -> list[WyzeCamera]:
249257
"thumbnails_url"
250258
)
251259

252-
if not p2p_type:
253-
continue
260+
#if not p2p_type: #removing because the new pan cam v4 doesnt return p2p_type and it isn't required
261+
# continue
262+
#if not ip and nickname:
263+
# ov = ip_overrides.get(str(nickname).strip())
264+
# if ov:
265+
# ip = ov
266+
# if this camera is listed in overrides, allow it even if p2p_type is missing
267+
# (and fill IP from override if cloud didn't provide one)
268+
if nn in _ip_overrides:
269+
if not ip:
270+
ip = _ip_overrides[nn]
271+
else:
272+
# not listed in overrides → keep original p2p gate
273+
if not p2p_type:
274+
continue
275+
254276
if not ip:
255277
continue
256278
if not enr:

home_assistant/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ schema:
6969
WYZE_PASSWORD: password?
7070
API_ID: match(\s*[a-fA-F0-9-]{36}\s*)?
7171
API_KEY: match(\s*[a-zA-Z0-9]{60}\s*)?
72+
WB_IP_OVERRIDES: str?
7273
WB_IP: str?
7374
REFRESH_TOKEN: str?
7475
ACCESS_TOKEN: str?

home_assistant/translations/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ configuration:
1111
API_KEY:
1212
name: API Key
1313
description: Optional, but must be used in combination with the Key ID.
14+
WB_IP_OVERRIDES:
15+
name: Manual Camera IP Overrides
16+
description: Some cams dont return local ips via Wyze IP and thus won't detect. Put them here in nickname:ip format using - for spaces for manual addition to the bridge. example Garage-Cam:192.168.0.5
1417
WB_IP:
1518
name: Bridge IP
1619
description: Home Assistant IP for WebRTC ICE traffic.

unraid/docker-wyze-bridge.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,7 @@
130130
<Config Name="Stream Host Name" Target="DOMAIN" Default="" Mode="" Description="Specifies the hostname for the camera stream URLs. Defaults to the Home Assistant domain or localhost" Type="Variable" Display="advanced" Required="false" Mask="false"/>
131131
<Config Name="Local Latitude" Target="LATITUDE" Default="" Mode="" Description="Used to compute sunset/sunrise for snapshots" Type="Variable" Display="always" Required="false" Mask="false"/>
132132
<Config Name="Local Longitude" Target="LONGITUDE" Default="" Mode="" Description="Used to compute sunset/sunrise for snapshots" Type="Variable" Display="always" Required="false" Mask="false"/>
133+
<Config Name="Manual camera IP overrides" Target="WB_IP_OVERRIDES" Default="" Description="Enter nickname-to-IP pairs. Format: Nickname:IP (or Nickname=IP). Separate multiple entries with newlines, semicolons, or commas. Example: Garage Cam:192.168.1.55; Driveway=192.168.1.101" Type="Variable" Display="always" Required="false" Mask="false">
134+
</Config>
135+
133136
</Container>

0 commit comments

Comments
 (0)