integrated lamp information

This commit is contained in:
2026-08-29 21:18:10 +02:00
parent 4762ad446b
commit d196ceb159
9 changed files with 336 additions and 105 deletions
View File
+18 -2
View File
@@ -27,7 +27,7 @@ class MessageHeaderWrapper(BaseModel):
class SMSMessage(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
path: ClassVar[List[str]]
params: ClassVar[Dict[str, Any] | None] = None
params: ClassVar[Dict[str, str]]
MessageHeader: MessageHeaderWrapper
@@ -135,7 +135,7 @@ class StorageInfo(SMSMessage):
FreeSpace: int
path = ['status', 'storage', 'info']
params = {'area': 'DCP'}
params = {'area': 'DCP'}
@model_validator(mode='before')
@classmethod
@@ -172,3 +172,19 @@ class SPLList(SMSMessage):
path = ['show','playlist','info','list']
SPLMetadata: List[SPLMetadataElem]
class PrjLampInformation(SMSMessage):
path = ['command', 'projector', 'maintenance', 'lamp', 'info', 'get']
ProjectorId: int
LampId: int
HoursOfUse: int
LampSerialNumber: str
LampMaximumWattage: int
LampWattage: int
LampLuminanceDecayRatio: int
LampPower: float
LampSelect: int
LuminanceShutterControl: int
LampOn: bool
LampStatus: str
+149 -83
View File
@@ -2,17 +2,17 @@ import logging
import os
import sys
import time
from typing import Dict, List, Optional, Type, TypeVar
from typing import Dict, List, Optional, Type, TypeVar, Set, Any
from uuid import UUID
import projrequest
from commands import ShowStatus, SMSMessage, SPLList, SPLMetadata, StorageInfo
from commands import ShowStatus, SMSMessage, SPLList, SPLMetadata, StorageInfo, PrjLampInformation
from paho.mqtt import client as mqtt_client
from paho.mqtt.enums import CallbackAPIVersion
from pydantic import BaseModel, Field
from requests import Timeout, ConnectionError
from pyutils.utils import SignalHandler
from utils import SignalHandler
T = TypeVar("T", bound=SMSMessage)
@@ -22,13 +22,6 @@ LOGGER: logging.Logger
DISCOVERY_PREFIX = "homeassistant"
class HAdevice(BaseModel):
identifiers: List[str]
name: str
manufacturer: str
model: str
short_name: str = Field(exclude=True)
class HAsensor(BaseModel):
name: str
unique_id: str
@@ -38,9 +31,19 @@ class HAsensor(BaseModel):
device_class: Optional[str] = None
state_class: Optional[str] = None
icon: Optional[str] = None
device: HAdevice
device: Any
sensor_type: str = Field(exclude = True, default="sensor")
request: Type = Field(exclude=True)
class HAdevice(BaseModel):
identifiers: List[str]
name: str
manufacturer: str
model: str
via_device: str | None = None
sensors: List[HAsensor] = Field(exclude=True, default_factory=list)
short_name: str = Field(exclude=True)
##################
###### MAIN ######
##################
@@ -76,152 +79,215 @@ def main() -> int:
short_name="sony_projector"
)
sensors: List[HAsensor] = []
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Free DCP Storage",
unique_id=f"{sony_device.short_name}_free_storage",
state_topic=f"({sony_device.short_name})/storage_info",
state_topic=f"{sony_device.short_name}/storage_info",
value_template="{{ value_json.FreeSpace }}",
device_class="data_size",
state_class="measurement",
icon="mdi:harddisk",
unit_of_measurement="B",
device=sony_device,
request=StorageInfo
request=StorageInfo,
device=sony_device
))
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Used DCP Storage",
unique_id=f"{sony_device.short_name}_used_storage",
state_topic=f"({sony_device.short_name})/storage_info",
state_topic=f"{sony_device.short_name}/storage_info",
value_template="{{ value_json.SpaceInUse }}",
device_class="data_size",
state_class="measurement",
icon="mdi:harddisk",
unit_of_measurement="B",
device=sony_device,
request=StorageInfo
request=StorageInfo,
device=sony_device
))
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Play State",
unique_id=f"{sony_device.short_name}_play_state",
state_topic=f"({sony_device.short_name})/show_info",
state_topic=f"{sony_device.short_name}/show_info",
value_template="{{ value_json.PlayState }}",
icon="mdi:play-pause",
device=sony_device,
request=ShowStatus
request=ShowStatus,
device=sony_device
))
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Playback Mode",
unique_id=f"{sony_device.short_name}_playback_mode",
state_topic=f"({sony_device.short_name})/show_info",
state_topic=f"{sony_device.short_name}/show_info",
value_template="{{ value_json.PlayBackMode }}",
icon="mdi:play-circle",
device=sony_device,
request=ShowStatus
request=ShowStatus,
device=sony_device
))
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Total Duration",
unique_id=f"{sony_device.short_name}_total_duration",
state_topic=f"({sony_device.short_name})/show_info",
state_topic=f"{sony_device.short_name}/show_info",
value_template="{{ value_json.ShowStatusDetail.TotalDuration }}",
unit_of_measurement="ms",
device_class="duration",
state_class="measurement",
icon="mdi:timer",
device=sony_device,
request=ShowStatus
request=ShowStatus,
device=sony_device
))
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Elapsed Time",
unique_id=f"{sony_device.short_name}_elapsed_time",
state_topic=f"({sony_device.short_name})/show_info",
state_topic=f"{sony_device.short_name}/show_info",
value_template="{{ value_json.ShowStatusDetail.ElapsedTime }}",
unit_of_measurement="ms",
device_class="duration",
state_class="measurement",
icon="mdi:timer",
device=sony_device,
request=ShowStatus
request=ShowStatus,
device=sony_device
))
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Remaining Time",
unique_id=f"{sony_device.short_name}_remaining_time",
state_topic=f"({sony_device.short_name})/show_info",
state_topic=f"{sony_device.short_name}/show_info",
value_template="{{ value_json.ShowStatusDetail.RemainingTime }}",
unit_of_measurement="ms",
device_class="duration",
state_class="measurement",
icon="mdi:timer",
device=sony_device,
request=ShowStatus
request=ShowStatus,
device=sony_device
))
sensors.append(HAsensor (
sony_device.sensors.append(HAsensor (
name="Event Title",
unique_id=f"{sony_device.short_name}_event_title",
state_topic=f"({sony_device.short_name})/show_info",
state_topic=f"{sony_device.short_name}/show_info",
value_template="{{ value_json.ShowStatusDetail.CurrentEventTitle }}",
icon="mdi:movie",
device=sony_device,
request=ShowStatus
request=ShowStatus,
device=sony_device
))
for s in sensors:
mqtt.publish(topic=f"{DISCOVERY_PREFIX}/sensor/{s.unique_id}/config",
payload=s.model_dump_json(indent=2),
retain=True)
N_LAMPS: List[str] = ["A1", "A2", "A3", "B1", "B2", "B3"]
lamp_devices: List[HAdevice] = []
for l in N_LAMPS:
lamp_device: HAdevice = HAdevice(
name=f"Projector Lamp {l}",
identifiers=[f"projector_lamp_{l}"],
manufacturer="Sony",
model="Lamp",
short_name=f"{sony_device.short_name}_lamp_{l}",
via_device=sony_device.identifiers[0]
)
lamp_device.sensors.append(HAsensor (
name=f"Hours of Use",
unique_id=f"{lamp_device.short_name}_hours",
state_topic=f"{lamp_device.short_name}/state",
value_template="{{ value_json.HoursOfUse }}",
device_class="uptime",
state_class="total_increasing",
unit_of_measurement="h",
icon="mdi:timer-play-outline",
request=PrjLampInformation,
device=lamp_device
))
lamp_device.sensors.append(HAsensor (
name=f"Power",
unique_id=f"{lamp_device.short_name}_power",
state_topic=f"{lamp_device.short_name}/state",
value_template="{{ value_json.LampPower }}",
device_class="power",
state_class="measurement",
unit_of_measurement="W",
icon="mdi:speedometer-slow",
request=PrjLampInformation,
device=lamp_device
))
lamp_device.sensors.append(HAsensor (
name=f"Status",
unique_id=f"{lamp_device.short_name}_status",
state_topic=f"{lamp_device.short_name}/state",
value_template="{{ value_json.LampStatus }}",
state_class="measurement",
icon="mdi:ab-testing",
request=PrjLampInformation,
device=lamp_device
))
lamp_device.sensors.append(HAsensor (
name=f"Lamp ON",
unique_id=f"{lamp_device.short_name}_lampon",
state_topic=f"{lamp_device.short_name}/state",
value_template="{{ value_json.LampOn }}",
device_class="power",
icon="mdi:lightbulb-on-outline",
sensor_type="binary_sensor",
request=PrjLampInformation,
device=lamp_device
))
lamp_devices.append(lamp_device)
all_devices: List[HAdevice] = [sony_device, *lamp_devices]
for d in all_devices:
for s in d.sensors:
mqtt.publish(topic=f"{DISCOVERY_PREFIX}/{s.sensor_type}/{s.unique_id}/config",
payload=s.model_dump_json(indent=1, exclude_none=True),
retain=True)
time.sleep(0.2)
playlists: SPLList | None = None
states: Dict[Type, SMSMessage | None] = {s.request: None for s in sensors}
while handler.running:
now:float = time.time()
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
for d in all_devices:
states: Set[Type] = set([s.request for s in d.sensors])
# request playlist list (hehehe)
if playlists is None:
playlists = projector.request(SPLList)
for s in states:
resp = projector.request(s)
# no respondse
if resp is None:
continue
# 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"
# request playlist list (hehehe)
if playlists is None:
playlists = projector.request(SPLList)
# continue after update of playlist title in case nothing else changed
if resp == states[s]:
continue
# 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"
# 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
# send only one message for sensor with this type of request
for s in d.sensors:
if isinstance(resp, SMSMessage):
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, ConnectionError) as ce:
LOGGER.debug("Projector is probably shutdown: %s", ce)
except Exception as e:
+2 -2
View File
@@ -43,13 +43,13 @@ class ProjectorConnection():
return xmltodict.parse(response.text.replace("%2F", "/").replace("%3A", ":")) # Common containert for all messages
return {}
def request(self, cls: Type[T]) -> T | None:
def request(self, cls: Type[T], reqParams: Dict[str, Any]= {}) -> T | None:
rv: dict
if TEST:
with open(f'{getcwd()}/projmon/xmlresponses/showstatus.xml', 'r') as fd:
rv = xmltodict.parse(fd.read().replace("%2F", "/").replace("%3A", ":"))
else:
rv = self.get(cls.path, cls.params)
rv = self.get(cls.path, cls.params | reqParams)
if isinstance(rv, dict) and rv.items():
rv = rv['SMSMessage']
tt: str = rv['MessageHeader']['Type']
+37
View File
@@ -0,0 +1,37 @@
import signal
from logging import Logger
from influxdb_client_3 import Point
class SignalHandler:
running: bool
logger: Logger
def __init__(self, logger):
self.running: bool = True
self.logger: Logger = logger
signal.signal(signal.SIGINT, self._handle_sigint)
signal.signal(signal.SIGTERM, self._handle_sigint)
def _handle_sigint(self, signum, frame):
self.logger.info(f"Received SIGNAL: {signal.strsignal(signum)}")
self.running = False
def dict2Point(measurement: str, fields: dict, tags: dict | None = None) -> Point:
p = Point(measurement)
for k,v in fields.items():
p.field(k,v)
if tags:
for k,v in tags.items():
p.tag(k,v)
return p
def convertInt(d: dict) -> dict:
for k,v in d.items():
if str.isdecimal(v):
d[k] = int(v)
return d
def convertIntList(l: list[dict]) -> list[dict]:
for n,d in enumerate(l):
l[n] = convertInt(d)
return l
+63
View File
@@ -0,0 +1,63 @@
<!--
{
"GET": {
"scheme": "https",
"host": "192.168.31.10",
"filename": "/status/storage/hdd",
"remote": {
"Indirizzo": "192.168.31.10:443"
}
}
}
-->
<?xml version="1.0" encoding="UTF-8"?>
<SMSMessage xmlns="http://xmlns.sony.net/d-cinema/sms/2007b">
<MessageHeader>
<Id>-1</Id>
<Type>RAIDStatusDetail</Type>
<Timestamp>1788009071658</Timestamp>
<Source>LSM-100v2</Source>
</MessageHeader>
<MessageBody>
<RAIDStatusDetail>
<RAIDStatus>NORMAL</RAIDStatus>
<HDDStatus>
<Name>HDD1</Name>
<IsSpareSlot>false</IsSpareSlot>
<Status>NORMAL</Status>
</HDDStatus>
<HDDStatus>
<Name>HDD2</Name>
<IsSpareSlot>false</IsSpareSlot>
<Status>NORMAL</Status>
</HDDStatus>
<HDDStatus>
<Name>HDD3</Name>
<IsSpareSlot>false</IsSpareSlot>
<Status>NORMAL</Status>
</HDDStatus>
<HDDStatus>
<Name>HDD4</Name>
<IsSpareSlot>false</IsSpareSlot>
<Status>NORMAL</Status>
</HDDStatus>
<HDDStatus>
<Name>HDD5</Name>
<IsSpareSlot>false</IsSpareSlot>
<Status>NORMAL</Status>
</HDDStatus>
<HDDStatus>
<Name>HDD6</Name>
<IsSpareSlot>false</IsSpareSlot>
<Status>NORMAL</Status>
</HDDStatus>
<HDDStatus>
<Name>HDD7</Name>
<IsSpareSlot>true</IsSpareSlot>
<Status>SPARE</Status>
</HDDStatus>
</RAIDStatusDetail>
</MessageBody>
</SMSMessage>
+14 -14
View File
@@ -13,28 +13,28 @@
}
}
-->
<?xml version="1.0" encoding="UTF-8"?>
<SMSMessage xmlns="http://xmlns.sony.net/d-cinema/sms/2007b">
<MessageHeader>
<Id>-1</Id>
<Type>PrjLampInformation</Type>
<Timestamp>1787752366456</Timestamp>
<Timestamp>1788008745066</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>
<ProjectorId>0</ProjectorId>
<LampId>2</LampId>
<HoursOfUse>1763</HoursOfUse>
<LampSerialNumber>CVK1616</LampSerialNumber>
<LampMaximumWattage>330</LampMaximumWattage>
<LampWattage>80</LampWattage>
<LampLuminanceDecayRatio>0</LampLuminanceDecayRatio>
<LampPower>100.0</LampPower>
<LampSelect>63</LampSelect>
<LuminanceShutterControl>3</LuminanceShutterControl>
<LampOn>true</LampOn>
<LampStatus>NORMAL</LampStatus>
</PrjLampInformation>
</MessageBody>
</SMSMessage>
+3 -4
View File
@@ -11,13 +11,12 @@
}
-->
<?xml version="1.0" encoding="UTF-8"?>
<SMSMessage xmlns="http://xmlns.sony.net/d-cinema/sms/2007b">
<MessageHeader>
<Id>-1</Id>
<Type>PowerStatusList</Type>
<Timestamp>1770226327272</Timestamp>
<Timestamp>1788008671488</Timestamp>
<Source>LSM-100v2</Source>
</MessageHeader>
<MessageBody>
@@ -32,7 +31,7 @@
</PowerStatus>
<PowerStatus>
<Device>MARRIAGE</Device>
<State>UNKNOWN</State>
<State>ON</State>
</PowerStatus>
<PowerStatus>
<Device>MB</Device>
@@ -44,7 +43,7 @@
</PowerStatus>
<PowerStatus>
<Device>PRJ</Device>
<State>UNKNOWN</State>
<State>WARM_UP</State>
</PowerStatus>
<PowerStatus>
<Device>ENCLOSURE</Device>
+50
View File
@@ -0,0 +1,50 @@
<!--
{
"GET": {
"scheme": "https",
"host": "192.168.31.10",
"filename": "/status/ups/list",
"remote": {
"Indirizzo": "192.168.31.10:443"
}
}
}
-->
<?xml version="1.0" encoding="UTF-8"?>
<SMSMessage xmlns="http://xmlns.sony.net/d-cinema/sms/2007b">
<MessageHeader>
<Id>-1</Id>
<Type>UPSDetailedStatus</Type>
<Timestamp>1788009071658</Timestamp>
<Source>LSM-100v2</Source>
</MessageHeader>
<MessageBody>
<UPSDetailedStatus>
<Model>TRIPP LITE UPS</Model>
<Status>ON_LINE</Status>
<BatteryInfo>
<CapacityPercentage>100</CapacityPercentage>
<Temperature>34</Temperature>
<RemainingRuntime>90</RemainingRuntime>
<IsBatteryNeedsReplacing>false</IsBatteryNeedsReplacing>
</BatteryInfo>
<InputInfo>
<Voltage>0</Voltage>
<FrequencyHz>0</FrequencyHz>
<LastTransferReason></LastTransferReason>
</InputInfo>
<OutputInfo>
<Voltage>0</Voltage>
<FrequencyHz>0</FrequencyHz>
<LoadPercentage>0</LoadPercentage>
<CurrentAmps>0</CurrentAmps>
</OutputInfo>
<DiagnosticInfo>
<LastSelftTestResult></LastSelftTestResult>
<LastSelftTestDate>1970-01-01</LastSelftTestDate>
</DiagnosticInfo>
</UPSDetailedStatus>
</MessageBody>
</SMSMessage>