update for teams 2.0
This commit is contained in:
parent
4f10c493ac
commit
c26e0c18fb
2 changed files with 46 additions and 30 deletions
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "teams-ctrl"
|
name = "teams-ctrl"
|
||||||
version = "0.1.0"
|
version = "0.1.5"
|
||||||
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"
|
||||||
|
|
|
@ -6,8 +6,8 @@ import json
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import serial_asyncio
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
import serial_asyncio
|
||||||
from websockets.client import connect, WebSocketClientProtocol
|
from websockets.client import connect, WebSocketClientProtocol
|
||||||
from pydantic import BaseModel, ValidationError
|
from pydantic import BaseModel, ValidationError
|
||||||
|
|
||||||
|
@ -56,22 +56,26 @@ 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
|
meetingState: MeetingState | None
|
||||||
meetingPermissions: MeetingPermissions
|
meetingPermissions: MeetingPermissions
|
||||||
|
|
||||||
|
|
||||||
class TeamsMsg(BaseModel):
|
class TeamsMsg(BaseModel):
|
||||||
apiVersion: str
|
requestId: int | None
|
||||||
meetingUpdate: MeetingUpdate
|
meetingUpdate: MeetingUpdate | None
|
||||||
|
tokenRefresh: str | None
|
||||||
|
errorMsg: str | None
|
||||||
|
response: str | None
|
||||||
|
|
||||||
|
|
||||||
class State:
|
class State:
|
||||||
|
@ -202,6 +206,8 @@ 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
|
||||||
|
|
||||||
|
@ -239,6 +245,8 @@ 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)
|
||||||
|
|
||||||
|
@ -246,30 +254,38 @@ 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 self.meeting_state != msg.meetingUpdate.meetingState:
|
if (
|
||||||
log.debug(
|
msg.meetingUpdate is not None
|
||||||
f"state changed:\n{self.meeting_state}\nto\n{msg.meetingUpdate.meetingState}"
|
and msg.meetingUpdate.meetingState is not None
|
||||||
)
|
):
|
||||||
changes: dict[str, bool] = {}
|
if self.meeting_state != msg.meetingUpdate.meetingState:
|
||||||
if self.meeting_state is None:
|
log.debug(
|
||||||
changes = msg.meetingUpdate.meetingState.dict()
|
f"state changed:\n{self.meeting_state}\nto\n{msg.meetingUpdate.meetingState}"
|
||||||
else:
|
)
|
||||||
old_s = self.meeting_state.dict()
|
changes: dict[str, bool] = {}
|
||||||
new_s = msg.meetingUpdate.meetingState.dict()
|
if self.meeting_state is None:
|
||||||
for state, old_v in old_s.items():
|
changes = msg.meetingUpdate.meetingState.dict()
|
||||||
new_v = new_s[state]
|
else:
|
||||||
if old_v != new_v:
|
old_s = self.meeting_state.dict()
|
||||||
changes[state] = new_v
|
new_s = msg.meetingUpdate.meetingState.dict()
|
||||||
next_state = self.current_state.handle_state_change(changes)
|
for state, old_v in old_s.items():
|
||||||
self.meeting_state = msg.meetingUpdate.meetingState
|
new_v = new_s[state]
|
||||||
if next_state is not None:
|
if old_v != new_v:
|
||||||
self.set_next_state(next_state=next_state)
|
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:
|
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 meetingState: {m}")
|
log.info(f"got unknown teams msg: {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():
|
||||||
|
@ -281,13 +297,13 @@ class TeamsCtrl:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def amain() -> None:
|
async def amain(token: str, serial_port: str) -> 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=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)
|
ws = await connect(url)
|
||||||
|
|
||||||
ctrl = TeamsCtrl(loop=loop, serial=serial, ws=ws)
|
ctrl = TeamsCtrl(loop=loop, serial=serial, ws=ws)
|
||||||
|
@ -318,7 +334,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) as tf:
|
with open(args.token, "r") as tf:
|
||||||
token = tf.read().strip()
|
token = tf.read().strip()
|
||||||
|
|
||||||
asyncio.run(amain())
|
asyncio.run(amain(token, args.port))
|
||||||
|
|
Loading…
Reference in a new issue