fix speaker volume control and auth

- Rename USERNAME/PASSWORD to UNIFI_USERNAME/UNIFI_PASSWORD to avoid
  conflict with the Windows USERNAME system environment variable
- Set both speakerSettings.volume and speakerSettings.speakerVolume
  in the PATCH request
- Log volume before and after the change to verify it took effect
This commit is contained in:
2026-03-01 05:13:32 +01:00
parent 20cc348995
commit 3a60ea5cb7
6 changed files with 116 additions and 14 deletions

43
main.py
View File

@@ -6,27 +6,60 @@ from dotenv import load_dotenv
load_dotenv()
API_KEY = os.environ["API_KEY"]
HOST = os.environ["HOST"]
API_KEY = os.environ["API_KEY"]
USERNAME = os.environ["UNIFI_USERNAME"]
PASSWORD = os.environ["UNIFI_PASSWORD"]
BASE = f"https://{HOST}/proxy/protect/integration/v1"
HEADERS = {"X-API-Key": API_KEY}
PRIVATE_BASE = f"https://{HOST}/proxy/protect/api"
CAMERA_NAME = "G6 Pro 360"
AUDIO_FILE = "./data/hello.wav"
VOLUME = 1.0 # 10%
SPEAKER_VOLUME = 50 # 0-100
def set_speaker_volume(camera_id: str, volume: int):
# Accept-Encoding must be suppressed at request level — server returns 403 when present
with httpx.Client(verify=False, http2=True) as client:
res = client.post(
f"https://{HOST}/api/auth/login",
content=f'{{"username":"{USERNAME}","password":"{PASSWORD}","rememberMe":false}}'.encode(),
headers={"Content-Type": "application/json", "Accept-Encoding": ""},
)
res.raise_for_status()
csrf = res.headers.get("x-csrf-token")
def get_volumes():
s = client.get(f"{PRIVATE_BASE}/cameras/{camera_id}").raise_for_status().json()["speakerSettings"]
return s["volume"], s["speakerVolume"]
before = get_volumes()
print(f"Speaker volume before: volume={before[0]} speakerVolume={before[1]}")
client.patch(
f"{PRIVATE_BASE}/cameras/{camera_id}",
headers={"x-csrf-token": csrf},
json={"speakerSettings": {"volume": volume, "speakerVolume": volume}},
).raise_for_status()
after = get_volumes()
print(f"Speaker volume after: volume={after[0]} speakerVolume={after[1]}")
def main():
with httpx.Client(headers=HEADERS, verify=False) as client:
with httpx.Client(headers={"X-API-Key": API_KEY}, verify=False) as client:
cameras = client.get(f"{BASE}/cameras").raise_for_status().json()
camera = next(c for c in cameras if CAMERA_NAME in c["name"])
print(f"Camera: {camera['name']} ({camera['id']})")
set_speaker_volume(camera["id"], SPEAKER_VOLUME)
session = client.post(f"{BASE}/cameras/{camera['id']}/talkback-session").raise_for_status().json()
print(f"Talkback: {session['url']} ({session['codec']} {session['samplingRate']}Hz)")
proc = subprocess.Popen([
"ffmpeg", "-re", "-i", AUDIO_FILE,
"-af", f"volume={VOLUME}",
"-acodec", "libopus",
"-ar", str(session["samplingRate"]),
"-ac", "1",