@@ -125,17 +125,20 @@ class Characteristic:
125
125
126
126
__slots__ = (
127
127
"broker" ,
128
- "display_name " ,
129
- "properties " ,
128
+ "_display_name " ,
129
+ "_properties " ,
130
130
"type_id" ,
131
- "value " ,
131
+ "_value " ,
132
132
"getter_callback" ,
133
133
"setter_callback" ,
134
134
"service" ,
135
135
"_uuid_str" ,
136
136
"_loader_display_name" ,
137
137
"allow_invalid_client_values" ,
138
138
"unique_id" ,
139
+ "_to_hap_cache_with_value" ,
140
+ "_to_hap_cache" ,
141
+ "_always_null" ,
139
142
)
140
143
141
144
def __init__ (
@@ -169,34 +172,68 @@ def __init__(
169
172
# to True and handle converting the Auto state to Cool or Heat
170
173
# depending on the device.
171
174
#
175
+ self ._always_null = type_id in ALWAYS_NULL
172
176
self .allow_invalid_client_values = allow_invalid_client_values
173
- self .display_name = display_name
174
- self .properties : Dict [str , Any ] = properties
177
+ self ._display_name = display_name
178
+ self ._properties : Dict [str , Any ] = properties
175
179
self .type_id = type_id
176
- self .value = self ._get_default_value ()
180
+ self ._value = self ._get_default_value ()
177
181
self .getter_callback : Optional [Callable [[], Any ]] = None
178
182
self .setter_callback : Optional [Callable [[Any ], None ]] = None
179
183
self .service : Optional ["Service" ] = None
180
184
self .unique_id = unique_id
181
185
self ._uuid_str = uuid_to_hap_type (type_id )
182
186
self ._loader_display_name : Optional [str ] = None
187
+ self ._to_hap_cache_with_value : Optional [Dict [str , Any ]] = None
188
+ self ._to_hap_cache : Optional [Dict [str , Any ]] = None
189
+
190
+ @property
191
+ def display_name (self ) -> Optional [str ]:
192
+ """Return the display name of the characteristic."""
193
+ return self ._display_name
194
+
195
+ @display_name .setter
196
+ def display_name (self , value : str ) -> None :
197
+ """Set the display name of the characteristic."""
198
+ self ._display_name = value
199
+ self ._clear_cache ()
200
+
201
+ @property
202
+ def value (self ) -> Any :
203
+ """Return the value of the characteristic."""
204
+ return self ._value
205
+
206
+ @value .setter
207
+ def value (self , value : Any ) -> None :
208
+ """Set the value of the characteristic."""
209
+ self ._value = value
210
+ self ._clear_cache ()
211
+
212
+ @property
213
+ def properties (self ) -> Dict [str , Any ]:
214
+ """Return the properties of the characteristic.
215
+
216
+ Properties should not be modified directly. Use override_properties instead.
217
+ """
218
+ return self ._properties
183
219
184
220
def __repr__ (self ) -> str :
185
221
"""Return the representation of the characteristic."""
186
222
return (
187
- f"<characteristic display_name={ self .display_name } unique_id={ self .unique_id } "
188
- f"value={ self .value } properties={ self .properties } >"
223
+ f"<characteristic display_name={ self ._display_name } unique_id={ self .unique_id } "
224
+ f"value={ self ._value } properties={ self ._properties } >"
189
225
)
190
226
191
227
def _get_default_value (self ) -> Any :
192
228
"""Return default value for format."""
193
- if self .type_id in ALWAYS_NULL :
229
+ if self ._always_null :
194
230
return None
195
231
196
- if self .properties .get (PROP_VALID_VALUES ):
197
- return min (self .properties [PROP_VALID_VALUES ].values ())
232
+ valid_values = self ._properties .get (PROP_VALID_VALUES )
233
+ if valid_values :
234
+ return min (valid_values .values ())
198
235
199
- value = HAP_FORMAT_DEFAULTS [self .properties [PROP_FORMAT ]]
236
+ value = HAP_FORMAT_DEFAULTS [self ._properties [PROP_FORMAT ]]
200
237
return self .to_valid_value (value )
201
238
202
239
def get_value (self ) -> Any :
@@ -207,43 +244,47 @@ def get_value(self) -> Any:
207
244
if self .getter_callback :
208
245
# pylint: disable=not-callable
209
246
self .value = self .to_valid_value (value = self .getter_callback ())
210
- return self .value
247
+ return self ._value
211
248
212
249
def valid_value_or_raise (self , value : Any ) -> None :
213
250
"""Raise ValueError if PROP_VALID_VALUES is set and the value is not present."""
214
- if self .type_id in ALWAYS_NULL :
251
+ if self ._always_null :
215
252
return
216
- valid_values = self .properties .get (PROP_VALID_VALUES )
253
+ valid_values = self ._properties .get (PROP_VALID_VALUES )
217
254
if not valid_values :
218
255
return
219
256
if value in valid_values .values ():
220
257
return
221
- error_msg = f"{ self .display_name } : value={ value } is an invalid value."
258
+ error_msg = f"{ self ._display_name } : value={ value } is an invalid value."
222
259
logger .error (error_msg )
223
260
raise ValueError (error_msg )
224
261
225
262
def to_valid_value (self , value : Any ) -> Any :
226
263
"""Perform validation and conversion to valid value."""
227
- if self .properties [PROP_FORMAT ] == HAP_FORMAT_STRING :
228
- value = str (value )[
229
- : self .properties .get (HAP_REPR_MAX_LEN , DEFAULT_MAX_LENGTH )
230
- ]
231
- elif self .properties [PROP_FORMAT ] == HAP_FORMAT_BOOL :
232
- value = bool (value )
233
- elif self .properties [PROP_FORMAT ] in HAP_FORMAT_NUMERICS :
264
+ properties = self ._properties
265
+ prop_format = properties [PROP_FORMAT ]
266
+
267
+ if prop_format == HAP_FORMAT_STRING :
268
+ return str (value )[: properties .get (HAP_REPR_MAX_LEN , DEFAULT_MAX_LENGTH )]
269
+
270
+ if prop_format == HAP_FORMAT_BOOL :
271
+ return bool (value )
272
+
273
+ if prop_format in HAP_FORMAT_NUMERICS :
234
274
if not isinstance (value , (int , float )):
235
275
error_msg = (
236
- f"{ self .display_name } : value={ value } is not a numeric value."
276
+ f"{ self ._display_name } : value={ value } is not a numeric value."
237
277
)
238
278
logger .error (error_msg )
239
279
raise ValueError (error_msg )
240
- min_step = self . properties .get (PROP_MIN_STEP )
280
+ min_step = properties .get (PROP_MIN_STEP )
241
281
if value and min_step :
242
282
value = round (min_step * round (value / min_step ), 14 )
243
- value = min (self .properties .get (PROP_MAX_VALUE , value ), value )
244
- value = max (self .properties .get (PROP_MIN_VALUE , value ), value )
245
- if self .properties [PROP_FORMAT ] != HAP_FORMAT_FLOAT :
246
- value = int (value )
283
+ value = min (properties .get (PROP_MAX_VALUE , value ), value )
284
+ value = max (properties .get (PROP_MIN_VALUE , value ), value )
285
+ if prop_format != HAP_FORMAT_FLOAT :
286
+ return int (value )
287
+
247
288
return value
248
289
249
290
def override_properties (
@@ -264,23 +305,30 @@ def override_properties(
264
305
if not properties and not valid_values :
265
306
raise ValueError ("No properties or valid_values specified to override." )
266
307
308
+ self ._clear_cache ()
309
+
267
310
if properties :
268
311
_validate_properties (properties )
269
- self .properties .update (properties )
312
+ self ._properties .update (properties )
270
313
271
314
if valid_values :
272
- self .properties [PROP_VALID_VALUES ] = valid_values
315
+ self ._properties [PROP_VALID_VALUES ] = valid_values
273
316
274
- if self .type_id in ALWAYS_NULL :
317
+ if self ._always_null :
275
318
self .value = None
276
319
return
277
320
278
321
try :
279
- self .value = self .to_valid_value (self .value )
280
- self .valid_value_or_raise (self .value )
322
+ self .value = self .to_valid_value (self ._value )
323
+ self .valid_value_or_raise (self ._value )
281
324
except ValueError :
282
325
self .value = self ._get_default_value ()
283
326
327
+ def _clear_cache (self ) -> None :
328
+ """Clear the cached HAP representation."""
329
+ self ._to_hap_cache = None
330
+ self ._to_hap_cache_with_value = None
331
+
284
332
def set_value (self , value : Any , should_notify : bool = True ) -> None :
285
333
"""Set the given raw value. It is checked if it is a valid value.
286
334
@@ -300,14 +348,14 @@ def set_value(self, value: Any, should_notify: bool = True) -> None:
300
348
subscribed clients. Notify will be performed if the broker is set.
301
349
:type should_notify: bool
302
350
"""
303
- logger .debug ("set_value: %s to %s" , self .display_name , value )
351
+ logger .debug ("set_value: %s to %s" , self ._display_name , value )
304
352
value = self .to_valid_value (value )
305
353
self .valid_value_or_raise (value )
306
- changed = self .value != value
354
+ changed = self ._value != value
307
355
self .value = value
308
356
if changed and should_notify and self .broker :
309
357
self .notify ()
310
- if self .type_id in ALWAYS_NULL :
358
+ if self ._always_null :
311
359
self .value = None
312
360
313
361
def client_update_value (
@@ -318,27 +366,27 @@ def client_update_value(
318
366
Change self.value to value and call callback.
319
367
"""
320
368
original_value = value
321
- if self . type_id not in ALWAYS_NULL or original_value is not None :
369
+ if not self . _always_null or original_value is not None :
322
370
value = self .to_valid_value (value )
323
371
if not self .allow_invalid_client_values :
324
372
self .valid_value_or_raise (value )
325
373
logger .debug (
326
374
"client_update_value: %s to %s (original: %s) from client: %s" ,
327
- self .display_name ,
375
+ self ._display_name ,
328
376
value ,
329
377
original_value ,
330
378
sender_client_addr ,
331
379
)
332
- previous_value = self .value
380
+ previous_value = self ._value
333
381
self .value = value
334
382
response = None
335
383
if self .setter_callback :
336
384
# pylint: disable=not-callable
337
385
response = self .setter_callback (value )
338
- changed = self .value != previous_value
386
+ changed = self ._value != previous_value
339
387
if changed :
340
388
self .notify (sender_client_addr )
341
- if self .type_id in ALWAYS_NULL :
389
+ if self ._always_null :
342
390
self .value = None
343
391
return response
344
392
@@ -352,49 +400,59 @@ def notify(self, sender_client_addr: Optional[Tuple[str, int]] = None) -> None:
352
400
self .broker .publish (self .value , self , sender_client_addr , immediate )
353
401
354
402
# pylint: disable=invalid-name
355
- def to_HAP (self ) -> Dict [str , Any ]:
403
+ def to_HAP (self , include_value : bool = True ) -> Dict [str , Any ]:
356
404
"""Create a HAP representation of this Characteristic.
357
405
358
406
Used for json serialization.
359
407
360
408
:return: A HAP representation.
361
409
:rtype: dict
362
410
"""
411
+ if include_value :
412
+ if self ._to_hap_cache_with_value is not None and not self .getter_callback :
413
+ return self ._to_hap_cache_with_value
414
+ elif self ._to_hap_cache is not None :
415
+ return self ._to_hap_cache
416
+
417
+ properties = self ._properties
418
+ permissions = properties [PROP_PERMISSIONS ]
419
+ prop_format = properties [PROP_FORMAT ]
363
420
hap_rep = {
364
421
HAP_REPR_IID : self .broker .iid_manager .get_iid (self ),
365
422
HAP_REPR_TYPE : self ._uuid_str ,
366
- HAP_REPR_PERM : self . properties [ PROP_PERMISSIONS ] ,
367
- HAP_REPR_FORMAT : self . properties [ PROP_FORMAT ] ,
423
+ HAP_REPR_PERM : permissions ,
424
+ HAP_REPR_FORMAT : prop_format ,
368
425
}
369
426
# HAP_REPR_DESC (description) is optional and takes up
370
427
# quite a bit of space in the payload. Only include it
371
428
# if it has been changed from the default loader version
372
- if (
373
- not self ._loader_display_name
374
- or self ._loader_display_name != self .display_name
375
- ):
376
- hap_rep [HAP_REPR_DESC ] = self .display_name
377
-
378
- value = self .get_value ()
379
- if self .properties [PROP_FORMAT ] in HAP_FORMAT_NUMERICS :
429
+ loader_display_name = self ._loader_display_name
430
+ display_name = self ._display_name
431
+ if not loader_display_name or loader_display_name != display_name :
432
+ hap_rep [HAP_REPR_DESC ] = display_name
433
+
434
+ if prop_format in HAP_FORMAT_NUMERICS :
380
435
hap_rep .update (
381
- {
382
- k : self .properties [k ]
383
- for k in PROP_NUMERIC .intersection (self .properties )
384
- }
436
+ {k : properties [k ] for k in PROP_NUMERIC .intersection (properties )}
385
437
)
386
438
387
- if PROP_VALID_VALUES in self . properties :
439
+ if PROP_VALID_VALUES in properties :
388
440
hap_rep [HAP_REPR_VALID_VALUES ] = sorted (
389
- self . properties [PROP_VALID_VALUES ].values ()
441
+ properties [PROP_VALID_VALUES ].values ()
390
442
)
391
- elif self . properties [ PROP_FORMAT ] == HAP_FORMAT_STRING :
392
- max_length = self . properties .get (HAP_REPR_MAX_LEN , DEFAULT_MAX_LENGTH )
443
+ elif prop_format == HAP_FORMAT_STRING :
444
+ max_length = properties .get (HAP_REPR_MAX_LEN , DEFAULT_MAX_LENGTH )
393
445
if max_length != DEFAULT_MAX_LENGTH :
394
446
hap_rep [HAP_REPR_MAX_LEN ] = max_length
395
- if HAP_PERMISSION_READ in self .properties [PROP_PERMISSIONS ]:
396
- hap_rep [HAP_REPR_VALUE ] = value
397
447
448
+ if include_value and HAP_PERMISSION_READ in permissions :
449
+ hap_rep [HAP_REPR_VALUE ] = self .get_value ()
450
+
451
+ if not include_value :
452
+ self ._to_hap_cache = hap_rep
453
+ elif not self .getter_callback :
454
+ # Only cache if there is no getter_callback
455
+ self ._to_hap_cache_with_value = hap_rep
398
456
return hap_rep
399
457
400
458
@classmethod
0 commit comments