Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.3.14 #950

Merged
merged 20 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Based on [@noelhibbard's script](https://gist.github.com/noelhibbard/03703f55129

Please consider ⭐️ starring or [☕️ sponsoring](https://ko-fi.com/mrlt8) this project if you found it useful, or use the [affiliate link](https://amzn.to/3NLnbvt) when shopping on amazon!

## API Changes

As of July 2023, you will need to update your bridge to v2.3.x or newer for compatibility with the latest changes to the Wyze API.

## Quick Start

Install [docker](https://docs.docker.com/get-docker/) and run:
Expand All @@ -33,23 +37,20 @@ You can then use the web interface at `http://localhost:5000` where localhost is

See [basic usage](#basic-usage) for additional information or visit the [wiki page](https://github.com/mrlt8/docker-wyze-bridge/wiki/Home-Assistant) for additional information on using the bridge as a Home Assistant Add-on.

## What's Changed in v2.3.13
## What's Changed in v2.3.14

FIXES:
* Errors when SET/GET `bitrate`. Thanks @plat2on1! (#929)
* Prevent exception on empty GET/SET payload.
NEW:
* PTZ controls in MQTT discovery as "cover"
* Add ffmpeg `filter_complex` config (#919)

## What's Changed in v2.3.12

* NEW:
* `update_snapshot` MQTT/REST API GET topic.
* Additional MQTT entities (#921)
* FIXES:
* Monitor and set preferred bitrate if/when the wyze app changes it. Thanks @plat2on1! (#929)
* `cruise_point` index starts at 1 when setting via MQTT/REST API. (#835)
* Camera status was always online. (#907) (#920)
* Power status was incorrect when using MQTT discovery. (#921)

CHANGED:
* Adjust default bitrate for re-encoding to 3000k.
* Case sensitive FFMPEG_CMD (#736) Thanks @392media!
* `DEBUG_FFMPEG` is now `FFMPEG_LOGLEVEL` with customizable levels:
* `quiet`, `panic`, `fatal`, `error`, `warning`, `info`, `verbose`, `debug`.
* Defaults to `fatal`.
* Bump Wyze App version to v2.44.1.1 (#946)

[View previous changes](https://github.com/mrlt8/docker-wyze-bridge/releases)

Expand Down
15 changes: 15 additions & 0 deletions app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## What's Changed in v2.3.14

NEW:
* PTZ controls in MQTT discovery as "cover"
* Add ffmpeg `filter_complex` config (#919)


CHANGED:
* Adjust default bitrate for re-encoding to 3000k.
* Case sensitive FFMPEG_CMD (#736) Thanks @392media!
* `DEBUG_FFMPEG` is now `FFMPEG_LOGLEVEL` with customizable levels:
* `quiet`, `panic`, `fatal`, `error`, `warning`, `info`, `verbose`, `debug`.
* Defaults to `fatal`.
* Bump Wyze App version to v2.44.1.1 (#946)

## What's Changed in v2.3.13

FIXES:
Expand Down
5 changes: 3 additions & 2 deletions app/Dockerfile.qsv
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ ARG QSV
RUN if [ -n "$QSV" ]; then echo 'deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware' >/etc/apt/sources.list.d/debian-testing.list; fi \
&& apt-get update \
&& apt-get install -y curl tar xz-utils \
${QSV:+i965-va-driver-shaders intel-media-va-driver-non-free intel-opencl-icd libmfx1 libva-drm2 libx11-6} \
${QSV:+i965-va-driver intel-gpu-tools intel-media-va-driver-non-free intel-opencl-icd libmfx1 libva-drm2 libx11-6 vainfo} \
&& if [ -n "$QSV" ]; then apt-get install -y i965-va-driver-shaders; fi \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY . /build/app/
Expand All @@ -20,7 +21,7 @@ RUN cd /build \
&& curl -SL https://github.com/bluenviron/mediamtx/releases/download/v${MTX_TAG}/mediamtx_v${MTX_TAG}_linux_amd64.tar.gz \
| tar -xzf - -C app --wildcards 'mediamtx*' \
&& cp app/amd.lib usr/local/lib/libIOTCAPIs_ALL.so \
&& if [ -n "$QSV" ]; then cp -R /usr/lib/x86_64-linux-gnu/ usr/lib/; fi \
&& if [ -n "$QSV" ]; then cp -R /usr/lib/x86_64-linux-gnu/ usr/lib/ && cp -R /usr/bin/ usr/bin; fi \
&& rm app/*.txt app/*.lib

FROM base
Expand Down
2 changes: 1 addition & 1 deletion app/static/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ document.addEventListener("DOMContentLoaded", () => {
.then((data) => {
let apiVersion = data.tag_name.replace(/[^0-9\.]/g, "");
if (apiVersion.localeCompare(checkAPI.dataset.version, undefined, { numeric: true }) === 1) {
sendNotification('Update available!', `🎉 v.${apiVersion}`, "warning");
sendNotification('Update available!', `🎉 v${apiVersion}`, "warning");
} else {
sendNotification('All up to date!', '✅ Running the latest version!', "success");
}
Expand Down
8 changes: 6 additions & 2 deletions app/wyzebridge/bridge_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
from wyzecam.api_models import WyzeCamera


def env_cam(env: str, uri: str, default="") -> str:
return env_bool(f"{env}_{uri}", env_bool(env, env_bool(f"{env}_all", default)))
def env_cam(env: str, uri: str, default="", style="") -> str:
return env_bool(
f"{env}_{uri}",
env_bool(env, env_bool(f"{env}_all", default, style=style), style=style),
style=style,
)


def env_bool(env: str, false="", true="", style="") -> Any:
Expand Down
2 changes: 1 addition & 1 deletion app/wyzebridge/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@


DEPRECATED = {
"DEBUG_LEVEL",
"DEBUG_FFMPEG",
}

for env in DEPRECATED:
Expand Down
33 changes: 26 additions & 7 deletions app/wyzebridge/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ def get_ffmpeg_cmd(
rtsp_transport = "udp" if "udp" in env_bool("MTX_PROTOCOLS") else "tcp"
rss_cmd = f"[{{}}f=rtsp:{rtsp_transport=:}:bsfs/v=dump_extra=freq=keyframe]rtsp://0.0.0.0:8554/{uri}"
rtsp_ss = rss_cmd.format("")
if env_cam("AUDIO_STREAM", uri) and audio:
if env_cam("AUDIO_STREAM", uri, style="original") and audio:
rtsp_ss += "|" + rss_cmd.format("select=a:") + "_audio"
h264_enc = env_bool("h264_enc").partition("_")[2]

cmd = env_cam("FFMPEG_CMD", uri).format(
cmd = env_cam("FFMPEG_CMD", uri, style="original").format(
cam_name=uri, CAM_NAME=uri.upper(), audio_in=audio_in
).split() or (
["-loglevel", "verbose" if env_bool("DEBUG_FFMPEG") else "fatal"]
["-hide_banner", "-loglevel", get_log_level()]
+ env_cam("FFMPEG_FLAGS", uri, flags).strip("'\"\n ").split()
+ ["-thread_queue_size", "100"]
+ (["-hwaccel", h264_enc] if h264_enc in {"vaapi", "qsv"} else [])
Expand All @@ -66,11 +66,29 @@ def get_ffmpeg_cmd(
)
if "ffmpeg" not in cmd[0].lower():
cmd.insert(0, "ffmpeg")
if env_bool("DEBUG_FFMPEG"):
if env_bool("FFMPEG_LOGLEVEL") in {"info", "verbose", "debug"}:
logger.info(f"[FFMPEG_CMD] {' '.join(cmd)}")
return cmd


def get_log_level():
level = env_bool("FFMPEG_LOGLEVEL", "fatal").lower()

if level in {
"quiet",
"panic",
"fatal",
"error",
"warning",
"info",
"verbose",
"debug",
}:
return level

return "verbose"


def re_encode_video(uri: str, is_vertical: bool) -> list[str]:
"""
Check if stream needs to be re-encoded.
Expand All @@ -92,6 +110,7 @@ def re_encode_video(uri: str, is_vertical: bool) -> list[str]:
"""
h264_enc: str = env_bool("h264_enc", "libx264")
custom_filter = env_cam("FFMPEG_FILTER", uri)
filter_complex = env_cam("FFMPEG_FILTER_COMPLEX", uri)
v_filter = []
transpose = "clock"
if (env_bool("ROTATE_DOOR") and is_vertical) or env_bool(f"ROTATE_CAM_{uri}"):
Expand All @@ -106,7 +125,7 @@ def re_encode_video(uri: str, is_vertical: bool) -> list[str]:
elif h264_enc == "h264_qsv":
v_filter[1] = f"vpp_qsv=transpose={transpose}"

if not env_bool("FORCE_ENCODE") and not v_filter and not custom_filter:
if not (env_bool("FORCE_ENCODE") or v_filter or custom_filter or filter_complex):
return ["copy"]

logger.info(
Expand All @@ -121,8 +140,8 @@ def re_encode_video(uri: str, is_vertical: bool) -> list[str]:
return (
[h264_enc]
+ v_filter
+ ["-b:v", "2000k", "-coder", "1", "-bufsize", "2000k"]
+ ["-maxrate", "2000k", "-minrate", "2000k"]
+ (["-filter_complex", filter_complex, "-map", "[v]"] if filter_complex else [])
+ ["-b:v", "3000k", "-coder", "1", "-bufsize", "3000k"]
+ ["-profile:v", "77" if h264_enc == "h264_v4l2m2m" else "main"]
+ ["-preset", "fast" if h264_enc in {"h264_nvenc", "h264_qsv"} else "ultrafast"]
+ ["-forced-idr", "1", "-force_key_frames", "expr:gte(t,n_forced*2)"]
Expand Down
17 changes: 16 additions & 1 deletion app/wyzebridge/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def publish_discovery(cam_uri: str, cam: WyzeCamera, stopped: bool = True) -> No
}

# Clear out old/renamed entities
REMOVE = {"alarm": "switch"}
REMOVE = {"alarm": "switch", "pan_tilt": "cover"}
for entity, type in REMOVE.items():
msgs.append((f"{MQTT_DISCOVERY}/{type}/{cam.mac}/{entity}/config", None))

Expand Down Expand Up @@ -390,6 +390,21 @@ def get_entities(base_topic: str, pan_cam: bool = False, rtsp: bool = False) ->
"icon": "mdi:map-marker-multiple",
},
},
"pan_tilt": {
"type": "cover",
"payload": {
"command_topic": f"{base_topic}rotary_degree/set",
"tilt_command_topic": f"{base_topic}rotary_degree/set",
"payload_open": "up",
"payload_close": "down",
"payload_stop": None,
"tilt_opened_value": 90,
"tilt_closed_value": -90,
"tilt_min": -90,
"tilt_max": 90,
"icon": "mdi:rotate-orbit",
},
},
}
if rtsp:
entities |= {
Expand Down
26 changes: 17 additions & 9 deletions app/wyzecam/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,20 @@ def login(
payload = sort_dict(
{"email": email.strip(), "password": triplemd5(password), **(mfa or {})}
)
api_version = "old"
api_version = "v2"
if getenv("API_ID") and getenv("API_KEY"):
api_version = "api"
elif getenv("v3"):
api_version = "v3"
headers["appid"] = "umgm_78ae6013d158c4a5"
headers["signature2"] = sign_msg("v3", payload)

base_url = f"{AUTH_API}/{api_version}" if api_version in {"api", "v3"} else AUTH_API
resp = requests.post(f"{base_url}/user/login", data=payload, headers=headers)
resp = requests.post(
f"{AUTH_API}/{api_version}/user/login", data=payload, headers=headers
)
resp.raise_for_status()

return WyzeCredential.parse_obj(dict(resp.json(), phone_id=phone_id))
return WyzeCredential.model_validate(dict(resp.json(), phone_id=phone_id))


def send_sms_code(auth_info: WyzeCredential, phone: str = "Primary") -> str:
Expand Down Expand Up @@ -157,7 +158,7 @@ def refresh_token(auth_info: WyzeCredential) -> WyzeCredential:
)
resp_json = validate_resp(resp)

return WyzeCredential.parse_obj(
return WyzeCredential.model_validate(
dict(
resp_json["data"],
user_id=auth_info.user_id,
Expand Down Expand Up @@ -185,7 +186,9 @@ def get_user_info(auth_info: WyzeCredential) -> WyzeAccount:
)
resp_json = validate_resp(resp)

return WyzeAccount.parse_obj(dict(resp_json["data"], phone_id=auth_info.phone_id))
return WyzeAccount.model_validate(
dict(resp_json["data"], phone_id=auth_info.phone_id)
)


def get_homepage_object_list(auth_info: WyzeCredential) -> dict[str, Any]:
Expand All @@ -204,7 +207,7 @@ def get_camera_list(auth_info: WyzeCredential) -> list[WyzeCamera]:
"""Return a list of all cameras on the account."""
data = get_homepage_object_list(auth_info)
result = []
for device in data["device_list"]: # type: dict[str, Any]
for device in data["device_list"]:
if device["product_type"] != "Camera":
continue

Expand Down Expand Up @@ -308,6 +311,9 @@ def set_device_info(

def get_cam_webrtc(auth_info: WyzeCredential, mac_id: str) -> dict:
"""Get webrtc for camera."""
if not auth_info.access_token:
raise AccessTokenError()

ui_headers = get_headers()
ui_headers["content-type"] = "application/json"
ui_headers["authorization"] = auth_info.access_token
Expand Down Expand Up @@ -339,7 +345,9 @@ def validate_resp(resp):
return resp_json


def _get_payload(access_token: Optional[str], phone_id: str, req_path: str = "default"):
def _get_payload(
access_token: Optional[str], phone_id: Optional[str] = "", req_path: str = "default"
):
return {
"sc": SC_SV[req_path]["sc"],
"sv": SC_SV[req_path]["sv"],
Expand All @@ -353,7 +361,7 @@ def _get_payload(access_token: Optional[str], phone_id: str, req_path: str = "de
}


def get_headers(phone_id: str = "") -> dict[str, str]:
def get_headers(phone_id: Optional[str] = "") -> dict[str, str]:
if not phone_id:
return {"user-agent": SCALE_USER_AGENT}
id, key = getenv("API_ID"), getenv("API_KEY")
Expand Down
2 changes: 1 addition & 1 deletion app/wyzecam/tutk/tutk_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ class K11000SetRotaryByDegree(TutkWyzeProtocolMessage):

"""

def __init__(self, horizontal: int, vertical: int, speed: int = 5):
def __init__(self, horizontal: int, vertical: int = 0, speed: int = 5):
super().__init__(11000)
self.horizontal = horizontal
self.vertical = vertical
Expand Down
Loading