Skip to content

Commit

Permalink
v2.0.2 (#754)
Browse files Browse the repository at this point in the history
* Media Controls

* Refresh preview if cam enabled  #750

* Show battery icon if cam is battery powered

* Use last modified date for API thumbnails

* Remove broken PTZ and add motion tagging commands

* update docs
  • Loading branch information
mrlt8 authored Mar 30, 2023
1 parent 30580d3 commit 56d59ca
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 22 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ You can then use the web interface at `http://localhost:5000` where localhost is

See [basic usage](#basic-usage) for additional information.

## What's Changed in v2.0.2

* Camera Control: Don't wait for a response when sending `set_rotary_` commands. #746
* Camera Control: Add commands for motion tagging (potentially useful if using waitmotion in mini hacks):
* `get_motion_tagging` current status: `1`=ON, `2`=OFF.
* `set_motion_tagging_on` turn on motion tagging.
* `set_motion_tagging_off` turn off motion tagging
* WebUI: Refresh image previews even if camera is not connected but enabled. (will still ignore battery cameras) #750
* WebUI: Add battery icon to cameras with a battery.
* WebUI: Use Last-Modified date to calculate the age of the thumbnails from the wyze API.
* Update documentation for K10010ControlChannel media controls for potential on-demand control of video/audio.

## What's Changed in v2.0.1

* Fixed a bug where the WebUI would not start if 2FA was required. #741
Expand Down
12 changes: 12 additions & 0 deletions app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## What's Changed in v2.0.2

* Camera Control: Don't wait for a response when sending `set_rotary_` commands. #746
* Camera Control: Add commands for motion tagging (potentially useful if using waitmotion in mini hacks):
* `get_motion_tagging` current status: `1`=ON, `2`=OFF.
* `set_motion_tagging_on` turn on motion tagging.
* `set_motion_tagging_off` turn off motion tagging
* WebUI: Refresh image previews even if camera is not connected but enabled. (will still ignore battery cameras) #750
* WebUI: Add battery icon to cameras with a battery.
* WebUI: Use Last-Modified date to calculate the age of the thumbnails from the wyze API.
* Update documentation for K10010ControlChannel media controls for potential on-demand control of video/audio.

## What's Changed in v2.0.1

* Fixed a bug where the WebUI would not start if 2FA was required. #741
Expand Down
2 changes: 1 addition & 1 deletion app/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"slug": "docker-wyze-bridge",
"url": "https://github.com/mrlt8/docker-wyze-bridge",
"image": "mrlt8/wyze-bridge",
"version": "2.0.1",
"version": "2.0.2",
"arch": [
"armv7",
"aarch64",
Expand Down
17 changes: 11 additions & 6 deletions app/static/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,11 @@ function refresh_imgs() {
console.debug("refresh_imgs " + Date.now());
document.querySelectorAll(".refresh_img").forEach(async function (image) {
let url = image.getAttribute("src");
// Skip if not connected
await update_img(url, !image.classList.contains("connected"));
// Skip if not enabled or battery
let CameraBattery = document.getElementById(image.dataset.cam).dataset.battery?.toLowerCase() == "true";
let CameraConnected = image.classList.contains("connected");
let CameraEnabled = image.classList.contains("enabled");
await update_img(url, !(CameraEnabled && (!CameraBattery || CameraConnected)));
});
}

Expand Down Expand Up @@ -319,7 +322,7 @@ document.addEventListener("DOMContentLoaded", () => {
oldUrl = `snapshot/${cam}.jpg`;
}
try {
let newUrl = await update_img(oldUrl, (getCookie("refresh_period") <= 10 || !img.classList.contains("connected")));
let newUrl = await update_img(oldUrl, (getCookie("refresh_period") <= 10 || !img.classList.contains("enabled")));
let newVal = newUrl;
img.parentElement.querySelectorAll("[src$=loading\\.svg],[style*=loading\\.svg],[poster$=loading\\.svg]")
.forEach((e) => {
Expand Down Expand Up @@ -387,7 +390,7 @@ document.addEventListener("DOMContentLoaded", () => {
clearInterval(refresh_interval);
document.getElementById("connection-lost").style.display = "block";
autoplay("stop");
document.querySelectorAll("img.connected").forEach((i) => { i.classList.remove("connected") })
document.querySelectorAll("img.refresh_img,video[data-cam='${cam}']").forEach((i) => { i.classList.remove("connected", "enabled") })
document.querySelectorAll(".cam-overlay").forEach((i) => {
i.getElementsByClassName("fas")[0].classList.remove("fa-spin");
})
Expand Down Expand Up @@ -439,13 +442,15 @@ document.addEventListener("DOMContentLoaded", () => {
card.dataset.connected = false;
statusIcon.setAttribute("class", "fas")
statusIcon.parentElement.title = ""
if (preview) { preview.classList.remove("connected") }
if (preview) {
if (status == "connected") { preview.classList.add("connected") } else { preview.classList.remove("connected") }
if (status == "disabled") { preview.classList.remove("enabled") } else { preview.classList.add("enabled") }
}
if (status == "connected") {
if (!connected) { sendNotification('Connected', `Connected to ${cam}`, "success"); }
card.dataset.connected = true;
statusIcon.classList.add("fa-circle-play", "has-text-success");
statusIcon.parentElement.title = "Click/tap to pause";
if (preview) { preview.classList.add("connected") }
autoplay();
let noPreview = card.querySelector('.no-preview')
if (noPreview) {
Expand Down
8 changes: 7 additions & 1 deletion app/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@
{% if cam_data.cameras %}
{% for cam_uri,camera in cam_data.cameras.items() %}
<div id="{{ cam_uri }}" class="camera column {{'is-hidden' if not camera.enabled}}"
data-enabled="{{camera.enabled}}" data-connected="{{camera.connected}}">
data-enabled="{{camera.enabled}}" data-connected="{{camera.connected}}"
data-battery="{{camera.is_battery}}">
<div class="card">
<header class="card-header fs-display-none">
<div class="card-header-title">
Expand Down Expand Up @@ -149,6 +150,11 @@
</span>
{% endif %}
{% endif %}
{% if camera.is_battery %}
<span class="icon" title="Battery">
<i class="fas fa-battery-empty" aria-hidden="true"></i>
</span>
{% endif %}
</div>
<span class="drag_handle" draggable="true"></span>
<button class="card-header-icon toggle-details" data-cam="{{cam_uri}}" title="Camera details">
Expand Down
7 changes: 6 additions & 1 deletion app/wyzebridge/wyze_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import pickle
import struct
from base64 import b32decode
from datetime import datetime
from functools import wraps
from os import environ, getenv, listdir, remove
from os import environ, getenv, listdir, remove, utime
from os.path import exists, getsize
from pathlib import Path
from time import sleep, time
Expand Down Expand Up @@ -153,6 +154,10 @@ def save_thumbnail(self, uri: str) -> bool:
return False
with open(save_to, "wb") as f:
f.write(img.content)
if modified := img.headers.get("Last-Modified"):
ts_format = "%a, %d %b %Y %H:%M:%S %Z"
if updated := int(datetime.strptime(modified, ts_format).timestamp()):
utime(save_to, (updated, updated))
return True

@authenticated
Expand Down
9 changes: 3 additions & 6 deletions app/wyzebridge/wyze_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,10 @@
"set_rotary_down": ("K11000SetRotaryByDegree", 0, -90),
"set_rotary_right": ("K11000SetRotaryByDegree", 90, 0),
"set_rotary_left": ("K11000SetRotaryByDegree", -90, 0),
"set_ptz_10": ("K11018SetPTZPosition", 10, 10),
"set_ptz_30": ("K11018SetPTZPosition", 30, 30),
"set_ptz_60": ("K11018SetPTZPosition", 60, 60),
"set_ptz_60v": ("K11018SetPTZPosition", 60, 0),
"set_ptz_60h": ("K11018SetPTZPosition", 0, 60),
"set_ptz_90": ("K11018SetPTZPosition", 90, 90),
"start_boa": ("K10148StartBoa",),
"get_motion_tagging": ("K10290GetMotionTagging",),
"set_motion_tagging_on": ("K10292SetMotionTagging", True),
"set_motion_tagging_off": ("K10292SetMotionTagging", False),
}


Expand Down
23 changes: 16 additions & 7 deletions app/wyzecam/tutk/tutk_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,20 +255,29 @@ def parse_response(self, resp_data):

class K10010ControlChannel(TutkWyzeProtocolMessage):
"""
A command used frequently by the mobile app to configure settings on the camera.
Media Controls.
A command used frequently by the mobile app to configure settings on the camera.
Not terribly well understood.
Parameters:
- media_type (int): The ID of the media to control:
- 1: Video
- 2: Audio
- 3: Return Audio
- 4: RDT
- enabled (bool): True if the media should be enabled, False otherwise
"""

def __init__(self, k: int = 1, v: int = 2):
def __init__(self, media_type: int = 1, enabled: bool = False):
super().__init__(10010)
assert k < 256, "control channel key must be < 256"
assert v < 256, "control channel value must be < 256"
self.k = k
self.v = v

assert 0 < media_type <= 4, "control channel media_type must be 1-4"
self.media_type = media_type
self.enabled = 1 if enabled else 2

def encode(self) -> bytes:
return encode(self.code, 2, bytes([self.k, self.v]))
return encode(self.code, 2, bytes([self.media_type, self.enabled]))


class K10020CheckCameraInfo(TutkWyzeProtocolMessage):
Expand Down

0 comments on commit 56d59ca

Please sign in to comment.