From c26e0c18fb08587976ba3eefaa61641844867672 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Dec 2023 11:23:50 +0100 Subject: [PATCH] update for teams 2.0 --- pyproject.toml | 2 +- src/teams_ctrl.py | 74 ++++++++++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4d64962..001b01f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "teams-ctrl" -version = "0.1.0" +version = "0.1.5" description = "MS Teams control" authors = ["Patrick Moessler "] readme = "README.md" diff --git a/src/teams_ctrl.py b/src/teams_ctrl.py index 8d0485f..6093031 100644 --- a/src/teams_ctrl.py +++ b/src/teams_ctrl.py @@ -6,8 +6,8 @@ import json import time import asyncio from typing import Any -import serial_asyncio from argparse import ArgumentParser +import serial_asyncio from websockets.client import connect, WebSocketClientProtocol from pydantic import BaseModel, ValidationError @@ -56,22 +56,26 @@ class MeetingPermissions(BaseModel): class MeetingState(BaseModel): isMuted: bool = False - isCameraOn: bool = False isHandRaised: bool = False isInMeeting: bool = False isRecordingOn: bool = False isBackgroundBlurred: bool = False + isSharing: bool = False hasUnreadMessages: bool = False + isVideoOn: bool = False class MeetingUpdate(BaseModel): - meetingState: MeetingState + meetingState: MeetingState | None meetingPermissions: MeetingPermissions class TeamsMsg(BaseModel): - apiVersion: str - meetingUpdate: MeetingUpdate + requestId: int | None + meetingUpdate: MeetingUpdate | None + tokenRefresh: str | None + errorMsg: str | None + response: str | None class State: @@ -202,6 +206,8 @@ class TeamsCtrl: self.meeting_state: MeetingState | None = None + self.request_id: int = 0 + def set_next_state(self, next_state: str) -> None: self.current_state_name = next_state @@ -239,6 +245,8 @@ class TeamsCtrl: self.set_next_state(next_state=next_state) async def send_ws_cmd(self, cmd: dict[str, Any]) -> None: + cmd.update({"requestId": self.request_id}) + self.request_id += 1 msg = json.dumps(cmd) await self.ws.send(msg) @@ -246,30 +254,38 @@ class TeamsCtrl: async for raw_msg in self.ws: try: msg = TeamsMsg.parse_raw(raw_msg) - if self.meeting_state != msg.meetingUpdate.meetingState: - log.debug( - f"state changed:\n{self.meeting_state}\nto\n{msg.meetingUpdate.meetingState}" - ) - changes: dict[str, bool] = {} - if self.meeting_state is None: - changes = msg.meetingUpdate.meetingState.dict() - else: - old_s = self.meeting_state.dict() - new_s = msg.meetingUpdate.meetingState.dict() - for state, old_v in old_s.items(): - new_v = new_s[state] - if old_v != new_v: - changes[state] = new_v - next_state = self.current_state.handle_state_change(changes) - self.meeting_state = msg.meetingUpdate.meetingState - if next_state is not None: - self.set_next_state(next_state=next_state) + if ( + msg.meetingUpdate is not None + and msg.meetingUpdate.meetingState is not None + ): + if self.meeting_state != msg.meetingUpdate.meetingState: + log.debug( + f"state changed:\n{self.meeting_state}\nto\n{msg.meetingUpdate.meetingState}" + ) + changes: dict[str, bool] = {} + if self.meeting_state is None: + changes = msg.meetingUpdate.meetingState.dict() + else: + old_s = self.meeting_state.dict() + new_s = msg.meetingUpdate.meetingState.dict() + for state, old_v in old_s.items(): + new_v = new_s[state] + if old_v != new_v: + changes[state] = new_v + next_state = self.current_state.handle_state_change(changes) + self.meeting_state = msg.meetingUpdate.meetingState + if next_state is not None: + self.set_next_state(next_state=next_state) + elif msg.tokenRefresh is not None: + log.warning("got new token: %s" % msg.tokenRefresh) + else: + log.info(f"got valid but unhandled msg: {msg}") except ValidationError: if isinstance(raw_msg, bytes): m = raw_msg.decode("utf-8") else: m = raw_msg - log.info(f"got unknown meetingState: {m}") + log.info(f"got unknown teams msg: {m}") async def process_statemachine(self) -> None: while not self.loop.is_closed(): @@ -281,13 +297,13 @@ class TeamsCtrl: ) -async def amain() -> None: +async def amain(token: str, serial_port: str) -> None: loop = asyncio.get_event_loop() serial = await serial_asyncio.open_serial_connection( - loop=loop, url=args.port, baudrate=115200 + loop=loop, url=serial_port, baudrate=115200 ) - url = f"ws://localhost:8124?token={token}&protocol-version=1.0.0&manufacturer=TeamsCtrl&device=TeamsCtrl&app=TeamsCtrlApp&app-version=1.4" + url = f"ws://localhost:8124?token={token}&protocol-version=2.0.0&manufacturer=TeamsCtrl&device=TeamsCtrl&app=TeamsCtrlApp&app-version=1.5" ws = await connect(url) ctrl = TeamsCtrl(loop=loop, serial=serial, ws=ws) @@ -318,7 +334,7 @@ if __name__ == "__main__": logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("websockets.client").setLevel(logging.INFO) - with open(args.token) as tf: + with open(args.token, "r") as tf: token = tf.read().strip() - asyncio.run(amain()) + asyncio.run(amain(token, args.port))