diff --git a/coderbot/api.py b/coderbot/api.py index 88052aff..70164c49 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -27,16 +27,8 @@ BUTTON_PIN = 16 -config = Config.read() -bot = CoderBot.get_instance(motor_trim_factor=float(config.get('move_motor_trim', 1.0)), - motor_max_power=int(config.get('motor_max_power', 100)), - motor_min_power=int(config.get('motor_min_power', 0)), - hw_version=config.get('hardware_version'), - pid_params=(float(config.get('pid_kp', 1.0)), - float(config.get('pid_kd', 0.1)), - float(config.get('pid_ki', 0.01)), - float(config.get('pid_max_speed', 200)), - float(config.get('pid_sample_time', 0.01)))) +config = Config.get() +bot = CoderBot.get_instance() audio_device = Audio.get_instance() cam = Camera.get_instance() diff --git a/coderbot/audio.py b/coderbot/audio.py index ed1a469e..42a4eeca 100644 --- a/coderbot/audio.py +++ b/coderbot/audio.py @@ -26,7 +26,7 @@ import pyaudio import alsaaudio -from six.moves import queue +import queue # [END import_libraries] # Audio recording parameters diff --git a/coderbot/coderbot.py b/coderbot/coderbot.py index 37877506..89c0de1c 100644 --- a/coderbot/coderbot.py +++ b/coderbot/coderbot.py @@ -157,8 +157,10 @@ def exit(self): s.cancel() @classmethod - def get_instance(cls, motor_trim_factor=1.0, motor_max_power=100, motor_min_power=0, hw_version="5", pid_params=(0.8, 0.1, 0.01, 200, 0.01)): + def get_instance(cls, motor_trim_factor=1.0, motor_max_power=100, motor_min_power=0, hw_version="5", pid_params=(0.8, 0.1, 0.01, 200, 0.01), from_defaults=True): if not cls.the_bot: + if from_defaults: + raise ValueError("incorrect CoderBot initialisation") cls.the_bot = CoderBot(motor_trim_factor=motor_trim_factor, motor_max_power= motor_max_power, motor_min_power=motor_min_power, hw_version=hw_version, pid_params=pid_params) return cls.the_bot @@ -272,5 +274,4 @@ def _cb_button(self, gpio, level, tick): elif tick - self._cb_last_tick[gpio] > elapse: self._cb_last_tick[gpio] = tick logging.info("pushed: %d, %d", level, tick) - cb() - + cb() \ No newline at end of file diff --git a/coderbot/main.py b/coderbot/main.py index edcb53d6..36383240 100644 --- a/coderbot/main.py +++ b/coderbot/main.py @@ -8,7 +8,9 @@ import picamera import connexion -from flask_cors import CORS +from connexion.options import SwaggerUIOptions +from connexion.middleware import MiddlewarePosition +from starlette.middleware.cors import CORSMiddleware from camera import Camera from motion import Motion @@ -22,29 +24,27 @@ # Logging configuration logger = logging.getLogger() logger.setLevel(os.environ.get("LOGLEVEL", "INFO")) -# sh = logging.StreamHandler() -# formatter = logging.Formatter('%(message)s') -# sh.setFormatter(formatter) -# logger.addHandler(sh) ## (Connexion) Flask app configuration # Serve a custom version of the swagger ui (Jinja2 templates) based on the default one # from the folder 'swagger-ui'. Clone the 'swagger-ui' repository inside the backend folder -options = {"swagger_ui": False} -connexionApp = connexion.App(__name__, options=options) - -# Connexion wraps FlaskApp, so app becomes connexionApp.app -app = connexionApp.app -# Access-Control-Allow-Origin -CORS(app) -app.debug = False +swagger_ui_options = SwaggerUIOptions(swagger_ui=True) +app = connexion.App(__name__, swagger_ui_options=swagger_ui_options) +app.add_middleware( + CORSMiddleware, + position=MiddlewarePosition.BEFORE_EXCEPTION, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) app.prog_engine = ProgramEngine.get_instance() ## New API and web application # API v1 is defined in v1.yml and its methods are in api.py -connexionApp.add_api('v1.yml') +app.add_api('v1.yml') def button_pushed(): if app.bot_config.get('button_func') == "startstop": @@ -67,8 +67,16 @@ def run_server(): try: try: app.bot_config = Config.read() - - bot = CoderBot.get_instance() + bot = CoderBot.get_instance(motor_trim_factor=float(app.bot_config.get('move_motor_trim', 1.0)), + motor_max_power=int(app.bot_config.get('motor_max_power', 100)), + motor_min_power=int(app.bot_config.get('motor_min_power', 0)), + hw_version=app.bot_config.get('hardware_version'), + pid_params=(float(app.bot_config.get('pid_kp', 1.0)), + float(app.bot_config.get('pid_kd', 0.1)), + float(app.bot_config.get('pid_ki', 0.01)), + float(app.bot_config.get('pid_max_speed', 200)), + float(app.bot_config.get('pid_sample_time', 0.01))), + from_defaults=False) try: audio_device = Audio.get_instance() @@ -78,6 +86,7 @@ def run_server(): logging.warning("Audio not present") try: + logging.info("starting camera") cam = Camera.get_instance() Motion.get_instance() except picamera.exc.PiCameraError: @@ -97,7 +106,7 @@ def run_server(): remove_doreset_file() - app.run(host="0.0.0.0", port=5000, debug=False, use_reloader=False, threaded=True) + app.run(host="0.0.0.0", port=5000) finally: if cam: cam.exit() @@ -105,4 +114,4 @@ def run_server(): bot.exit() if __name__ == "__main__": - run_server() + run_server() \ No newline at end of file diff --git a/coderbot/program.py b/coderbot/program.py index d76395fd..bc66fc3e 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -87,12 +87,13 @@ def __init__(self): for filename in filenames: if PROGRAM_PREFIX in filename: program_name = filename[len(PROGRAM_PREFIX):-len(PROGRAM_SUFFIX)] - logging.info("adding program %s in path %s as default %r", program_name, dirname, ("default" in dirname)) - with open(os.path.join(dirname, filename), "r") as f: - program_dict = json.load(f) - program_dict["default"] = "default" in dirname - program = Program.from_dict(program_dict) - self.save(program) + if self._programs.search(query.name == program_name) == []: + logging.info("adding program %s in path %s as default %r", program_name, dirname, ("default" in dirname)) + with open(os.path.join(dirname, filename), "r") as f: + program_dict = json.load(f) + program_dict["default"] = "default" in dirname + program = Program.from_dict(program_dict) + self.save(program) @classmethod def get_instance(cls): diff --git a/coderbot/v1.yml b/coderbot/v1.yml index a3c8872f..37142724 100644 --- a/coderbot/v1.yml +++ b/coderbot/v1.yml @@ -170,6 +170,10 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' + tags: - Program management responses: @@ -184,6 +188,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' responses: 200: description: "ok" @@ -200,6 +207,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' requestBody: description: Program object required: true @@ -225,6 +235,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' requestBody: description: Program object required: true @@ -248,6 +261,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' responses: 200: description: "ok" @@ -264,6 +280,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' responses: 200: description: "ok" @@ -304,6 +323,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' - name: default in: query schema: @@ -323,6 +345,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' requestBody: description: Update Activity required: true @@ -346,6 +371,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' responses: 200: description: "ok" @@ -386,6 +414,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' tags: - Music extensions responses: @@ -502,12 +533,13 @@ paths: type: string minLength: 1 maxLength: 256 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' description: text to be "spoken" locale: type: string minLength: 1 maxLength: 2 - pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' + pattern: '^[a-zA-Z]+$' description: locale of text to be "spoken" required: - text @@ -586,6 +618,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' tags: - CNN Models responses: @@ -600,6 +635,9 @@ paths: required: true schema: type: string + minLength: 1 + maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' tags: - CNN Models responses: @@ -679,6 +717,7 @@ components: properties: name: type: string + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' tag: type: string Program: @@ -686,9 +725,9 @@ components: properties: name: type: string - pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' minLength: 1 maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' code: type: string minLength: 1 @@ -709,6 +748,7 @@ components: type: string minLength: 1 maxLength: 128 + pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' description: type: string minLength: 0 @@ -722,4 +762,4 @@ components: - description - default - stock - + \ No newline at end of file diff --git a/docker/stub/requirements.txt b/docker/stub/requirements.txt index 70ff5c16..d937b268 100644 --- a/docker/stub/requirements.txt +++ b/docker/stub/requirements.txt @@ -1,9 +1,6 @@ # API framework -connexion==2.14.2 -Flask==2.2.5 -Flask-Cors==3.0.10 +connexion[uvicorn,flask,swagger-ui]==3.0.5 tinydb==4.8.0 -Werkzeug==2.2.3 # Misc utils setuptools==69.2.0 diff --git a/docker/stub/start.sh b/docker/stub/start.sh index ff2ed337..f28cc766 100755 --- a/docker/stub/start.sh +++ b/docker/stub/start.sh @@ -2,4 +2,4 @@ export PYTHONPATH=./stub:./test:./coderbot cd /coderbot -python3 coderbot/main.py \ No newline at end of file +python3 coderbot/main.py & python3 stub/wifi/main.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 139d980c..d2085ec9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,6 @@ # API framework -connexion==2.14.2 -Flask==2.2.5 -Flask-Cors==3.0.10 +connexion[uvicorn,flask,swagger-ui]==3.0.5 tinydb==4.8.0 -Werkzeug==2.2.3 # Misc utils setuptools==69.2.0 diff --git a/stub/wifi/api.py b/stub/wifi/api.py new file mode 100644 index 00000000..a6a86e9b --- /dev/null +++ b/stub/wifi/api.py @@ -0,0 +1,19 @@ +import logging + +def list_access_points(): + return {"ssids": [{"ssid": "my_wifi"}]} + +def connection_status(): + return {"wifi": "true", "internet": "true"} + +def connect(): + return "ok" + +def forget(): + return "ok" + +def sset_hotspot_ssid(): + return "ok" + +def set_hotspot_password(): + return "ok" \ No newline at end of file diff --git a/stub/wifi/main.py b/stub/wifi/main.py new file mode 100644 index 00000000..d341cb8f --- /dev/null +++ b/stub/wifi/main.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +import os +import logging +import logging.handlers +import connexion +from connexion.middleware import MiddlewarePosition +from starlette.middleware.cors import CORSMiddleware + +# Logging configuration +logger = logging.getLogger() +logger.setLevel(os.environ.get("LOGLEVEL", "INFO")) + +## (Connexion) Flask app configuration + +# Serve a custom version of the swagger ui (Jinja2 templates) based on the default one +# from the folder 'swagger-ui'. Clone the 'swagger-ui' repository inside the backend folder + +app = connexion.App(__name__) +app.add_middleware( + CORSMiddleware, + position=MiddlewarePosition.BEFORE_EXCEPTION, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +## New API and web application + +# API v1 is defined in v1.yml and its methods are in api.py +app.add_api('v1.yml') + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=9090) \ No newline at end of file diff --git a/stub/wifi/v1.yml b/stub/wifi/v1.yml new file mode 100644 index 00000000..352871d9 --- /dev/null +++ b/stub/wifi/v1.yml @@ -0,0 +1,64 @@ +openapi: "3.0.0" +info: + version: "1.0" + title: OpenAPI 3.0 definition of WiFi API + +servers: + - url: http://coderbot.local/v1 + +# Paths supported by the server application +paths: + /list_access_points: + get: + operationId: "api.list_access_points" + summary: "list Access Points" + responses: + 200: + description: "ok" + tags: + - Wifi + /connection_status: + get: + operationId: "api.connection_status" + summary: "connection Status" + responses: + 200: + description: "ok" + tags: + - Wifi + /connect: + post: + operationId: "api.connect" + summary: "connect" + responses: + 200: + description: "ok" + tags: + - Wifi + /forget: + post: + operationId: "api.forget" + summary: "forget" + responses: + 200: + description: "ok" + tags: + - Wifi + /sset_hotspot_ssid: + post: + operationId: "api.sset_hotspot_ssid" + summary: "sset_hotspot_ssid" + responses: + 200: + description: "ok" + tags: + - Wifi + /set_hotspot_password: + post: + operationId: "api.set_hotspot_password" + summary: "set_hotspot_password" + responses: + 200: + description: "ok" + tags: + - Wifi \ No newline at end of file