Adds Arper, cleans up other stuff.
authorScott Gasch <[email protected]>
Fri, 29 Oct 2021 17:33:07 +0000 (10:33 -0700)
committerScott Gasch <[email protected]>
Fri, 29 Oct 2021 17:33:07 +0000 (10:33 -0700)
arper.py [new file with mode: 0644]
collect/bidict.py
smart_home/cameras.py
smart_home/device.py
smart_home/lights.py
smart_home/outlets.py

diff --git a/arper.py b/arper.py
new file mode 100644 (file)
index 0000000..73d1dd6
--- /dev/null
+++ b/arper.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+
+import datetime
+import logging
+import os
+from typing import Any, Optional
+
+import argparse_utils
+from collect.bidict import BiDict
+import config
+import exec_utils
+import persistent
+import string_utils
+
+logger = logging.getLogger(__name__)
+
+cfg = config.add_commandline_args(
+    f'MAC <--> IP Address mapping table cache ({__file__})',
+    'Commandline args related to MAC <--> IP Address mapping',
+)
+cfg.add_argument(
+    '--arper_cache_location',
+    default=f'{os.environ["HOME"]}/cache/.arp_table_cache',
+    metavar='FILENAME',
+    help='Where to cache the kernel ARP table',
+)
+cfg.add_argument(
+    '--arp_cache_max_staleness',
+    type=argparse_utils.valid_duration,
+    default=datetime.timedelta(seconds=60 * 5),
+    metavar='DURATION',
+    help='Max acceptable age of the kernel arp table cache'
+)
+
+
[email protected]_autoloaded_singleton()
+class Arper(persistent.Persistent):
+    def __init__(self, cached_state: Optional[BiDict[str, str]] = None) -> None:
+        self.state = BiDict()
+        if cached_state is not None:
+            logger.debug('Loading Arper map from cached state.')
+            self.state = cached_state
+        else:
+            logger.debug('No usable cached state; calling /usr/sbin/arp')
+            output = exec_utils.cmd(
+                '/usr/sbin/arp -a',
+                timeout_seconds=5.0
+            )
+            for line in output.split('\n'):
+                line = str(line, 'ascii')
+                ip = string_utils.extract_ip_v4(line)
+                mac = string_utils.extract_mac_address(line)
+                mac = mac.lower()
+                logger.debug(f'    {mac} => {ip}')
+                self.state[mac] = ip
+
+    def get_ip_by_mac(self, mac: str) -> Optional[str]:
+        mac = mac.lower()
+        return self.state.get(mac, None)
+
+    def get_mac_by_ip(self, ip: str) -> Optional[str]:
+        return self.state.inverse.get(ip, None)
+
+    def save(self) -> bool:
+        logger.debug(f'Persisting state to {config.config["arp_cache_location"]}')
+        with open(config.config['arp_cache_location'], 'w') as wf:
+            for (mac, ip) in self.state.items():
+                mac = mac.lower()
+                print(f'{mac}, {ip}', file=wf)
+
+    @classmethod
+    def load(cls) -> Any:
+        if persistent.was_file_written_within_n_seconds(
+                config.config['arp_cache_location'],
+                config.config['arp_cache_max_staleness'].total_seconds(),
+        ):
+            logger.debug(f'Loading state from {config.config["arp_cache_location"]}')
+            cached_state = BiDict()
+            with open(config.config['arp_cache_location'], 'r') as rf:
+                for line in rd.readline():
+                    (mac, ip) = line.split(',')
+                    mac = mac.strip()
+                    mac = mac.lower()
+                    ip = ip.strip()
+                    cached_state[mac] = ip
+            return Arper(cached_state)
+        logger.debug('No usable saved state found')
+        return None
index 1fa66dc81fe6bd64e16b80a8fa49bea7502c46db..8153e54446c8a4c5a36f96ba85e0b93c9afafe18 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-class bidict(dict):
+class BiDict(dict):
     def __init__(self, *args, **kwargs):
         """
         A class that stores both a Mapping between keys and values and
@@ -9,7 +9,7 @@ class bidict(dict):
         is possible to have several keys with the same value, using
         the inverse map returns a sequence of keys.
 
-        >>> d = bidict()
+        >>> d = BiDict()
         >>> d['a'] = 1
         >>> d['b'] = 2
         >>> d['c'] = 2
index 963f54ef089f6fdaf6d7fa3f041912ac229dc881..40850a9fa1230013bde7782a1b23f26077b93dab 100644 (file)
@@ -2,25 +2,13 @@
 
 """Utilities for dealing with the webcams."""
 
-from abc import abstractmethod
-import datetime
-import json
 import logging
-import os
-import re
-import subprocess
-import sys
-from typing import Any, Dict, List, Optional, Set
-
-import argparse_utils
-import config
-import logging_utils
+
 import smart_home.device as dev
-from google_assistant import ask_google, GoogleResponse
-from decorator_utils import timeout, memoized
 
 logger = logging.getLogger(__name__)
 
+
 class BaseCamera(dev.Device):
     camera_mapping = {
         'cabin_drivewaycam': 'cabin_driveway',
index 27860c5bbcc75bdbb97defabf820f0a601a9e0f6..0953b8dadccc886376c164fe1bef6a2bb7dd9c96 100644 (file)
@@ -1,7 +1,8 @@
 #!/usr/bin/env python3
 
 import re
-from typing import Any, List, Optional, Tuple
+from typing import List, Optional
+
 
 class Device(object):
     def __init__(
@@ -24,6 +25,9 @@ class Device(object):
     def get_mac(self) -> str:
         return self.mac
 
+    def get_ip(self) -> str:
+        pass
+
     def get_keywords(self) -> Optional[List[str]]:
         return self.kws
 
index 07521398307a9234eaac8d2e977b30def714a40d..54467223b9b1cc36c5edabe9d81df187cc1a409c 100644 (file)
@@ -2,7 +2,7 @@
 
 """Utilities for dealing with the smart lights."""
 
-from abc import ABC, abstractmethod
+from abc import abstractmethod
 import datetime
 import json
 import logging
@@ -10,7 +10,7 @@ import os
 import re
 import subprocess
 import sys
-from typing import Any, Dict, List, Optional, Set
+from typing import Any, Dict, List, Optional
 
 import tinytuya as tt
 
index 81e10a71fa0cd4960c2e5bf1a03789c0663375e3..f34d574ec0c721bb597124a90369215309208ec2 100644 (file)
@@ -10,7 +10,7 @@ import os
 import re
 import subprocess
 import sys
-from typing import Any, Dict, List, Optional, Set
+from typing import Dict, List, Optional
 
 import argparse_utils
 import config