working title recovery from playlist and error correction

This commit is contained in:
Emanuele Trabattoni
2026-08-26 16:14:10 +02:00
parent 278c3c10d5
commit 7da0f851c6
5 changed files with 173 additions and 42 deletions
+10 -1
View File
@@ -15,6 +15,15 @@ class MessageHeaderWrapper(BaseModel):
def to_datetime(cls, value: str) -> datetime:
return datetime.fromtimestamp(int(value)/1000)
def __eq__(self, other):
if not isinstance(other, MessageHeaderWrapper):
return NotImplemented
return (
self.model_dump(exclude={"Timestamp"})
== other.model_dump(exclude={"Timestamp"})
)
class SMSMessage(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
path: ClassVar[List[str]]
@@ -162,4 +171,4 @@ type SPLMetadataElem = SPLMetadata
class SPLList(SMSMessage):
path = ['show','playlist','info','list']
SPLMetadata: List[SPLMetadataElem] | None = None
SPLMetadata: List[SPLMetadataElem]
+65 -34
View File
@@ -2,14 +2,16 @@ import logging
import os
import sys
import time
from typing import Any, List, Optional, Type, TypeVar
from typing import Dict, List, Optional, Type, TypeVar
from uuid import UUID
import projrequest
from commands import ShowStatus, SMSMessage, SPLList, StorageInfo
from commands import ShowStatus, SMSMessage, SPLList, SPLMetadata, StorageInfo
from paho.mqtt import client as mqtt_client
from paho.mqtt.enums import CallbackAPIVersion
from pydantic import BaseModel, Field
from requests import Timeout
from utils import SignalHandler
T = TypeVar("T", bound=SMSMessage)
@@ -50,10 +52,10 @@ def main() -> int:
mqtt.connect(host=env['MQTT_HOST'], port=int(env['MQTT_PORT']))
mqtt.loop_start()
except Exception as e:
LOGGER.error(e)
LOGGER.error("%s", e)
return 1
finally:
LOGGER.info(f"MQTT Connected: [{env['MQTT_HOST']}:{env['MQTT_PORT']}]")
LOGGER.info("MQTT Connected: [%s:%s]", env['MQTT_HOST'], env['MQTT_PORT'])
##############################
########## MAIN LOOP #########
@@ -64,7 +66,7 @@ def main() -> int:
ip=env['PROJECTOR_IP'],
username=env['PROJECTOR_USER'],
password=env['PROJECTOR_PASSWORD']
)
)
sony_device: HAdevice = HAdevice (
identifiers=["sony_digitalcinema_projector"],
@@ -162,10 +164,10 @@ def main() -> int:
))
sensors.append(HAsensor (
name="Play Title",
unique_id=f"{sony_device.short_name}_play_title",
state_topic=f"({sony_device.short_name})/play_title",
value_template="{{ value_json.PlayTitle }}",
name="Event Title",
unique_id=f"{sony_device.short_name}_event_title",
state_topic=f"({sony_device.short_name})/show_info",
value_template="{{ value_json.ShowStatusDetail.CurrentEventTitle }}",
icon="mdi:movie",
device=sony_device,
request=ShowStatus
@@ -176,31 +178,60 @@ def main() -> int:
payload=s.model_dump_json(indent=2),
retain=True)
while handler.running:
try:
now:float = time.time()
tt: Any = None
ret: None | SMSMessage = None
spls: SPLList | None = projector.request(SPLList)
for s in sensors:
if tt != s.request:
tt = s.request
ret = projector.request(s.request)
if ret:
mqtt.publish(topic=s.state_topic,
payload=ret.model_dump_json(
indent=2,
exclude={"MessageHeader"},exclude_unset=True),
retain=True)
playlists: SPLList | None = None
states: Dict[Type, SMSMessage | None] = {s.request: None for s in sensors}
last: float = time.time()
cycle_time: float = last - now
LOGGER.debug(f"Cycle Time: {cycle_time:4.3f}")
if cycle_time < INTERVAL:
time.sleep(INTERVAL-cycle_time)
while handler.running:
now:float = time.time()
try:
resp: None | SMSMessage = None
# update projector states
for s in states:
resp = projector.request(s)
# no responde
if resp is None:
continue
# request playlist list (hehehe)
if playlists is None:
playlists = projector.request(SPLList)
# find playlist title
if isinstance(resp, ShowStatus) and isinstance(playlists, SPLList):
playlist_id: UUID = resp.ShowStatusDetail.Id
playlist: List[SPLMetadata] = list(filter(lambda p: p.Id == playlist_id, playlists.SPLMetadata))
if len(playlist):
resp.ShowStatusDetail.CurrentEventTitle = playlist.pop().Title
else:
resp.ShowStatusDetail.CurrentEventTitle = "Unknown or CPL"
# continue after update of playlist title in case nothing else changed
if resp == states[s]:
continue
# update sensor states
states[s] = resp
# send only one message for sensor with this type of request
for s in sensors:
if isinstance(resp, s.request):
sts: SMSMessage = resp
mqtt.publish(topic=s.state_topic,
payload=sts.model_dump_json(
indent=2,
exclude={"MessageHeader"},
exclude_unset=True),
retain=True)
break
except Timeout as te:
LOGGER.debug("Projector is probably shutdown: %s", te)
except Exception as e:
print(f"Unexpected exception: [{e}]")
LOGGER.error("Unexpected exception: [%s]", e)
return 1
last: float = time.time()
cycle_time: float = last - now
LOGGER.debug("Cycle Time: %4.3f", cycle_time)
if cycle_time < INTERVAL:
time.sleep(INTERVAL-cycle_time)
mqtt.disconnect()
return 0
@@ -228,9 +259,9 @@ if __name__ == "__main__":
cl.setFormatter(formatter)
LOGGER.addHandler(cl)
LOGGER.warning(f"Routermon started on: {time.asctime()}")
LOGGER.info(f"Routermon BUILD: {env.get("VER", "Test")}")
LOGGER.warning("Projmon started on: %s", time.asctime())
LOGGER.info("Projmon BUILD: %s", env.get("VER", "Test"))
while main():
LOGGER.error("Main thread exited unexpectedly")
time.sleep(15)
+6 -7
View File
@@ -1,15 +1,15 @@
import requests
import urllib3
# Suppress only the single warning from urllib3.
urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning)
import xmltodict
from requests.auth import HTTPBasicAuth
from os import getcwd
import json
from typing import Any, Dict, List, Type, TypeVar
import xmltodict
from commands import SMSMessage
from requests.auth import HTTPBasicAuth
from typing import Dict, List, Any, Type, TypeVar
T = TypeVar("T", bound=SMSMessage)
TEST = False
@@ -25,7 +25,6 @@ class ProjectorConnection():
self.username = username
self.password = password
self.auth = HTTPBasicAuth(username=self.username, password=self.password)
pass
def get(self, path: List[str], params: Dict[str, str] | None = None) -> Dict[str, Any]:
response: requests.Response = requests.get(
@@ -53,8 +52,8 @@ class ProjectorConnection():
rv = self.get(cls.path, cls.params)
if isinstance(rv, dict) and rv.items():
rv = rv['SMSMessage']
type: str = rv['MessageHeader']['Type']
rv.update(**rv['MessageBody'][type])
tt: str = rv['MessageHeader']['Type']
rv.update(**rv['MessageBody'][tt])
del rv['MessageBody']
del rv['@xmlns']
#print(json.dumps(rv, indent = 2))
+52
View File
@@ -0,0 +1,52 @@
<!--
{
"GET": {
"scheme": "https",
"host": "192.168.31.10",
"filename": "/status/alert/log",
"query": {
"formatDate": "true"
},
"remote": {
"Indirizzo": "192.168.31.10:443"
}
}
}
-->
<SMSMessage xmlns="http://xmlns.sony.net/d-cinema/sms/2007b">
<MessageHeader>
<Id>-1</Id>
<Type>EventLog</Type>
<Timestamp>1787752196399</Timestamp>
<Source>LSM-100v2</Source>
</MessageHeader>
<MessageBody>
<EventLog>
<LogEventItem>
<Time>1787750400</Time>
<Index>0</Index>
<Level>ERROR</Level>
<Code>3050</Code>
<Message>Failed to connect to CP750.</Message>
<TimeForFormatDate>2026-08-26T15:20:00.000+02:00</TimeForFormatDate>
</LogEventItem>
<LogEventItem>
<Time>1787750421</Time>
<Index>1</Index>
<Level>WARN</Level>
<Code>3014</Code>
<Message>Projector not connected</Message>
<TimeForFormatDate>2026-08-26T15:20:21.000+02:00</TimeForFormatDate>
</LogEventItem>
<LogEventItem>
<Time>1787750421</Time>
<Index>2</Index>
<Level>WARN</Level>
<Code>3015</Code>
<Message>PCIe cable not connected</Message>
<TimeForFormatDate>2026-08-26T15:20:21.000+02:00</TimeForFormatDate>
</LogEventItem>
</EventLog>
</MessageBody>
</SMSMessage>
+40
View File
@@ -0,0 +1,40 @@
<!--
{
"GET": {
"scheme": "https",
"host": "192.168.31.10",
"filename": "/command/projector/maintenance/lamp/info/get",
"query": {
"lampId": "05"
},
"remote": {
"Indirizzo": "192.168.31.10:443"
}
}
}
-->
<SMSMessage xmlns="http://xmlns.sony.net/d-cinema/sms/2007b">
<MessageHeader>
<Id>-1</Id>
<Type>PrjLampInformation</Type>
<Timestamp>1787752366456</Timestamp>
<Source>LSM-100v2</Source>
</MessageHeader>
<MessageBody>
<PrjLampInformation>
<ProjectorId></ProjectorId>
<LampId></LampId>
<HoursOfUse></HoursOfUse>
<LampSerialNumber></LampSerialNumber>
<LampMaximumWattage>0</LampMaximumWattage>
<LampWattage>-1</LampWattage>
<LampLuminanceDecayRatio></LampLuminanceDecayRatio>
<LampPower></LampPower>
<LampSelect></LampSelect>
<LuminanceShutterControl></LuminanceShutterControl>
<LampOn></LampOn>
<LampStatus></LampStatus>
</PrjLampInformation>
</MessageBody>
</SMSMessage>