77
88from pyportainer .models .docker import DockerContainer
99
10- from homeassistant .components .sensor import SensorEntity , SensorEntityDescription
10+ from homeassistant .components .sensor import (
11+ EntityCategory ,
12+ SensorDeviceClass ,
13+ SensorEntity ,
14+ SensorEntityDescription ,
15+ SensorStateClass ,
16+ StateType ,
17+ )
18+ from homeassistant .const import UnitOfInformation
1119from homeassistant .core import HomeAssistant
1220from homeassistant .helpers .entity_platform import AddConfigEntryEntitiesCallback
1321
1422from .coordinator import PortainerConfigEntry , PortainerCoordinator
15- from .entity import PortainerContainerEntity , PortainerCoordinatorData
23+ from .entity import (
24+ PortainerContainerEntity ,
25+ PortainerCoordinatorData ,
26+ PortainerEndpointEntity ,
27+ )
28+
29+
30+ @dataclass (frozen = True , kw_only = True )
31+ class PortainerContainerSensorEntityDescription (SensorEntityDescription ):
32+ """Class to hold Portainer container sensor description."""
33+
34+ value_fn : Callable [[DockerContainer ], StateType ]
1635
1736
1837@dataclass (frozen = True , kw_only = True )
19- class PortainerSensorEntityDescription (SensorEntityDescription ):
20- """Class to hold Portainer sensor description."""
38+ class PortainerEndpointSensorEntityDescription (SensorEntityDescription ):
39+ """Class to hold Portainer endpoint sensor description."""
2140
22- value_fn : Callable [[DockerContainer ], str | None ]
41+ value_fn : Callable [[PortainerCoordinatorData ], StateType ]
2342
2443
25- CONTAINER_SENSORS : tuple [PortainerSensorEntityDescription , ...] = (
26- PortainerSensorEntityDescription (
44+ CONTAINER_SENSORS : tuple [PortainerContainerSensorEntityDescription , ...] = (
45+ PortainerContainerSensorEntityDescription (
2746 key = "image" ,
2847 translation_key = "image" ,
2948 value_fn = lambda data : data .image ,
3049 ),
3150)
51+ ENDPOINT_SENSORS : tuple [PortainerEndpointSensorEntityDescription , ...] = (
52+ PortainerEndpointSensorEntityDescription (
53+ key = "api_version" ,
54+ translation_key = "api_version" ,
55+ value_fn = lambda data : data .docker_version .api_version ,
56+ entity_category = EntityCategory .DIAGNOSTIC ,
57+ entity_registry_enabled_default = False ,
58+ ),
59+ PortainerEndpointSensorEntityDescription (
60+ key = "kernel_version" ,
61+ translation_key = "kernel_version" ,
62+ value_fn = lambda data : data .docker_version .kernel_version ,
63+ entity_category = EntityCategory .DIAGNOSTIC ,
64+ entity_registry_enabled_default = False ,
65+ ),
66+ PortainerEndpointSensorEntityDescription (
67+ key = "operating_system" ,
68+ translation_key = "operating_system" ,
69+ value_fn = lambda data : data .docker_info .os_type ,
70+ entity_category = EntityCategory .DIAGNOSTIC ,
71+ entity_registry_enabled_default = False ,
72+ ),
73+ PortainerEndpointSensorEntityDescription (
74+ key = "operating_system_version" ,
75+ translation_key = "operating_system_version" ,
76+ value_fn = lambda data : data .docker_info .os_version ,
77+ entity_category = EntityCategory .DIAGNOSTIC ,
78+ entity_registry_enabled_default = False ,
79+ ),
80+ PortainerEndpointSensorEntityDescription (
81+ key = "docker_version" ,
82+ translation_key = "docker_version" ,
83+ value_fn = lambda data : data .docker_info .server_version ,
84+ entity_category = EntityCategory .DIAGNOSTIC ,
85+ entity_registry_enabled_default = False ,
86+ ),
87+ PortainerEndpointSensorEntityDescription (
88+ key = "architecture" ,
89+ translation_key = "architecture" ,
90+ value_fn = lambda data : data .docker_info .architecture ,
91+ entity_category = EntityCategory .DIAGNOSTIC ,
92+ entity_registry_enabled_default = False ,
93+ ),
94+ PortainerEndpointSensorEntityDescription (
95+ key = "containers_count" ,
96+ translation_key = "containers_count" ,
97+ value_fn = lambda data : data .docker_info .containers ,
98+ entity_category = EntityCategory .DIAGNOSTIC ,
99+ entity_registry_enabled_default = False ,
100+ state_class = SensorStateClass .MEASUREMENT ,
101+ ),
102+ PortainerEndpointSensorEntityDescription (
103+ key = "containers_running" ,
104+ translation_key = "containers_running" ,
105+ value_fn = lambda data : data .docker_info .containers_running ,
106+ entity_category = EntityCategory .DIAGNOSTIC ,
107+ entity_registry_enabled_default = False ,
108+ state_class = SensorStateClass .MEASUREMENT ,
109+ ),
110+ PortainerEndpointSensorEntityDescription (
111+ key = "containers_stopped" ,
112+ translation_key = "containers_stopped" ,
113+ value_fn = lambda data : data .docker_info .containers_stopped ,
114+ entity_category = EntityCategory .DIAGNOSTIC ,
115+ entity_registry_enabled_default = False ,
116+ state_class = SensorStateClass .MEASUREMENT ,
117+ ),
118+ PortainerEndpointSensorEntityDescription (
119+ key = "containers_paused" ,
120+ translation_key = "containers_paused" ,
121+ value_fn = lambda data : data .docker_info .containers_paused ,
122+ entity_category = EntityCategory .DIAGNOSTIC ,
123+ entity_registry_enabled_default = False ,
124+ state_class = SensorStateClass .MEASUREMENT ,
125+ ),
126+ PortainerEndpointSensorEntityDescription (
127+ key = "images_count" ,
128+ translation_key = "images_count" ,
129+ value_fn = lambda data : data .docker_info .images ,
130+ entity_category = EntityCategory .DIAGNOSTIC ,
131+ entity_registry_enabled_default = False ,
132+ state_class = SensorStateClass .MEASUREMENT ,
133+ ),
134+ PortainerEndpointSensorEntityDescription (
135+ key = "memory_total" ,
136+ translation_key = "memory_total" ,
137+ value_fn = lambda data : data .docker_info .mem_total ,
138+ device_class = SensorDeviceClass .DATA_SIZE ,
139+ state_class = SensorStateClass .MEASUREMENT ,
140+ native_unit_of_measurement = UnitOfInformation .BYTES ,
141+ entity_category = EntityCategory .DIAGNOSTIC ,
142+ entity_registry_enabled_default = False ,
143+ ),
144+ PortainerEndpointSensorEntityDescription (
145+ key = "cpu_total" ,
146+ translation_key = "cpu_total" ,
147+ value_fn = lambda data : data .docker_info .ncpu ,
148+ entity_category = EntityCategory .DIAGNOSTIC ,
149+ entity_registry_enabled_default = False ,
150+ state_class = SensorStateClass .MEASUREMENT ,
151+ ),
152+ )
32153
33154
34155async def async_setup_entry (
@@ -38,29 +159,41 @@ async def async_setup_entry(
38159) -> None :
39160 """Set up Portainer sensors based on a config entry."""
40161 coordinator = entry .runtime_data
162+ entities : list [SensorEntity ] = []
163+
164+ for endpoint in coordinator .data .values ():
165+ entities .extend (
166+ PortainerEndpointSensor (
167+ coordinator ,
168+ entity_description ,
169+ endpoint ,
170+ )
171+ for entity_description in ENDPOINT_SENSORS
172+ )
41173
42- async_add_entities (
43- PortainerContainerSensor (
44- coordinator ,
45- entity_description ,
46- container ,
47- endpoint ,
174+ entities .extend (
175+ PortainerContainerSensor (
176+ coordinator ,
177+ entity_description ,
178+ container ,
179+ endpoint ,
180+ )
181+ for container in endpoint .containers .values ()
182+ for entity_description in CONTAINER_SENSORS
48183 )
49- for endpoint in coordinator .data .values ()
50- for container in endpoint .containers .values ()
51- for entity_description in CONTAINER_SENSORS
52- )
184+
185+ async_add_entities (entities )
53186
54187
55188class PortainerContainerSensor (PortainerContainerEntity , SensorEntity ):
56189 """Representation of a Portainer container sensor."""
57190
58- entity_description : PortainerSensorEntityDescription
191+ entity_description : PortainerContainerSensorEntityDescription
59192
60193 def __init__ (
61194 self ,
62195 coordinator : PortainerCoordinator ,
63- entity_description : PortainerSensorEntityDescription ,
196+ entity_description : PortainerContainerSensorEntityDescription ,
64197 device_info : DockerContainer ,
65198 via_device : PortainerCoordinatorData ,
66199 ) -> None :
@@ -76,8 +209,37 @@ def available(self) -> bool:
76209 return super ().available and self .endpoint_id in self .coordinator .data
77210
78211 @property
79- def native_value (self ) -> str | None :
212+ def native_value (self ) -> StateType :
80213 """Return the state of the sensor."""
81214 return self .entity_description .value_fn (
82215 self .coordinator .data [self .endpoint_id ].containers [self .device_id ]
83216 )
217+
218+
219+ class PortainerEndpointSensor (PortainerEndpointEntity , SensorEntity ):
220+ """Representation of a Portainer endpoint sensor."""
221+
222+ entity_description : PortainerEndpointSensorEntityDescription
223+
224+ def __init__ (
225+ self ,
226+ coordinator : PortainerCoordinator ,
227+ entity_description : PortainerEndpointSensorEntityDescription ,
228+ device_info : PortainerCoordinatorData ,
229+ ) -> None :
230+ """Initialize the Portainer endpoint sensor."""
231+ self .entity_description = entity_description
232+ super ().__init__ (device_info , coordinator )
233+
234+ self ._attr_unique_id = f"{ coordinator .config_entry .entry_id } _{ device_info .id } _{ entity_description .key } "
235+
236+ @property
237+ def available (self ) -> bool :
238+ """Return if the device is available."""
239+ return super ().available and self .device_id in self .coordinator .data
240+
241+ @property
242+ def native_value (self ) -> StateType :
243+ """Return the state of the sensor."""
244+ endpoint_data = self .coordinator .data [self ._device_info .endpoint .id ]
245+ return self .entity_description .value_fn (endpoint_data )
0 commit comments