11
11
You may obtain a copy of the license at
12
12
http://www.apache.org/licenses/LICENSE-2.0
13
13
"""
14
+ import collections .abc
14
15
import functools
15
16
import inspect
16
17
import logging
17
18
from dataclasses import dataclass
18
19
from datetime import datetime , timedelta
19
20
from enum import Enum , auto
20
- from typing import Any , Dict , List , Optional
21
+ from typing import Any , Dict , List , Optional , Set
21
22
22
23
from .emeterstatus import EmeterStatus
23
24
from .exceptions import SmartDeviceException
@@ -51,6 +52,16 @@ class WifiNetwork:
51
52
rssi : Optional [int ] = None
52
53
53
54
55
+ def merge (d , u ):
56
+ """Update dict recursively."""
57
+ for k , v in u .items ():
58
+ if isinstance (v , collections .abc .Mapping ):
59
+ d [k ] = merge (d .get (k , {}), v )
60
+ else :
61
+ d [k ] = v
62
+ return d
63
+
64
+
54
65
def requires_update (f ):
55
66
"""Indicate that `update` should be called before accessing this method.""" # noqa: D202
56
67
if inspect .iscoroutinefunction (f ):
@@ -204,6 +215,11 @@ def _create_request(
204
215
205
216
return request
206
217
218
+ def _verify_emeter (self ) -> None :
219
+ """Raise an exception if there is no emeter."""
220
+ if not self .has_emeter :
221
+ raise SmartDeviceException ("Device has no emeter" )
222
+
207
223
async def _query_helper (
208
224
self , target : str , cmd : str , arg : Optional [Dict ] = None , child_ids = None
209
225
) -> Any :
@@ -240,13 +256,17 @@ async def _query_helper(
240
256
241
257
return result
242
258
259
+ @property # type: ignore
260
+ @requires_update
261
+ def features (self ) -> Set [str ]:
262
+ """Return a set of features that the device supports."""
263
+ return set (self .sys_info ["feature" ].split (":" ))
264
+
243
265
@property # type: ignore
244
266
@requires_update
245
267
def has_emeter (self ) -> bool :
246
268
"""Return True if device has an energy meter."""
247
- sys_info = self .sys_info
248
- features = sys_info ["feature" ].split (":" )
249
- return "ENE" in features
269
+ return "ENE" in self .features
250
270
251
271
async def get_sys_info (self ) -> Dict [str , Any ]:
252
272
"""Retrieve system information."""
@@ -374,10 +394,8 @@ def location(self) -> Dict:
374
394
@requires_update
375
395
def rssi (self ) -> Optional [int ]:
376
396
"""Return WiFi signal strenth (rssi)."""
377
- sys_info = self .sys_info
378
- if "rssi" in sys_info :
379
- return int (sys_info ["rssi" ])
380
- return None
397
+ rssi = self .sys_info .get ("rssi" )
398
+ return None if rssi is None else int (rssi )
381
399
382
400
@property # type: ignore
383
401
@requires_update
@@ -410,16 +428,12 @@ async def set_mac(self, mac):
410
428
@requires_update
411
429
def emeter_realtime (self ) -> EmeterStatus :
412
430
"""Return current energy readings."""
413
- if not self .has_emeter :
414
- raise SmartDeviceException ("Device has no emeter" )
415
-
431
+ self ._verify_emeter ()
416
432
return EmeterStatus (self ._last_update [self .emeter_type ]["get_realtime" ])
417
433
418
434
async def get_emeter_realtime (self ) -> EmeterStatus :
419
435
"""Retrieve current energy readings."""
420
- if not self .has_emeter :
421
- raise SmartDeviceException ("Device has no emeter" )
422
-
436
+ self ._verify_emeter ()
423
437
return EmeterStatus (await self ._query_helper (self .emeter_type , "get_realtime" ))
424
438
425
439
def _create_emeter_request (self , year : int = None , month : int = None ):
@@ -429,23 +443,12 @@ def _create_emeter_request(self, year: int = None, month: int = None):
429
443
if month is None :
430
444
month = datetime .now ().month
431
445
432
- import collections .abc
433
-
434
- def update (d , u ):
435
- """Update dict recursively."""
436
- for k , v in u .items ():
437
- if isinstance (v , collections .abc .
10000
Mapping ):
438
- d [k ] = update (d .get (k , {}), v )
439
- else :
440
- d [k ] = v
441
- return d
442
-
443
446
req : Dict [str , Any ] = {}
444
- update (req , self ._create_request (self .emeter_type , "get_realtime" ))
445
- update (
447
+ merge (req , self ._create_request (self .emeter_type , "get_realtime" ))
448
+ merge (
446
449
req , self ._create_request (self .emeter_type , "get_monthstat" , {"year" : year })
447
450
)
448
- update (
451
+ merge (
449
452
req ,
450
453
self ._create_request (
451
454
self .emeter_type , "get_daystat" , {"month" : month , "year" : year }
@@ -458,9 +461,7 @@ def update(d, u):
458
461
@requires_update
459
462
def emeter_today (self ) -> Optional [float ]:
460
463
"""Return today's energy consumption in kWh."""
461
- if not self .has_emeter :
462
- raise SmartDeviceException ("Device has no emeter" )
463
-
464
+ self ._verify_emeter ()
464
465
raw_data = self ._last_update [self .emeter_type ]["get_daystat" ]["day_list" ]
465
466
data = self ._emeter_convert_emeter_data (raw_data )
466
467
today = datetime .now ().day
@@ -474,9 +475,7 @@ def emeter_today(self) -> Optional[float]:
474
475
@requires_update
475
476
def emeter_this_month (self ) -> Optional [float ]:
476
477
"""Return this month's energy consumption in kWh."""
477
- if not self .has_emeter :
478
- raise SmartDeviceException ("Device has no emeter" )
479
-
478
+ self ._verify_emeter ()
480
479
raw_data = self ._last_update [self .emeter_type ]["get_monthstat" ]["month_list" ]
481
480
data = self ._emeter_convert_emeter_data (raw_data )
482
481
current_month = datetime .now ().month
@@ -516,9 +515,7 @@ async def get_emeter_daily(
516
515
:param kwh: return usage in kWh (default: True)
517
516
:return: mapping of day of month to value
518
517
"""
519
- if not self .has_emeter :
520
- raise SmartDeviceException ("Device has no emeter" )
521
-
518
+ self ._verify_emeter ()
522
519
if year is None :
523
520
year = datetime .now ().year
524
521
if month is None :
@@ -538,9 +535,7 @@ async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
538
535
:param kwh: return usage in kWh (default: True)
539
536
:return: dict: mapping of month to value
540
537
"""
541
- if not self .has_emeter :
542
- raise SmartDeviceException ("Device has no emeter" )
543
-
538
+ self ._verify_emeter ()
544
539
if year is None :
545
540
year = datetime .now ().year
546
541
@@ -553,17 +548,13 @@ async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
553
548
@requires_update
554
549
async def erase_emeter_stats (self ) -> Dict :
555
550
"""Erase energy meter statistics."""
556
- if not self .has_emeter :
557
- raise SmartDeviceException ("Device has no emeter" )
558
-
551
+ self ._verify_emeter ()
559
552
return await self ._query_helper (self .emeter_type , "erase_emeter_stat" , None )
560
553
561
554
@requires_update
562
555
async def current_consumption (self ) -> float :
563
556
"""Get the current power consumption in Watt."""
564
- if not self .has_emeter :
565
- raise SmartDeviceException ("Device has no emeter" )
566
-
557
+ self ._verify_emeter ()
567
558
response = EmeterStatus (await self .get_emeter_realtime ())
568
559
return float (response ["power" ])
569
560
0 commit comments