diff --git a/pico-w/.gitignore b/pico-w/.gitignore index 2f379ae..0d61ced 100644 --- a/pico-w/.gitignore +++ b/pico-w/.gitignore @@ -1,3 +1,4 @@ .env env.json -config.py \ No newline at end of file +config.py +__pycache__ \ No newline at end of file diff --git a/pico-w/src/codes.py b/pico-w/src/codes.py new file mode 100644 index 0000000..604b8f0 --- /dev/null +++ b/pico-w/src/codes.py @@ -0,0 +1,6 @@ +codes = { + 200: 'OK', + 400: 'Bad Request', + 404: 'Not Found', + 405: 'Method Not Allowed', +} diff --git a/pico-w/src/decorators.py b/pico-w/src/decorators.py new file mode 100644 index 0000000..c45e41a --- /dev/null +++ b/pico-w/src/decorators.py @@ -0,0 +1,114 @@ +import socket +import json +from codes import codes + + +def respond(cl: socket.socket, status: int, response: dict | list): + + stringified = json.dumps(response, separators=(',', ':')) + cl.send( + f'HTTP/1.0 {status} {codes.get(status)}\r\nContent-type: text/json\r\n\r\n') + cl.send(stringified) + cl.close() + + +class HttpError(Exception): + def __init__(self, status: int, message: dict | list): + + super().__init__(message) + + self.status = status + self.message = message + + +class Endpoint: + def __init__(self, path: str, method='GET'): + if method not in ('GET', 'POST', 'PATCH', 'PUT', 'DELETE'): + raise ValueError( + 'Valid values are GET, POST, PATCH, PUT', 'DELETE') + self.method = method + self.path = path + + def __call__(self, function): + def wrapper(instance, *args, **kwargs): + + request: str = instance.__request # type: ignore + cl: socket.socket = instance.__cl # type: ignore + # TODO: Improve find checker here to be more precise when dealing with requests. + if request.find(self.path) == -1 and self.path != '*' or request.find(self.method) == -1: + return + try: + val = function(instance, *args, **kwargs) + respond(cl, 200, val) + return val + except HttpError as e: + respond(cl, e.status, + e.message) + + return wrapper + + +class ServerHandler(object): + def __init__(self, s: socket.socket): + not_found_endpoints = (self.__post_not_found, + self.__get_not_found, self.__patch_not_found, self.__put_not_found, self.__delete_not_found) + + while True: + try: + + cl, addr = s.accept() + print('client connected from', addr) + request: bytes = cl.recv(1024) + print(f"{request} \n") + + self.__request = request.decode('utf-8') + self.__cl = cl + method_list = dir(self.__class__) + alreadyReturned = False + for endpoint in method_list: + + if endpoint.startswith('_') is True: + continue + + func = getattr(self.__class__, endpoint) + + if not callable(func): + continue + + result = func(self) + if result: + alreadyReturned = True + break + if (not alreadyReturned): + for endpoint in not_found_endpoints: + result = endpoint() + if result: + break + + except OSError as e: + print(e) + self.__cl.close() + s.close() + + print('connection closed') + break + + @Endpoint('*', 'POST') + def __post_not_found(self): + raise HttpError(404, {'status': 'Not Found'}) + + @Endpoint('*', 'GET') + def __get_not_found(self): + raise HttpError(404, {'status': 'Not Found'}) + + @Endpoint('*', 'PATCH') + def __patch_not_found(self): + raise HttpError(404, {'status': 'Not Found'}) + + @Endpoint('*', 'PUT') + def __put_not_found(self): + raise HttpError(404, {'status': 'Not Found'}) + + @Endpoint('*', 'DELETE') + def __delete_not_found(self): + raise HttpError(404, {'status': 'Not Found'}) diff --git a/pico-w/src/main.py b/pico-w/src/main.py index 8766049..0f4a111 100644 --- a/pico-w/src/main.py +++ b/pico-w/src/main.py @@ -1,9 +1,7 @@ -import socket from utime import sleep_ms -from machine import Pin -from config import ssid, password +from server import initalize_app +from config import led -led = Pin("LED", Pin.OUT) led.value(1) sleep_ms(200) @@ -11,47 +9,4 @@ led.value(0) print("Successfully started pico...") -addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] - -s = socket.socket() -s.bind(addr) -s.listen(1) - -print('listening on', addr) - - -def respond(cl, status, response): - cl.send(f'HTTP/1.0 {status} OK\r\nContent-type: text/html\r\n\r\n') - cl.send(response) - cl.close() - - -# Listen for connections -while True: - try: - cl, addr = s.accept() - print('client connected from', addr) - request = cl.recv(1024) - print(request) - - request = str(request) - led_on = request.find('/light/on') - led_off = request.find('/light/off') - print('led on = ' + str(led_on)) - print('led off = ' + str(led_off)) - - if led_on == 6: - print("led on") - led.value(1) - respond(cl, 200, '{"led": "on"}') - - if led_off == 6: - print("led off") - led.value(0) - respond(cl, 200, '{"led": "off"}') - - except OSError as e: - cl.close() - s.close() - - print('connection closed') +initalize_app() diff --git a/pico-w/src/server.py b/pico-w/src/server.py new file mode 100644 index 0000000..abe2a43 --- /dev/null +++ b/pico-w/src/server.py @@ -0,0 +1,31 @@ +import socket +from config import led +from decorators import Endpoint, ServerHandler + + +def initalize_app(): + + addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] + + s = socket.socket() + s.bind(addr) + s.listen(1) + print('listening on', addr) + + # Listen for connections + Handler(s) + + +class Handler(ServerHandler): + + @Endpoint('light/on', 'POST') + def light_on(self): + print("led on") + led.value(1) + return {"led": "on"} + + @Endpoint('light/off', 'POST') + def light_off(self): + print("led off") + led.value(0) + return {"led": "off"}