723e0973032fbf1825a9513c4ccd62943352981a
[python_utils.git] / smart_home / config.py
1 #!/usr/bin/env python3
2
3 import re
4 from typing import List, Optional, Set
5
6 import argparse_utils
7 import config
8 import file_utils
9 import logical_search
10 import smart_home.device as device
11 import smart_home.lights as lights
12 import smart_home.outlets as outlets
13
14 parser = config.add_commandline_args(
15     f"Smart Home Config ({__file__})",
16     "Args related to the smart home config."
17 )
18 parser.add_argument(
19     '--smart_home_config_file_location',
20     default='/home/scott/bin/network_mac_addresses.txt',
21     metavar='FILENAME',
22     help='The location of network_mac_addresses.txt',
23     type=argparse_utils.valid_filename,
24 )
25
26
27 class SmartHomeConfig(object):
28     def __init__(
29             self,
30             config_file: Optional[str] = None,
31             filters: List[str] = ['smart'],
32     ) -> None:
33         if config_file is None:
34             config_file = config.config[
35                 'smart_home_config_file_location'
36             ]
37         assert file_utils.does_file_exist(config_file)
38         with open(config_file, "r") as f:
39             contents = f.readlines()
40
41         self._macs_by_name = {}
42         self._keywords_by_name = {}
43         self._keywords_by_mac = {}
44         self._names_by_mac = {}
45         self._corpus = logical_search.Corpus()
46
47         for line in contents:
48             line = line.rstrip("\n")
49             line = re.sub(r"#.*$", r"", line)
50             line = line.strip()
51             if line == "":
52                 continue
53             (mac, name, keywords) = line.split(",")
54             mac = mac.strip()
55             name = name.strip()
56             keywords = keywords.strip()
57
58             if filters is not None:
59                 for f in filters:
60                     if not f in keywords:
61                         continue
62
63             self._macs_by_name[name] = mac
64             self._keywords_by_name[name] = keywords
65             self._keywords_by_mac[mac] = keywords
66             self._names_by_mac[mac] = name
67             self.index_device(name, keywords, mac)
68
69     def index_device(self, name: str, keywords: str, mac: str) -> None:
70         properties = [("name", name)]
71         tags = set()
72         for kw in keywords.split():
73             if ":" in kw:
74                 key, value = kw.split(":")
75                 properties.append((key, value))
76             else:
77                 tags.add(kw)
78         device = logical_search.Document(
79             docid=mac,
80             tags=tags,
81             properties=properties,
82             reference=None,
83         )
84         self._corpus.add_doc(device)
85
86     def __repr__(self) -> str:
87         s = "Known devices:\n"
88         for name, keywords in self._keywords_by_name.items():
89             mac = self._macs_by_name[name]
90             s += f"  {name} ({mac}) => {keywords}\n"
91         return s
92
93     def get_keywords_by_name(self, name: str) -> Optional[device.Device]:
94         return self._keywords_by_name.get(name, None)
95
96     def get_macs_by_name(self, name: str) -> Set[str]:
97         retval = set()
98         for (mac, lname) in self._names_by_mac.items():
99             if name in lname:
100                 retval.add(mac)
101         return retval
102
103     def get_macs_by_keyword(self, keyword: str) -> Set[str]:
104         retval = set()
105         for (mac, keywords) in self._keywords_by_mac.items():
106             if keyword in keywords:
107                 retval.add(mac)
108         return retval
109
110     def get_device_by_name(self, name: str) -> Optional[device.Device]:
111         if name in self._macs_by_name:
112             return self.get_device_by_mac(self._macs_by_name[name])
113         return None
114
115     def get_all_devices(self) -> List[device.Device]:
116         retval = []
117         for (mac, kws) in self._keywords_by_mac.items():
118             if mac is not None:
119                 device = self.get_device_by_mac(mac)
120                 if device is not None:
121                     retval.append(device)
122         return retval
123
124     def get_device_by_mac(self, mac: str) -> Optional[device.Device]:
125         if mac in self._keywords_by_mac:
126             name = self._names_by_mac[mac]
127             kws = self._keywords_by_mac[mac]
128             if 'light' in kws.lower():
129                 if 'tplink' in kws.lower():
130                     return lights.TPLinkLight(name, mac, kws)
131                 elif 'tuya' in kws.lower():
132                     return lights.TuyaLight(name, mac, kws)
133                 elif 'goog' in kws.lower():
134                     return lights.GoogleLight(name, mac, kws)
135                 else:
136                     raise Exception(f'Unknown light device: {name}, {mac}, {kws}')
137             elif 'outlet' in kws.lower():
138                 if 'tplink' in kws.lower():
139                     if 'children' in kws.lower():
140                         return outlets.TPLinkOutletWithChildren(name, mac, kws)
141                     else:
142                         return outlets.TPLinkOutlet(name, mac, kws)
143                 elif 'goog' in kws.lower():
144                     return outlets.GoogleOutlet(name, mac, kws)
145                 else:
146                     raise Exception(f'Unknown outlet device: {name}, {mac}, {kws}')
147             else:
148                 return device.Device(name, mac, kws)
149         return None
150
151     def query(self, query: str) -> List[device.Device]:
152         """Evaluates a lighting query expression formed of keywords to search
153         for, logical operators (and, or, not), and parenthesis.
154         Returns a list of matching lights.
155         """
156         retval = []
157         results = self._corpus.query(query)
158         if results is not None:
159             for mac in results:
160                 if mac is not None:
161                     device = self.get_device_by_mac(mac)
162                     if device is not None:
163                         retval.append(device)
164         return retval