From 58407b7ead9dcb8f992ad68696fec729d0254b3d Mon Sep 17 00:00:00 2001 From: Igor Tkachenko Date: Thu, 20 Jun 2019 23:42:45 +0300 Subject: [PATCH 01/27] Basis for python API server Use module https://github.com/TionAPI/python for work with Tion devices --- .gitignore | 2 ++ .gitmodules | 3 ++ apiServer.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 16 +++++++++++ 4 files changed, 101 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 apiServer.py create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ab6c70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tionDevices/ +__pycache__/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..79e998c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tionDevices"] + path = tionDevices + url = https://github.com/TionAPI/python.git diff --git a/apiServer.py b/apiServer.py new file mode 100644 index 0000000..297c0c7 --- /dev/null +++ b/apiServer.py @@ -0,0 +1,80 @@ +from http.server import BaseHTTPRequestHandler +from pprint import pprint +import json +import sys +import os + +from tionDevices import * + +class tionAPIserver(BaseHTTPRequestHandler): + allowed_devices = [] + def __init__(self, request, client_addr, server): + self.allowed_devices = self._get_allowed_devices() + BaseHTTPRequestHandler.__init__(self, request, client_addr, server) + + def _get_allowed_devices(self): + result = [] + for c in tion.__subclasses__(): + result.append(c.__name__) + return result + + + def _set_headers(self, code): + self.send_response(code) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def _send_response(self, code, message: dict, error = ""): + self._set_headers(200) + + response = { + "code": code, + "error": error, + } + response = {**response, **message} + response = json.dumps(response) + self.log_message("Response is: " + response) + self.wfile.write((response+"\n").encode()) + + def _create_device(self, device_name: str): + if device_name in self.allowed_devices: + device = getattr(sys.modules[__name__], device_name) + else: + raise AttributeError + return device + + def _send_bad_request(self, message: str): + self._send_response(400, {"message": "request is {}".format(self.path)}, message) + + def do_GET(self): + s = self.path.split("?") + r = s[0].split("/") + try: + + device_name = r[1] + device_mac = r[2] + except IndexError as e: + self._send_bad_request("Use requests like '/model/mac_addres'") + return + + if len(s) > 1: + p = s[1].split("&") + + try: + device = self._create_device(device_name) + except AttributeError as e: + self._send_response(422,{}, "Device {} is not supported".format(device_name)) + return + + self.log_message(self.path) + self.log_message("Process device {0} with mac {1}".format(device_name, device_mac) ) + self._send_response(200, device.get()) + + def do_POST(self): + '''Reads post request body''' + content_len = int(self.headers.get('content-length', 0)) + post_body = self.rfile.read(content_len).decode() + self._send_response(200, {"message": "WIP: received post request: {}".format(post_body)}) + + def do_PUT(self): + self.do_POST() diff --git a/main.py b/main.py new file mode 100644 index 0000000..f06bf02 --- /dev/null +++ b/main.py @@ -0,0 +1,16 @@ +from http.server import HTTPServer +from pprint import pprint +import json +import sys + +from apiServer import tionAPIserver + +PORT = 8000 +ADDRESS = "127.0.0.1" + +server_address = (ADDRESS, PORT) + +handler = tionAPIserver +httpd = HTTPServer(server_address, tionAPIserver) +httpd.serve_forever() + From f2f0be7063ee9c9bc31fe4ec9616fc0b55e0bee9 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sun, 23 Jun 2019 19:28:01 +0100 Subject: [PATCH 02/27] Added python module as git submodule --- .gitignore | 1 - tionDevices | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 160000 tionDevices diff --git a/.gitignore b/.gitignore index 9ab6c70..c18dd8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -tionDevices/ __pycache__/ diff --git a/tionDevices b/tionDevices new file mode 160000 index 0000000..2aa6be3 --- /dev/null +++ b/tionDevices @@ -0,0 +1 @@ +Subproject commit 2aa6be382a32c4daf715a41af302303139ff6645 From ba37c9a176c0b977ed01fd8097db31cdee15ccae Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 25 Jun 2019 18:03:16 +0100 Subject: [PATCH 03/27] Added real processing for requests --- apiServer.py | 57 ++++++++++++++++++++++++++++++++++++++++++---------- tionDevices | 2 +- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/apiServer.py b/apiServer.py index 297c0c7..988a62a 100644 --- a/apiServer.py +++ b/apiServer.py @@ -38,7 +38,7 @@ def _send_response(self, code, message: dict, error = ""): def _create_device(self, device_name: str): if device_name in self.allowed_devices: - device = getattr(sys.modules[__name__], device_name) + device = getattr(sys.modules[__name__], device_name)() else: raise AttributeError return device @@ -46,35 +46,70 @@ def _create_device(self, device_name: str): def _send_bad_request(self, message: str): self._send_response(400, {"message": "request is {}".format(self.path)}, message) - def do_GET(self): - s = self.path.split("?") + def _get_device_from_request(self, path:str) -> str: + + s = path.split("?") r = s[0].split("/") try: - device_name = r[1] device_mac = r[2] - except IndexError as e: + except IndexError as e: self._send_bad_request("Use requests like '/model/mac_addres'") return - + if len(s) > 1: p = s[1].split("&") - + try: device = self._create_device(device_name) except AttributeError as e: self._send_response(422,{}, "Device {} is not supported".format(device_name)) return - self.log_message(self.path) + self.log_message(path) self.log_message("Process device {0} with mac {1}".format(device_name, device_mac) ) - self._send_response(200, device.get()) + + return device_mac, device + + def _try_several_times(self, times: int, function, *args): + i = 0 + done = False + exception = None + while i < times: + try: + result = function(*args) + done = True + break + except Exception as e: + exception = e + if not done: + raise exception + + return result + + def do_GET(self): + try: + device_mac, device = self._get_device_from_request(self.path) + response = self._try_several_times(3, device.get, device_mac) + except Exception as e: + self._send_response(400, {}, str(e)) + else: + self._send_response(200, response) def do_POST(self): - '''Reads post request body''' content_len = int(self.headers.get('content-length', 0)) post_body = self.rfile.read(content_len).decode() - self._send_response(200, {"message": "WIP: received post request: {}".format(post_body)}) + device_mac, device = self._get_device_from_request(self.path) + + i = 0 + done = False + exception = None + try: + self._try_several_times(3, device.set, device_mac, json.loads(post_body)) + except Exception as e: + self._send_response(400, {}, str(e)) + else: + self._send_response(200, {"message": "Data {} were sent to device {}".format(post_body, device_mac)}) def do_PUT(self): self.do_POST() diff --git a/tionDevices b/tionDevices index 2aa6be3..5a1bd65 160000 --- a/tionDevices +++ b/tionDevices @@ -1 +1 @@ -Subproject commit 2aa6be382a32c4daf715a41af302303139ff6645 +Subproject commit 5a1bd6526b42a2d559bd578b3c5c0488528a39d1 From bada7929ac3cad635174afa28846b342bdd85d1d Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 2 Jul 2019 11:43:42 +0300 Subject: [PATCH 04/27] Send response with header Content-type application/json --- apiServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiServer.py b/apiServer.py index 988a62a..5d00e87 100644 --- a/apiServer.py +++ b/apiServer.py @@ -21,7 +21,7 @@ def _get_allowed_devices(self): def _set_headers(self, code): self.send_response(code) - self.send_header('Content-type', 'text/html') + self.send_header('Content-type', 'application/json') self.end_headers() def _send_response(self, code, message: dict, error = ""): From 56e16b779ec0c04a29bc217f053a427f107a34bb Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 2 Jul 2019 11:47:07 +0300 Subject: [PATCH 05/27] Send disconnect to Tion device in nay case --- apiServer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apiServer.py b/apiServer.py index 5d00e87..9701579 100644 --- a/apiServer.py +++ b/apiServer.py @@ -95,6 +95,8 @@ def do_GET(self): self._send_response(400, {}, str(e)) else: self._send_response(200, response) + finally: + device._btle.disconnect() def do_POST(self): content_len = int(self.headers.get('content-length', 0)) @@ -110,6 +112,8 @@ def do_POST(self): self._send_response(400, {}, str(e)) else: self._send_response(200, {"message": "Data {} were sent to device {}".format(post_body, device_mac)}) + finally: + device._btle.disconnect() def do_PUT(self): self.do_POST() From 7a5a268481a634cbf572ab042d151139095ebc01 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 2 Jul 2019 11:49:11 +0300 Subject: [PATCH 06/27] Added timeout for processing requests to server --- main.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/main.py b/main.py index f06bf02..ecdd518 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ from pprint import pprint import json import sys +import signal from apiServer import tionAPIserver @@ -11,6 +12,24 @@ server_address = (ADDRESS, PORT) handler = tionAPIserver + +class limitedHttpServer(HTTPServer): + def _signal_handler(self, signum, frame): + raise Exception("Timed out!") + + def serve_forever(self): + """Handle one request at a time until doomsday.""" + while 1: + signal.signal(signal.SIGALRM, self._signal_handler) + signal.alarm(4) + try: + self.handle_request() + except Exception as e: + print("Got exceptin {}".format(str(e))) + finally: + signal.alarm(0) + + httpd = HTTPServer(server_address, tionAPIserver) httpd.serve_forever() From 82ce96b59c613b191f62bf3b70695dd3203ce73a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 4 Sep 2019 19:51:11 +0300 Subject: [PATCH 07/27] apiServer: added inrement in trySeveralTimes loop --- apiServer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apiServer.py b/apiServer.py index 9701579..112fbe5 100644 --- a/apiServer.py +++ b/apiServer.py @@ -76,6 +76,7 @@ def _try_several_times(self, times: int, function, *args): done = False exception = None while i < times: + i = i + 1 try: result = function(*args) done = True @@ -106,6 +107,7 @@ def do_POST(self): i = 0 done = False exception = None + self.log_message(post_body) try: self._try_several_times(3, device.set, device_mac, json.loads(post_body)) except Exception as e: From 0e56baf5d1e878cb1868eabd4de53a8e543aba57 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sun, 5 Jan 2020 21:12:14 +0300 Subject: [PATCH 08/27] Use tionDevices updated at 2020-01-05 --- tionDevices | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tionDevices b/tionDevices index 5a1bd65..5750228 160000 --- a/tionDevices +++ b/tionDevices @@ -1 +1 @@ -Subproject commit 5a1bd6526b42a2d559bd578b3c5c0488528a39d1 +Subproject commit 5750228f95cd3e5c5055d126bfbe31f39c376cda From 292e6b7e0563769514b90e325ae5b26b9d482e38 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 9 Mar 2020 13:05:35 +0300 Subject: [PATCH 09/27] apiServer: get code for response from tionDevice part of code --- apiServer.py | 2 +- tionDevices | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apiServer.py b/apiServer.py index 112fbe5..f4f5954 100644 --- a/apiServer.py +++ b/apiServer.py @@ -95,7 +95,7 @@ def do_GET(self): except Exception as e: self._send_response(400, {}, str(e)) else: - self._send_response(200, response) + self._send_response(response["code"], response) finally: device._btle.disconnect() diff --git a/tionDevices b/tionDevices index 5750228..c6f599d 160000 --- a/tionDevices +++ b/tionDevices @@ -1 +1 @@ -Subproject commit 5750228f95cd3e5c5055d126bfbe31f39c376cda +Subproject commit c6f599dfd22b87423a638a080e642ebe466e3072 From 9cbe90b748ed60563d140468346d4dbd4f867563 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 9 Mar 2020 14:26:30 +0300 Subject: [PATCH 10/27] apiServer: added cache We could not get response from Tion as fast as we wish and we should not send requests too often. So cache is the best solution: we will cache response from device for 5 minutes if nothig was changed via server. It should be fine, because we assume that this server used for smart home integration and all changes performed via smart home controller. For cached responses "code": 304 is used, but server sends HTTP response code 200 in any way, because other codes may be wrong interpreted by smart home controller. --- apiServer.py | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/apiServer.py b/apiServer.py index f4f5954..a5fc2b6 100644 --- a/apiServer.py +++ b/apiServer.py @@ -3,11 +3,35 @@ import json import sys import os +import time from tionDevices import * class tionAPIserver(BaseHTTPRequestHandler): allowed_devices = [] + cache_valid = 600 # seconds + cache_expire = 0 + cache_response = {} + + @classmethod + def _is_cache_valid(cls, ts) -> bool: + return (not cls.cache_expire < ts) + + @classmethod + def _invalidate_cache(cls): + cls.cache_expire = 0 + + @classmethod + def _get_cache(cls) -> dict: + return cls.cache_response + + @classmethod + def _set_cache(cls, response, ts): + cls.cache_expire = ts + cls.cache_valid + cls.cache_response = dict(response) + cls.cache_response["code"] = 304 + cls.cache_response["last_update"] = ts + def __init__(self, request, client_addr, server): self.allowed_devices = self._get_allowed_devices() BaseHTTPRequestHandler.__init__(self, request, client_addr, server) @@ -89,15 +113,22 @@ def _try_several_times(self, times: int, function, *args): return result def do_GET(self): - try: - device_mac, device = self._get_device_from_request(self.path) - response = self._try_several_times(3, device.get, device_mac) - except Exception as e: + now = time.time(); + if (self._is_cache_valid(now)): + try: + device_mac, device = self._get_device_from_request(self.path) + response = self._try_several_times(3, device.get, device_mac) + except Exception as e: + self._invalidate_cache() #drop cache self._send_response(400, {}, str(e)) + else: + self._set_cache(response, now) + self._send_response(response["code"], response) + finally: + device._btle.disconnect() else: + response = self._get_cache() self._send_response(response["code"], response) - finally: - device._btle.disconnect() def do_POST(self): content_len = int(self.headers.get('content-length', 0)) @@ -108,6 +139,7 @@ def do_POST(self): done = False exception = None self.log_message(post_body) + self._invalidate_cache() #drop cache try: self._try_several_times(3, device.set, device_mac, json.loads(post_body)) except Exception as e: From 1581726432b904b16221955459440b7ebd8e30dc Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 9 Mar 2020 14:40:45 +0300 Subject: [PATCH 11/27] apiServer (fix): fix logical issue for cache --- apiServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiServer.py b/apiServer.py index a5fc2b6..706de85 100644 --- a/apiServer.py +++ b/apiServer.py @@ -114,7 +114,7 @@ def _try_several_times(self, times: int, function, *args): def do_GET(self): now = time.time(); - if (self._is_cache_valid(now)): + if (not self._is_cache_valid(now)): try: device_mac, device = self._get_device_from_request(self.path) response = self._try_several_times(3, device.get, device_mac) From 5930416662e76c0d6a9f32ece9f8e3171aebef62 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 9 Mar 2020 16:11:00 +0300 Subject: [PATCH 12/27] apiServer: cache only response with code==200 --- apiServer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apiServer.py b/apiServer.py index 706de85..0bb8771 100644 --- a/apiServer.py +++ b/apiServer.py @@ -122,7 +122,8 @@ def do_GET(self): self._invalidate_cache() #drop cache self._send_response(400, {}, str(e)) else: - self._set_cache(response, now) + if (response["code"] == 200): + self._set_cache(response, now) self._send_response(response["code"], response) finally: device._btle.disconnect() From a7d49b48b71faffeaa703eac9a182ae2ce732e8b Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 15:42:01 +0300 Subject: [PATCH 13/27] Fix indents and PEP8 formatting issues --- apiServer.py | 283 +++++++++++++++++++++++++-------------------------- main.py | 30 +++--- 2 files changed, 155 insertions(+), 158 deletions(-) diff --git a/apiServer.py b/apiServer.py index 0bb8771..0587ab3 100644 --- a/apiServer.py +++ b/apiServer.py @@ -7,148 +7,145 @@ from tionDevices import * + class tionAPIserver(BaseHTTPRequestHandler): - allowed_devices = [] - cache_valid = 600 # seconds - cache_expire = 0 - cache_response = {} - - @classmethod - def _is_cache_valid(cls, ts) -> bool: - return (not cls.cache_expire < ts) - - @classmethod - def _invalidate_cache(cls): - cls.cache_expire = 0 - - @classmethod - def _get_cache(cls) -> dict: - return cls.cache_response - - @classmethod - def _set_cache(cls, response, ts): - cls.cache_expire = ts + cls.cache_valid - cls.cache_response = dict(response) - cls.cache_response["code"] = 304 - cls.cache_response["last_update"] = ts - - def __init__(self, request, client_addr, server): - self.allowed_devices = self._get_allowed_devices() - BaseHTTPRequestHandler.__init__(self, request, client_addr, server) - - def _get_allowed_devices(self): - result = [] - for c in tion.__subclasses__(): - result.append(c.__name__) - return result - - - def _set_headers(self, code): - self.send_response(code) - self.send_header('Content-type', 'application/json') - self.end_headers() - - def _send_response(self, code, message: dict, error = ""): - self._set_headers(200) - - response = { - "code": code, - "error": error, - } - response = {**response, **message} - response = json.dumps(response) - self.log_message("Response is: " + response) - self.wfile.write((response+"\n").encode()) - - def _create_device(self, device_name: str): - if device_name in self.allowed_devices: - device = getattr(sys.modules[__name__], device_name)() - else: - raise AttributeError - return device - - def _send_bad_request(self, message: str): - self._send_response(400, {"message": "request is {}".format(self.path)}, message) - - def _get_device_from_request(self, path:str) -> str: - - s = path.split("?") - r = s[0].split("/") - try: - device_name = r[1] - device_mac = r[2] - except IndexError as e: - self._send_bad_request("Use requests like '/model/mac_addres'") - return - - if len(s) > 1: - p = s[1].split("&") - - try: - device = self._create_device(device_name) - except AttributeError as e: - self._send_response(422,{}, "Device {} is not supported".format(device_name)) - return - - self.log_message(path) - self.log_message("Process device {0} with mac {1}".format(device_name, device_mac) ) - - return device_mac, device - - def _try_several_times(self, times: int, function, *args): - i = 0 - done = False - exception = None - while i < times: - i = i + 1 - try: - result = function(*args) - done = True - break - except Exception as e: - exception = e - if not done: - raise exception - - return result - - def do_GET(self): - now = time.time(); - if (not self._is_cache_valid(now)): - try: + allowed_devices = [] + cache_valid = 600 # seconds + cache_expire = 0 + cache_response = {} + + @classmethod + def _is_cache_valid(cls, ts) -> bool: + return (not cls.cache_expire < ts) + + @classmethod + def _invalidate_cache(cls): + cls.cache_expire = 0 + + @classmethod + def _get_cache(cls) -> dict: + return cls.cache_response + + @classmethod + def _set_cache(cls, response, ts): + cls.cache_expire = ts + cls.cache_valid + cls.cache_response = dict(response) + cls.cache_response["code"] = 304 + cls.cache_response["last_update"] = ts + + def __init__(self, request, client_addr, server): + self.allowed_devices = self._get_allowed_devices() + BaseHTTPRequestHandler.__init__(self, request, client_addr, server) + + def _get_allowed_devices(self): + result = [] + for c in tion.__subclasses__(): + result.append(c.__name__) + return result + + def _set_headers(self, code): + self.send_response(code) + self.send_header('Content-type', 'application/json') + self.end_headers() + + def _send_response(self, code, message: dict, error=""): + self._set_headers(200) + + response = {"code": code, "error": error, } + response = {**response, **message} + response = json.dumps(response) + self.log_message("Response is: " + response) + self.wfile.write((response + "\n").encode()) + + def _create_device(self, device_name: str): + if device_name in self.allowed_devices: + device = getattr(sys.modules[__name__], device_name)() + else: + raise AttributeError + return device + + def _send_bad_request(self, message: str): + self._send_response(400, {"message": "request is {}".format(self.path)}, message) + + def _get_device_from_request(self, path: str) -> str: + + s = path.split("?") + r = s[0].split("/") + try: + device_name = r[1] + device_mac = r[2] + except IndexError as e: + self._send_bad_request("Use requests like '/model/mac_addres'") + return + + if len(s) > 1: + p = s[1].split("&") + + try: + device = self._create_device(device_name) + except AttributeError as e: + self._send_response(422, {}, "Device {} is not supported".format(device_name)) + return + + self.log_message(path) + self.log_message("Process device {0} with mac {1}".format(device_name, device_mac)) + + return device_mac, device + + def _try_several_times(self, times: int, function, *args): + i = 0 + done = False + exception = None + while i < times: + i = i + 1 + try: + result = function(*args) + done = True + break + except Exception as e: + exception = e + if not done: + raise exception + + return result + + def do_GET(self): + now = time.time(); + if (not self._is_cache_valid(now)): + try: + device_mac, device = self._get_device_from_request(self.path) + response = self._try_several_times(3, device.get, device_mac) + except Exception as e: + self._invalidate_cache() # drop cache + self._send_response(400, {}, str(e)) + else: + if (response["code"] == 200): + self._set_cache(response, now) + self._send_response(response["code"], response) + finally: + device._btle.disconnect() + else: + response = self._get_cache() + self._send_response(response["code"], response) + + def do_POST(self): + content_len = int(self.headers.get('content-length', 0)) + post_body = self.rfile.read(content_len).decode() device_mac, device = self._get_device_from_request(self.path) - response = self._try_several_times(3, device.get, device_mac) - except Exception as e: - self._invalidate_cache() #drop cache - self._send_response(400, {}, str(e)) - else: - if (response["code"] == 200): - self._set_cache(response, now) - self._send_response(response["code"], response) - finally: - device._btle.disconnect() - else: - response = self._get_cache() - self._send_response(response["code"], response) - - def do_POST(self): - content_len = int(self.headers.get('content-length', 0)) - post_body = self.rfile.read(content_len).decode() - device_mac, device = self._get_device_from_request(self.path) - - i = 0 - done = False - exception = None - self.log_message(post_body) - self._invalidate_cache() #drop cache - try: - self._try_several_times(3, device.set, device_mac, json.loads(post_body)) - except Exception as e: - self._send_response(400, {}, str(e)) - else: - self._send_response(200, {"message": "Data {} were sent to device {}".format(post_body, device_mac)}) - finally: - device._btle.disconnect() - - def do_PUT(self): - self.do_POST() + + i = 0 + done = False + exception = None + self.log_message(post_body) + self._invalidate_cache() # drop cache + try: + self._try_several_times(3, device.set, device_mac, json.loads(post_body)) + except Exception as e: + self._send_response(400, {}, str(e)) + else: + self._send_response(200, {"message": "Data {} were sent to device {}".format(post_body, device_mac)}) + finally: + device._btle.disconnect() + + def do_PUT(self): + self.do_POST() diff --git a/main.py b/main.py index ecdd518..c19a00c 100644 --- a/main.py +++ b/main.py @@ -13,23 +13,23 @@ handler = tionAPIserver + class limitedHttpServer(HTTPServer): - def _signal_handler(self, signum, frame): - raise Exception("Timed out!") - - def serve_forever(self): - """Handle one request at a time until doomsday.""" - while 1: - signal.signal(signal.SIGALRM, self._signal_handler) - signal.alarm(4) - try: - self.handle_request() - except Exception as e: - print("Got exceptin {}".format(str(e))) - finally: - signal.alarm(0) + def _signal_handler(self, signum, frame): + raise Exception("Timed out!") + + def serve_forever(self): + """Handle one request at a time until doomsday.""" + while 1: + signal.signal(signal.SIGALRM, self._signal_handler) + signal.alarm(4) + try: + self.handle_request() + except Exception as e: + print("Got exceptin {}".format(str(e))) + finally: + signal.alarm(0) httpd = HTTPServer(server_address, tionAPIserver) httpd.serve_forever() - From ce3ddd5be1be807e44479a38e01416cfc66cfaf7 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 16:16:06 +0300 Subject: [PATCH 14/27] Use dev branch of tionDevices as default --- .gitmodules | 1 + tionDevices | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 79e998c..c47ff9d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "tionDevices"] path = tionDevices url = https://github.com/TionAPI/python.git + branch = dev diff --git a/tionDevices b/tionDevices index c6f599d..a39592c 160000 --- a/tionDevices +++ b/tionDevices @@ -1 +1 @@ -Subproject commit c6f599dfd22b87423a638a080e642ebe466e3072 +Subproject commit a39592cef36040bba2b17e5c29a88ed160d74310 From 265e5f1e9d2eb6867a11ece0c7e2076f5ec7c9d4 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 16:24:27 +0300 Subject: [PATCH 15/27] Pass MAC to module while init --- apiServer.py | 18 +++++++++--------- tionDevices | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apiServer.py b/apiServer.py index 0587ab3..ef3474d 100644 --- a/apiServer.py +++ b/apiServer.py @@ -57,9 +57,9 @@ def _send_response(self, code, message: dict, error=""): self.log_message("Response is: " + response) self.wfile.write((response + "\n").encode()) - def _create_device(self, device_name: str): + def _create_device(self, device_name: str, device_mac: str) -> tion: if device_name in self.allowed_devices: - device = getattr(sys.modules[__name__], device_name)() + device = getattr(sys.modules[__name__], device_name)(device_mac) else: raise AttributeError return device @@ -67,7 +67,7 @@ def _create_device(self, device_name: str): def _send_bad_request(self, message: str): self._send_response(400, {"message": "request is {}".format(self.path)}, message) - def _get_device_from_request(self, path: str) -> str: + def _get_device_from_request(self, path: str) -> tion: s = path.split("?") r = s[0].split("/") @@ -82,7 +82,7 @@ def _get_device_from_request(self, path: str) -> str: p = s[1].split("&") try: - device = self._create_device(device_name) + device = self._create_device(device_name, device_mac) except AttributeError as e: self._send_response(422, {}, "Device {} is not supported".format(device_name)) return @@ -90,7 +90,7 @@ def _get_device_from_request(self, path: str) -> str: self.log_message(path) self.log_message("Process device {0} with mac {1}".format(device_name, device_mac)) - return device_mac, device + return device def _try_several_times(self, times: int, function, *args): i = 0 @@ -114,7 +114,7 @@ def do_GET(self): if (not self._is_cache_valid(now)): try: device_mac, device = self._get_device_from_request(self.path) - response = self._try_several_times(3, device.get, device_mac) + response = self._try_several_times(3, device.get) except Exception as e: self._invalidate_cache() # drop cache self._send_response(400, {}, str(e)) @@ -131,7 +131,7 @@ def do_GET(self): def do_POST(self): content_len = int(self.headers.get('content-length', 0)) post_body = self.rfile.read(content_len).decode() - device_mac, device = self._get_device_from_request(self.path) + device = self._get_device_from_request(self.path) i = 0 done = False @@ -139,11 +139,11 @@ def do_POST(self): self.log_message(post_body) self._invalidate_cache() # drop cache try: - self._try_several_times(3, device.set, device_mac, json.loads(post_body)) + self._try_several_times(3, device.set, json.loads(post_body)) except Exception as e: self._send_response(400, {}, str(e)) else: - self._send_response(200, {"message": "Data {} were sent to device {}".format(post_body, device_mac)}) + self._send_response(200, {"message": "Data {} were sent to device {}".format(post_body, device.mac)}) finally: device._btle.disconnect() diff --git a/tionDevices b/tionDevices index a39592c..96e6751 160000 --- a/tionDevices +++ b/tionDevices @@ -1 +1 @@ -Subproject commit a39592cef36040bba2b17e5c29a88ed160d74310 +Subproject commit 96e675132de8bd50fec8cccbc55a885fa24b05ff From bb836120f0a8b91bb5f4a451e02e16a7ae77af14 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 16:26:57 +0300 Subject: [PATCH 16/27] missed for: Pass MAC to module while init --- apiServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiServer.py b/apiServer.py index ef3474d..2f6d981 100644 --- a/apiServer.py +++ b/apiServer.py @@ -113,7 +113,7 @@ def do_GET(self): now = time.time(); if (not self._is_cache_valid(now)): try: - device_mac, device = self._get_device_from_request(self.path) + device = self._get_device_from_request(self.path) response = self._try_several_times(3, device.get) except Exception as e: self._invalidate_cache() # drop cache From ff1375c5378afa855fccea446bf39df628c32fbc Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 17:48:30 +0300 Subject: [PATCH 17/27] Remove _try_several_times: module do this --- apiServer.py | 21 ++------------------- tionDevices | 2 +- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/apiServer.py b/apiServer.py index 2f6d981..9e84360 100644 --- a/apiServer.py +++ b/apiServer.py @@ -92,29 +92,12 @@ def _get_device_from_request(self, path: str) -> tion: return device - def _try_several_times(self, times: int, function, *args): - i = 0 - done = False - exception = None - while i < times: - i = i + 1 - try: - result = function(*args) - done = True - break - except Exception as e: - exception = e - if not done: - raise exception - - return result - def do_GET(self): now = time.time(); if (not self._is_cache_valid(now)): try: device = self._get_device_from_request(self.path) - response = self._try_several_times(3, device.get) + response = device.get() except Exception as e: self._invalidate_cache() # drop cache self._send_response(400, {}, str(e)) @@ -139,7 +122,7 @@ def do_POST(self): self.log_message(post_body) self._invalidate_cache() # drop cache try: - self._try_several_times(3, device.set, json.loads(post_body)) + device.set(json.loads(post_body)) except Exception as e: self._send_response(400, {}, str(e)) else: diff --git a/tionDevices b/tionDevices index 96e6751..3185e7b 160000 --- a/tionDevices +++ b/tionDevices @@ -1 +1 @@ -Subproject commit 96e675132de8bd50fec8cccbc55a885fa24b05ff +Subproject commit 3185e7b607fe49883a4d03f888fa725d3e658f0d From 99e600431b396185f621b14dcb3f227fada3c9c0 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 17:48:57 +0300 Subject: [PATCH 18/27] remove calling disconnect method: tion module do this --- apiServer.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apiServer.py b/apiServer.py index 9e84360..229dcc0 100644 --- a/apiServer.py +++ b/apiServer.py @@ -105,8 +105,6 @@ def do_GET(self): if (response["code"] == 200): self._set_cache(response, now) self._send_response(response["code"], response) - finally: - device._btle.disconnect() else: response = self._get_cache() self._send_response(response["code"], response) @@ -127,8 +125,6 @@ def do_POST(self): self._send_response(400, {}, str(e)) else: self._send_response(200, {"message": "Data {} were sent to device {}".format(post_body, device.mac)}) - finally: - device._btle.disconnect() def do_PUT(self): self.do_POST() From aa66f4ab56bee75437d8631a9123e21882e37f34 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 18:09:30 +0300 Subject: [PATCH 19/27] Move tionDevices to new repository --- .gitmodules | 2 +- tionDevices | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index c47ff9d..3c52516 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "tionDevices"] path = tionDevices - url = https://github.com/TionAPI/python.git + url = https://github.com/TionAPI/tion_python.git branch = dev diff --git a/tionDevices b/tionDevices index 3185e7b..e761b4a 160000 --- a/tionDevices +++ b/tionDevices @@ -1 +1 @@ -Subproject commit 3185e7b607fe49883a4d03f888fa725d3e658f0d +Subproject commit e761b4ad9f865b6199043e61b9cbe3e493a2a4b4 From c0a955a786fdcca86bb4c6d091f9aff5d3bbcae5 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Mon, 1 Jun 2020 20:28:15 +0300 Subject: [PATCH 20/27] Use pip module instead of submodule --- requirements.txt | 1 + .gitmodules | 4 ---- README.md | 11 +++++++++++ apiServer.py | 2 +- tionDevices | 1 - 5 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 requirements.txt delete mode 100644 .gitmodules create mode 100644 README.md delete mode 160000 tionDevices diff --git a/ requirements.txt b/ requirements.txt new file mode 100644 index 0000000..f624703 --- /dev/null +++ b/ requirements.txt @@ -0,0 +1 @@ +git+https://github.com/TionAPI/tion_python.git@dev \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3c52516..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "tionDevices"] - path = tionDevices - url = https://github.com/TionAPI/tion_python.git - branch = dev diff --git a/README.md b/README.md new file mode 100644 index 0000000..294d6f6 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# API server for Tion breezer +## Installation +```shell script +git clone https://github.com/TionAPI/python-server.git +cd python-server +pip3 install -r requirements.txt +``` +## Usage +```shell script +python3 main.py +``` \ No newline at end of file diff --git a/apiServer.py b/apiServer.py index 229dcc0..b4840a0 100644 --- a/apiServer.py +++ b/apiServer.py @@ -5,7 +5,7 @@ import os import time -from tionDevices import * +from tion import * class tionAPIserver(BaseHTTPRequestHandler): diff --git a/tionDevices b/tionDevices deleted file mode 160000 index e761b4a..0000000 --- a/tionDevices +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e761b4ad9f865b6199043e61b9cbe3e493a2a4b4 From d69c87fbc15fd68823d010244733c02c5bc79f0e Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 1 Jun 2020 20:43:12 +0300 Subject: [PATCH 21/27] Update README.md Added usage examples --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 294d6f6..b247bc3 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,6 @@ pip3 install -r requirements.txt ## Usage ```shell script python3 main.py -``` \ No newline at end of file +curl -XGET 'http://127.0.0.1/s3/mac_of_you_breezer' +curl -XPOST 'http://127.0.0.1/s3/mac_of_you_breezer --data {"fan_speed": "1"}' +``` From 18c6ecd9d25e1c3b3de053258bb4bcdf2305b271 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sun, 21 Jun 2020 16:05:55 +0300 Subject: [PATCH 22/27] Fix I1: remove space in filename " requirements.txt" --- requirements.txt => requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename requirements.txt => requirements.txt (100%) diff --git a/ requirements.txt b/requirements.txt similarity index 100% rename from requirements.txt rename to requirements.txt From e119a1bd80d517420d52d816e0c6e9845113bef1 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sat, 28 Nov 2020 14:38:16 +0300 Subject: [PATCH 23/27] fix: use tion-btle module from PyPi Fixes #2 --- requirements.txt | 2 +- apiServer.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ requirements.txt b/ requirements.txt index f624703..6ed592c 100644 --- a/ requirements.txt +++ b/ requirements.txt @@ -1 +1 @@ -git+https://github.com/TionAPI/tion_python.git@dev \ No newline at end of file +tion-btle==1.0.0 \ No newline at end of file diff --git a/apiServer.py b/apiServer.py index b4840a0..f0a6af4 100644 --- a/apiServer.py +++ b/apiServer.py @@ -5,7 +5,7 @@ import os import time -from tion import * +from tion_btle import * class tionAPIserver(BaseHTTPRequestHandler): @@ -39,7 +39,7 @@ def __init__(self, request, client_addr, server): def _get_allowed_devices(self): result = [] - for c in tion.__subclasses__(): + for c in tion_btle.__subclasses__(): result.append(c.__name__) return result @@ -57,7 +57,7 @@ def _send_response(self, code, message: dict, error=""): self.log_message("Response is: " + response) self.wfile.write((response + "\n").encode()) - def _create_device(self, device_name: str, device_mac: str) -> tion: + def _create_device(self, device_name: str, device_mac: str) -> tion_btle: if device_name in self.allowed_devices: device = getattr(sys.modules[__name__], device_name)(device_mac) else: @@ -67,7 +67,7 @@ def _create_device(self, device_name: str, device_mac: str) -> tion: def _send_bad_request(self, message: str): self._send_response(400, {"message": "request is {}".format(self.path)}, message) - def _get_device_from_request(self, path: str) -> tion: + def _get_device_from_request(self, path: str) -> tion_btle: s = path.split("?") r = s[0].split("/") From cb0fd98818ba398e292ded321e6e55b66125b028 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sat, 19 Dec 2020 12:55:39 +0300 Subject: [PATCH 24/27] fix: fix imports and class names Fixes #3 --- apiServer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apiServer.py b/apiServer.py index f0a6af4..5c5cc73 100644 --- a/apiServer.py +++ b/apiServer.py @@ -5,7 +5,9 @@ import os import time -from tion_btle import * +from tion_btle.tion import tion +from tion_btle.s3 import S3 +from tion_btle.lite import Lite class tionAPIserver(BaseHTTPRequestHandler): @@ -39,7 +41,7 @@ def __init__(self, request, client_addr, server): def _get_allowed_devices(self): result = [] - for c in tion_btle.__subclasses__(): + for c in tion.__subclasses__(): result.append(c.__name__) return result @@ -57,7 +59,7 @@ def _send_response(self, code, message: dict, error=""): self.log_message("Response is: " + response) self.wfile.write((response + "\n").encode()) - def _create_device(self, device_name: str, device_mac: str) -> tion_btle: + def _create_device(self, device_name: str, device_mac: str) -> tion: if device_name in self.allowed_devices: device = getattr(sys.modules[__name__], device_name)(device_mac) else: @@ -67,7 +69,7 @@ def _create_device(self, device_name: str, device_mac: str) -> tion_btle: def _send_bad_request(self, message: str): self._send_response(400, {"message": "request is {}".format(self.path)}, message) - def _get_device_from_request(self, path: str) -> tion_btle: + def _get_device_from_request(self, path: str) -> tion: s = path.split("?") r = s[0].split("/") From 57f2e02322db80a3bae54b91284ac96b2944ff8d Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sat, 19 Dec 2020 12:56:50 +0300 Subject: [PATCH 25/27] style: remove redundant squares --- apiServer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apiServer.py b/apiServer.py index 5c5cc73..8b903c5 100644 --- a/apiServer.py +++ b/apiServer.py @@ -18,7 +18,7 @@ class tionAPIserver(BaseHTTPRequestHandler): @classmethod def _is_cache_valid(cls, ts) -> bool: - return (not cls.cache_expire < ts) + return not cls.cache_expire < ts @classmethod def _invalidate_cache(cls): @@ -96,7 +96,7 @@ def _get_device_from_request(self, path: str) -> tion: def do_GET(self): now = time.time(); - if (not self._is_cache_valid(now)): + if not self._is_cache_valid(now): try: device = self._get_device_from_request(self.path) response = device.get() @@ -104,7 +104,7 @@ def do_GET(self): self._invalidate_cache() # drop cache self._send_response(400, {}, str(e)) else: - if (response["code"] == 200): + if response["code"] == 200: self._set_cache(response, now) self._send_response(response["code"], response) else: From 47f06a611ff17fd978d37817d24e055a0e8efade Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sat, 19 Dec 2020 12:57:20 +0300 Subject: [PATCH 26/27] style: use class name in CamelCase --- apiServer.py | 2 +- main.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apiServer.py b/apiServer.py index 8b903c5..d9dcbed 100644 --- a/apiServer.py +++ b/apiServer.py @@ -10,7 +10,7 @@ from tion_btle.lite import Lite -class tionAPIserver(BaseHTTPRequestHandler): +class TionApiServer(BaseHTTPRequestHandler): allowed_devices = [] cache_valid = 600 # seconds cache_expire = 0 diff --git a/main.py b/main.py index c19a00c..f5927c5 100644 --- a/main.py +++ b/main.py @@ -4,14 +4,14 @@ import sys import signal -from apiServer import tionAPIserver +from apiServer import TionApiServer PORT = 8000 ADDRESS = "127.0.0.1" server_address = (ADDRESS, PORT) -handler = tionAPIserver +handler = TionApiServer class limitedHttpServer(HTTPServer): @@ -31,5 +31,5 @@ def serve_forever(self): signal.alarm(0) -httpd = HTTPServer(server_address, tionAPIserver) +httpd = HTTPServer(server_address, TionApiServer) httpd.serve_forever() From e15832fe1a4862092415343ba334e40828c4c736 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Sat, 19 Dec 2020 12:57:49 +0300 Subject: [PATCH 27/27] style: remove trailing semicolon --- apiServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiServer.py b/apiServer.py index d9dcbed..d7719ce 100644 --- a/apiServer.py +++ b/apiServer.py @@ -95,7 +95,7 @@ def _get_device_from_request(self, path: str) -> tion: return device def do_GET(self): - now = time.time(); + now = time.time() if not self._is_cache_valid(now): try: device = self._get_device_from_request(self.path)