Compare commits

..

No commits in common. "main" and "async" have entirely different histories.
main ... async

2 changed files with 30 additions and 46 deletions

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "teams-ctrl" name = "teams-ctrl"
version = "0.1.5" version = "0.1.0"
description = "MS Teams control" description = "MS Teams control"
authors = ["Patrick Moessler <pub@asaril.de>"] authors = ["Patrick Moessler <pub@asaril.de>"]
readme = "README.md" readme = "README.md"

View file

@ -6,8 +6,8 @@ import json
import time import time
import asyncio import asyncio
from typing import Any from typing import Any
from argparse import ArgumentParser
import serial_asyncio import serial_asyncio
from argparse import ArgumentParser
from websockets.client import connect, WebSocketClientProtocol from websockets.client import connect, WebSocketClientProtocol
from pydantic import BaseModel, ValidationError from pydantic import BaseModel, ValidationError
@ -56,26 +56,22 @@ class MeetingPermissions(BaseModel):
class MeetingState(BaseModel): class MeetingState(BaseModel):
isMuted: bool = False isMuted: bool = False
isCameraOn: bool = False
isHandRaised: bool = False isHandRaised: bool = False
isInMeeting: bool = False isInMeeting: bool = False
isRecordingOn: bool = False isRecordingOn: bool = False
isBackgroundBlurred: bool = False isBackgroundBlurred: bool = False
isSharing: bool = False
hasUnreadMessages: bool = False hasUnreadMessages: bool = False
isVideoOn: bool = False
class MeetingUpdate(BaseModel): class MeetingUpdate(BaseModel):
meetingState: MeetingState | None meetingState: MeetingState
meetingPermissions: MeetingPermissions meetingPermissions: MeetingPermissions
class TeamsMsg(BaseModel): class TeamsMsg(BaseModel):
requestId: int | None apiVersion: str
meetingUpdate: MeetingUpdate | None meetingUpdate: MeetingUpdate
tokenRefresh: str | None
errorMsg: str | None
response: str | None
class State: class State:
@ -206,8 +202,6 @@ class TeamsCtrl:
self.meeting_state: MeetingState | None = None self.meeting_state: MeetingState | None = None
self.request_id: int = 0
def set_next_state(self, next_state: str) -> None: def set_next_state(self, next_state: str) -> None:
self.current_state_name = next_state self.current_state_name = next_state
@ -245,8 +239,6 @@ class TeamsCtrl:
self.set_next_state(next_state=next_state) self.set_next_state(next_state=next_state)
async def send_ws_cmd(self, cmd: dict[str, Any]) -> None: 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) msg = json.dumps(cmd)
await self.ws.send(msg) await self.ws.send(msg)
@ -254,38 +246,30 @@ class TeamsCtrl:
async for raw_msg in self.ws: async for raw_msg in self.ws:
try: try:
msg = TeamsMsg.parse_raw(raw_msg) msg = TeamsMsg.parse_raw(raw_msg)
if ( if self.meeting_state != msg.meetingUpdate.meetingState:
msg.meetingUpdate is not None log.debug(
and msg.meetingUpdate.meetingState is not None f"state changed:\n{self.meeting_state}\nto\n{msg.meetingUpdate.meetingState}"
): )
if self.meeting_state != msg.meetingUpdate.meetingState: changes: dict[str, bool] = {}
log.debug( if self.meeting_state is None:
f"state changed:\n{self.meeting_state}\nto\n{msg.meetingUpdate.meetingState}" changes = msg.meetingUpdate.meetingState.dict()
) else:
changes: dict[str, bool] = {} old_s = self.meeting_state.dict()
if self.meeting_state is None: new_s = msg.meetingUpdate.meetingState.dict()
changes = msg.meetingUpdate.meetingState.dict() for state, old_v in old_s.items():
else: new_v = new_s[state]
old_s = self.meeting_state.dict() if old_v != new_v:
new_s = msg.meetingUpdate.meetingState.dict() changes[state] = new_v
for state, old_v in old_s.items(): next_state = self.current_state.handle_state_change(changes)
new_v = new_s[state] self.meeting_state = msg.meetingUpdate.meetingState
if old_v != new_v: if next_state is not None:
changes[state] = new_v self.set_next_state(next_state=next_state)
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: except ValidationError:
if isinstance(raw_msg, bytes): if isinstance(raw_msg, bytes):
m = raw_msg.decode("utf-8") m = raw_msg.decode("utf-8")
else: else:
m = raw_msg m = raw_msg
log.info(f"got unknown teams msg: {m}") log.info(f"got unknown meetingState: {m}")
async def process_statemachine(self) -> None: async def process_statemachine(self) -> None:
while not self.loop.is_closed(): while not self.loop.is_closed():
@ -297,13 +281,13 @@ class TeamsCtrl:
) )
async def amain(token: str, serial_port: str) -> None: async def amain() -> None:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
serial = await serial_asyncio.open_serial_connection( serial = await serial_asyncio.open_serial_connection(
loop=loop, url=serial_port, baudrate=115200 loop=loop, url=args.port, baudrate=115200
) )
url = f"ws://localhost:8124?token={token}&protocol-version=2.0.0&manufacturer=TeamsCtrl&device=TeamsCtrl&app=TeamsCtrlApp&app-version=1.5" url = f"ws://localhost:8124?token={token}&protocol-version=1.0.0&manufacturer=TeamsCtrl&device=TeamsCtrl&app=TeamsCtrlApp&app-version=1.4"
ws = await connect(url) ws = await connect(url)
ctrl = TeamsCtrl(loop=loop, serial=serial, ws=ws) ctrl = TeamsCtrl(loop=loop, serial=serial, ws=ws)
@ -334,7 +318,7 @@ if __name__ == "__main__":
logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO)
logging.getLogger("websockets.client").setLevel(logging.INFO) logging.getLogger("websockets.client").setLevel(logging.INFO)
with open(args.token, "r") as tf: with open(args.token) as tf:
token = tf.read().strip() token = tf.read().strip()
asyncio.run(amain(token, args.port)) asyncio.run(amain())