Compare commits

...
3 Commits
Author SHA1 Message Date
Obbart 68b4e10db3 added hdd status classes 2026-08-29 23:03:46 +02:00
Obbart 70a623a451 working with lamp information 2026-08-29 22:44:10 +02:00
Obbart d196ceb159 integrated lamp information 2026-08-29 21:18:10 +02:00
8 changed files with 318 additions and 104 deletions
View File
+29 -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,30 @@ class SPLList(SMSMessage):
path = ['show','playlist','info','list']
SPLMetadata: List[SPLMetadataElem]
class PrjLampInformation(SMSMessage):
path = ['command', 'projector', 'maintenance', 'lamp', 'info', 'get']
ProjectorId: int | None
LampId: int | None
HoursOfUse: int | None
LampSerialNumber: str | None
LampMaximumWattage: int | None
LampWattage: int | None
LampLuminanceDecayRatio: int | None
LampPower: float | None
LampSelect: int | None
LuminanceShutterControl: int | None
LampOn: bool | None
LampStatus: str | None
class HDDStatusWrapper(BaseModel):
Name: str
IsSpareSlot: bool
Status: str
type HDDStatusElem = HDDStatusWrapper
class RAIDStatusDetail(SMSMessage):
RAIDStatus: str
HDDStatus: HDDStatusElem
+157 -82
View File
@@ -2,11 +2,11 @@ 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
@@ -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,224 @@ 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"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"{sony_device.short_name}/{lamp_device.short_name}/state",
value_template="{{ value_json.HoursOfUse }}",
device_class="duration",
state_class="measurement",
unit_of_measurement="h",
icon="mdi:timer-sand-complete",
request=PrjLampInformation,
device=lamp_device
))
lamp_device.sensors.append(HAsensor (
name=f"Power",
unique_id=f"{lamp_device.short_name}_power",
state_topic=f"{sony_device.short_name}/{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"{sony_device.short_name}/{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"{sony_device.short_name}/{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
lamp_num: int = 0
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)
# 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
for s in states:
# include lamp id in the requests parameters
if s == PrjLampInformation:
resp = projector.request(s, reqParams={"lampId": lamp_num})
lamp_num += 1
else:
resp.ShowStatusDetail.CurrentEventTitle = "Unknown or CPL"
resp = projector.request(s)
# no respondse
if resp is None:
continue
# continue after update of playlist title in case nothing else changed
if resp == states[s]:
continue
# request playlist list (hehehe)
if playlists is None:
playlists = projector.request(SPLList)
# 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
# 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"
# send only one message for sensor with this type of request
last_cls: Type = SMSMessage
# filter sensors of this device per this type of request
for sens in filter(lambda x: x.request == s, d.sensors):
if last_cls == sens.request:
continue
if isinstance(resp, SMSMessage):
sts: SMSMessage = resp
mqtt.publish(topic=sens.state_topic,
payload=sts.model_dump_json(
indent=1,
exclude={"MessageHeader"},
exclude_unset=True))
last_cls = sens.request
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']
+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>