From 51f034694d73fb5578a8025e9bba4bfadd43e475 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 10 Apr 2023 23:37:54 +0100 Subject: [PATCH 01/60] sync setting, activities --- coderbot/cloud/__init__.py | 171 +++++++++++++++++++++++++++++++++++++ coderbot/config.py | 3 + coderbot/main.py | 4 + coderbot/program.py | 18 +++- 4 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 coderbot/cloud/__init__.py diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py new file mode 100644 index 00000000..ec93640c --- /dev/null +++ b/coderbot/cloud/__init__.py @@ -0,0 +1,171 @@ +# Sync CoderBot configuration with remote Cloud configuration +# +# For all configuration entities (settings, activities, programs): +# check sync mode (upstream, downstream, both) +# if up: +# compare entity, if different, push changes +# if down: +# compare entity, if different, pull changes +# if both: +# compare entity, if different, take most recent and push/pull changes +# + +import threading +from datetime import datetime, timezone +import logging +import json +from time import sleep + +from config import Config +from activity import Activities +from program import ProgramEngine + +import cloud_api_robot_client +from cloud_api_robot_client.apis.tags import robot_sync_api +from cloud_api_robot_client.model.activity import Activity +from cloud_api_robot_client.model.program import Program +from cloud_api_robot_client.model.robot_data import RobotData +from cloud_api_robot_client.model.setting import Setting + +class CloudManager(threading.Thread): + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = CloudManager() + return cls._instance + + def __init__(self): + threading.Thread.__init__(self) + # Defining the host is optional and defaults to https://api.coderbot.org/api/v1 + # See configuration.py for a list of all supported configuration parameters. + self.configuration = cloud_api_robot_client.Configuration( + host = "http://192.168.1.8:8090/api/v1", + # Configure Bearer authorization: coderbot_auth + ) + self.configuration.access_token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkNaMVFtVGM1WGZIV2NfQ1dPVG9kcm1QaXZFNFJ2ckFXaFZ3T28yTm85eDAifQ.eyJpc3MiOiJDb2RlckJvdCBDbG91ZCBBUEkiLCJpYXQiOjE2Nzc3MDI4NjIsImV4cCI6MTcwOTIzODg2MiwiYXVkIjoic3QtYXBpLmNvZGVyYm90Lm9yZyIsInN1YiI6InRwWkpYNFlsNElZd21QSzhEd2JmIiwiZW1haWwiOiJ0cFpKWDRZbDRJWXdtUEs4RHdiZkBib3RzLmNvZGVyYm90Lm9yZyIsInBpY3R1cmUiOiJodHRwczovL3N0LWFwcC5jb2RlcmJvdC5vcmcvcGljdHVyZXMvbm9waWMifQ.WlrYd-n6-WWHUxlz1kqnGl8TkjspVWn1UhKK_RIWyIJVlczD1GkqT4uqkHl2aGnp9I_E2SETUvC3dWkkUBG7qHvUIIZVaVhGpfiQy7WMekEdMnXtsPxK8NsWjHYUTbqz2dyz2Z1eQi5Ydhj4niEWsKCAT2BG-nwTIDxu-uxKrah6AtCGGyGKCQu0qje-qUNCxT5S1Y5RT10XS4Ewl2ROsMr1M6P3EVa0VoSJ26QZlh5jIz-8fhyGspxBHFEnZF-p95vEGCQp6M7epwoesDGVlX4AxEEpPk7c_Pd4c2gNLx1nhpkV26sT_c_NESNTM42tVyH9ZjQ5fxCUOEi_ELJ2vQ' + + self.start() + + def run(self): + while(True): + logging.info("run.begin") + settings = Config.read() + syncmodes = settings.get("syncmodes", {"settings": "n", "activities": "n", "programs": "n"}) + # Enter a context with an instance of the API client + with cloud_api_robot_client.ApiClient(self.configuration) as api_client: + # Create an instance of the API class + api_instance = robot_sync_api.RobotSyncApi(api_client) + + self.sync_settings(api_instance, syncmodes["settings"]) + + self.sync_activities(api_instance, syncmodes["activities"]) + + self.sync_programs(api_instance, syncmodes["programs"]) + + sleep(10) + logging.info("run.end") + + def sync_settings(self, api_instance, syncmode): + try: + # Create an instance of the API class + api_response = api_instance.get_robot_setting() + cloud_setting_object = api_response.body + cloud_setting = json.loads(cloud_setting_object.get('data')) + local_setting = Config.read() + local_most_recent = datetime.fromisoformat(cloud_setting_object["modified"]).timestamp() < Config.modified() + if cloud_setting != local_setting: + if syncmode == "u" or (syncmode == "b" and local_most_recen): + body = Setting( + id=api_response.body.get('id'), + org_id=api_response.body.get('org_id'), + name=api_response.body.get('name'), + description=api_response.body.get('description'), + data=json.dumps(setting), + modified=datetime.now().isoformat(), + status=api_response.body.get('status'), + ) + api_response = api_instance.set_robot_setting(body) + logging.info("run.4") + if syncmode == 'd': # setting, down + logging.info("cloud_setting: ", str(cloud_setting.data.setting)) + Config.write(cloud_setting.data.setting) + except cloud_api_robot_client.ApiException as e: + logging.warn("Exception when calling RobotSyncApi: %s\n" % e) + + def sync_activities(self, api_instance, syncmode): + activities = Activities.get_instance().list() + try: + # Get robot activities + api_response = api_instance.get_robot_activities() + cloud_activities = api_response.body + logging.info("run.activities.cloud" + str(cloud_activities)) + # cloud activities + a_c_m = {} # activities_cloud_map + for a in cloud_activities: + a_c_m[a.get("id")] = a + + a_l_m = {} # activities_local_map + # local activities no id + for a in activities: + if a.get("id") is not None: + a_l_m[a.get("id")] = a + + # loop through local + for al in activities: + logging.info("syncing: " + str(al.get("id"))) + ac = a_c_m.get(al.get("id")) + if ac is not None: + al["modified"] = al.get("modified", datetime.now(tz=timezone.utc).isoformat()) + local_activity_more_recent = datetime.fromisoformat(ac.get("modified")).timestamp() < datetime.fromisoformat(al.get("modified")).timestamp() + if syncmode == "u" or (local_activity_more_recent and syncmode == 'b'): + ac["data"] = al.get("data") + ac["modified"] = al.get("modified") + body = Activity( + id=ac.get("id"), + org_id=ac.get("org_id"), + name=al.get("name"), + description=al.get("description"), + data=json.dumps(al.get("data")), + modified=al.get("modified").isoformat(), + status='active', + ) + #logging.info("run.activities.cloud.saving") + api_response = api_instance.set_robot_activity(ac.get("id"), body) + #logging.info("run.activities.cloud.saved") + elif syncmode == "d" or (not local_activity_more_recent and syncmode == 'b'): + al["data"] = ac.get("data") + al["modified"] = ac.get("modified") + Activities.get_instance().save(al.get("name"), al) + logging.info("run.activities.local.saved: " + ac.get("name")) + elif ac is None and syncmode in ['u', 'b']: + #logging.info("activity:" + str(al)) + body = Activity( + id="", + org_id="", + name=al.get("name"), + description=al.get("description"), + data=json.dumps(al), + modified=al.get("modified", datetime.now(tz=timezone.utc).isoformat()), + status="active", + ) + api_response = api_instance.create_robot_activity(body=body) + logging.info("run.activities.cloud.created: " + str(api_response.body["id"])) + al["id"] = api_response.body["id"] + al["org_id"] = api_response.body["org_id"] + logging.info("run.activities.saving_local: " + al.get("name")) + Activities.get_instance().save(al.get("name"), al) + elif ac is None and syncmode in ['d']: + logging.info("run.activities.deleting_local: " + al.get("name")) + Activities.get_instance().delete(al.get("name")) + for ac, k in a_c_m.items(): + if a_l_m.get(k) is None and syncmode in ['d', 'b']: + Activities.get_instance().save(ac.get("name"), ac) + logging.info("run.activities.local.saved: " + ac.get("name")) + + except cloud_api_robot_client.ApiException as e: + logging.warn("Exception when calling RobotSyncApi: %s\n" % e) + + def sync_programs(self, api_client, syncmode): + pass diff --git a/coderbot/config.py b/coderbot/config.py index 071dd7da..a2098973 100644 --- a/coderbot/config.py +++ b/coderbot/config.py @@ -52,3 +52,6 @@ def restore(cls): with open(CONFIG_DEFAULT_FILE) as f: cls.write(json.loads(f.read())) + @classmethod + def modified(cls): + return os.stat(CONFIG_FILE).st_mtime \ No newline at end of file diff --git a/coderbot/main.py b/coderbot/main.py index edcb53d6..61bb1b24 100644 --- a/coderbot/main.py +++ b/coderbot/main.py @@ -18,6 +18,7 @@ from cnn.cnn_manager import CNNManager from event import EventManager from coderbot import CoderBot +from cloud import CloudManager # Logging configuration logger = logging.getLogger() @@ -89,6 +90,9 @@ def run_server(): if app.bot_config.get('load_at_start') and app.bot_config.get('load_at_start'): prog = app.prog_engine.load(app.bot_config.get('load_at_start')) prog.execute() + + CloudManager.get_instance() + except ValueError as e: app.bot_config = {} logging.error(e) diff --git a/coderbot/program.py b/coderbot/program.py index d76395fd..6e41afee 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -22,8 +22,9 @@ import json import shutil import logging - +from datetime import datetime import math + from tinydb import TinyDB, Query from threading import Lock @@ -159,12 +160,14 @@ class Program: def dom_code(self): return self._dom_code - def __init__(self, name, code=None, dom_code=None, default=False): + def __init__(self, name, code=None, dom_code=None, default=False, id=None, modified=None): self._thread = None self.name = name self._dom_code = dom_code self._code = code self._default = default + self._id = id + self._modified = modified def execute(self, options={}): if self._running: @@ -242,8 +245,15 @@ def as_dict(self): return {'name': self.name, 'dom_code': self._dom_code, 'code': self._code, - 'default': self._default} + 'default': self._default, + 'id': self._id, + 'modified': self._modified.isoformat()} @classmethod def from_dict(cls, amap): - return Program(name=amap['name'], dom_code=amap['dom_code'], code=amap['code'], default=amap.get('default', False)) + return Program(name=amap['name'], + dom_code=amap['dom_code'], + code=amap['code'], + default=amap.get('default', False), + id=amap.get('id', None), + modified=datetime.fromisoformat(amap.get('modified', datetime.now().isoformat()))) From c857b1a7299c35de6bcc044798affb63fb6f4efe Mon Sep 17 00:00:00 2001 From: previ Date: Tue, 18 Apr 2023 23:38:23 +0100 Subject: [PATCH 02/60] wip --- coderbot/api.py | 7 +- coderbot/cloud/__init__.py | 182 ++++++++++++++---- coderbot/program.py | 56 ++++-- defaults/programs/program_demo_ar_tags.json | 2 +- .../programs/program_demo_cat_follower.json | 2 +- .../programs/program_demo_color_seeker.json | 2 +- defaults/programs/program_demo_io_ext.json | 2 +- .../programs/program_demo_line_follower.json | 2 +- .../programs/program_demo_roboetologist.json | 2 +- .../program_demo_sound_clap_control.json | 2 +- defaults/programs/program_test_find_code.json | 2 +- .../programs/program_test_find_color.json | 2 +- defaults/programs/program_test_find_face.json | 2 +- .../program_test_find_path_ahead.json | 2 +- .../programs/program_test_img_average.json | 2 +- .../programs/program_test_sound_hear.json | 2 +- defaults/programs/program_test_sound_rec.json | 2 +- 17 files changed, 200 insertions(+), 73 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index 88052aff..a5fe50f5 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -7,6 +7,7 @@ import os import subprocess import urllib +from datetime import datetime import connexion import picamera @@ -295,7 +296,7 @@ def saveProgram(name, body): return "askOverwrite" elif existing_program is not None and existing_program.is_default() == True: return "defaultCannotOverwrite", 400 - program = Program(name=body.get("name"), code=body.get("code"), dom_code=body.get("dom_code")) + program = Program(name=body.get("name"), code=body.get("code"), dom_code=body.get("dom_code"), modified=datetime.now(), status="active") prog_engine.save(program) return 200 @@ -307,10 +308,10 @@ def loadProgram(name): return 404 def deleteProgram(name): - prog_engine.delete(name) + prog_engine.delete(name, logical=True) def listPrograms(): - return prog_engine.prog_list() + return prog_engine.prog_list(active_only=True) def runProgram(name, body): """ diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index ec93640c..7f583fcf 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -19,6 +19,7 @@ from config import Config from activity import Activities from program import ProgramEngine +import program import cloud_api_robot_client from cloud_api_robot_client.apis.tags import robot_sync_api @@ -27,6 +28,10 @@ from cloud_api_robot_client.model.robot_data import RobotData from cloud_api_robot_client.model.setting import Setting +SYNC_UPSTREAM = 'u' +SYNC_DOWNSTREAM = 'd' +SYNC_BIDIRECTIONAL = 'b' + class CloudManager(threading.Thread): _instance = None @@ -41,31 +46,30 @@ def __init__(self): # Defining the host is optional and defaults to https://api.coderbot.org/api/v1 # See configuration.py for a list of all supported configuration parameters. self.configuration = cloud_api_robot_client.Configuration( - host = "http://192.168.1.8:8090/api/v1", - # Configure Bearer authorization: coderbot_auth + host = "http://192.168.1.7:8090/api/v1", ) + # Configure Bearer authorization: coderbot_auth self.configuration.access_token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkNaMVFtVGM1WGZIV2NfQ1dPVG9kcm1QaXZFNFJ2ckFXaFZ3T28yTm85eDAifQ.eyJpc3MiOiJDb2RlckJvdCBDbG91ZCBBUEkiLCJpYXQiOjE2Nzc3MDI4NjIsImV4cCI6MTcwOTIzODg2MiwiYXVkIjoic3QtYXBpLmNvZGVyYm90Lm9yZyIsInN1YiI6InRwWkpYNFlsNElZd21QSzhEd2JmIiwiZW1haWwiOiJ0cFpKWDRZbDRJWXdtUEs4RHdiZkBib3RzLmNvZGVyYm90Lm9yZyIsInBpY3R1cmUiOiJodHRwczovL3N0LWFwcC5jb2RlcmJvdC5vcmcvcGljdHVyZXMvbm9waWMifQ.WlrYd-n6-WWHUxlz1kqnGl8TkjspVWn1UhKK_RIWyIJVlczD1GkqT4uqkHl2aGnp9I_E2SETUvC3dWkkUBG7qHvUIIZVaVhGpfiQy7WMekEdMnXtsPxK8NsWjHYUTbqz2dyz2Z1eQi5Ydhj4niEWsKCAT2BG-nwTIDxu-uxKrah6AtCGGyGKCQu0qje-qUNCxT5S1Y5RT10XS4Ewl2ROsMr1M6P3EVa0VoSJ26QZlh5jIz-8fhyGspxBHFEnZF-p95vEGCQp6M7epwoesDGVlX4AxEEpPk7c_Pd4c2gNLx1nhpkV26sT_c_NESNTM42tVyH9ZjQ5fxCUOEi_ELJ2vQ' - self.start() def run(self): while(True): - logging.info("run.begin") + logging.info("run.sync.begin") settings = Config.read() syncmodes = settings.get("syncmodes", {"settings": "n", "activities": "n", "programs": "n"}) + sync_period = settings.get("sync_period", 10) + # Enter a context with an instance of the API client with cloud_api_robot_client.ApiClient(self.configuration) as api_client: # Create an instance of the API class api_instance = robot_sync_api.RobotSyncApi(api_client) self.sync_settings(api_instance, syncmodes["settings"]) - self.sync_activities(api_instance, syncmodes["activities"]) - self.sync_programs(api_instance, syncmodes["programs"]) - sleep(10) - logging.info("run.end") + sleep(sync_period) + logging.info("run.sync.end") def sync_settings(self, api_instance, syncmode): try: @@ -75,24 +79,25 @@ def sync_settings(self, api_instance, syncmode): cloud_setting = json.loads(cloud_setting_object.get('data')) local_setting = Config.read() local_most_recent = datetime.fromisoformat(cloud_setting_object["modified"]).timestamp() < Config.modified() + logging.info("settings.syncing: " + cloud_setting_object.get("id") + " name: " + cloud_setting_object.get("id")) if cloud_setting != local_setting: - if syncmode == "u" or (syncmode == "b" and local_most_recen): + if syncmode == SYNC_UPSTREAM or (syncmode == SYNC_BIDIRECTIONAL and local_most_recent): body = Setting( - id=api_response.body.get('id'), - org_id=api_response.body.get('org_id'), - name=api_response.body.get('name'), - description=api_response.body.get('description'), - data=json.dumps(setting), - modified=datetime.now().isoformat(), - status=api_response.body.get('status'), + id = cloud_setting_object.get('id'), + org_id = cloud_setting_object.get('org_id'), + name = cloud_setting_object.get('name'), + description = cloud_setting_object.get('description'), + data = json.dumps(local_setting), + modified = datetime.now().isoformat(), + status = cloud_setting_object.get('status'), ) api_response = api_instance.set_robot_setting(body) - logging.info("run.4") - if syncmode == 'd': # setting, down - logging.info("cloud_setting: ", str(cloud_setting.data.setting)) + logging.info("settings.upstream") + if syncmode == SYNC_DOWNSTREAM: # setting, down Config.write(cloud_setting.data.setting) + logging.info("settings.downstream") except cloud_api_robot_client.ApiException as e: - logging.warn("Exception when calling RobotSyncApi: %s\n" % e) + logging.warn("Exception when calling settings RobotSyncApi: %s\n" % e) def sync_activities(self, api_instance, syncmode): activities = Activities.get_instance().list() @@ -100,7 +105,6 @@ def sync_activities(self, api_instance, syncmode): # Get robot activities api_response = api_instance.get_robot_activities() cloud_activities = api_response.body - logging.info("run.activities.cloud" + str(cloud_activities)) # cloud activities a_c_m = {} # activities_cloud_map for a in cloud_activities: @@ -114,12 +118,12 @@ def sync_activities(self, api_instance, syncmode): # loop through local for al in activities: - logging.info("syncing: " + str(al.get("id"))) + logging.info("activities.syncing: " + str(al.get("id")) + " name: " + str(al.get("name"))) ac = a_c_m.get(al.get("id")) - if ac is not None: + if ac is not None and ac.get("data") != al.get("data"): al["modified"] = al.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_activity_more_recent = datetime.fromisoformat(ac.get("modified")).timestamp() < datetime.fromisoformat(al.get("modified")).timestamp() - if syncmode == "u" or (local_activity_more_recent and syncmode == 'b'): + if syncmode == SYNC_UPSTREAM or (local_activity_more_recent and syncmode == SYNC_BIDIRECTIONAL): ac["data"] = al.get("data") ac["modified"] = al.get("modified") body = Activity( @@ -133,14 +137,13 @@ def sync_activities(self, api_instance, syncmode): ) #logging.info("run.activities.cloud.saving") api_response = api_instance.set_robot_activity(ac.get("id"), body) - #logging.info("run.activities.cloud.saved") - elif syncmode == "d" or (not local_activity_more_recent and syncmode == 'b'): + logging.info("activities.update.upstream: " + al.get("name")) + elif syncmode == "d" or (not local_activity_more_recent and syncmode == SYNC_BIDIRECTIONAL): al["data"] = ac.get("data") al["modified"] = ac.get("modified") Activities.get_instance().save(al.get("name"), al) - logging.info("run.activities.local.saved: " + ac.get("name")) - elif ac is None and syncmode in ['u', 'b']: - #logging.info("activity:" + str(al)) + logging.info("activities.update.downstream: " + al.get("name")) + elif ac is None and syncmode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: body = Activity( id="", org_id="", @@ -151,21 +154,122 @@ def sync_activities(self, api_instance, syncmode): status="active", ) api_response = api_instance.create_robot_activity(body=body) - logging.info("run.activities.cloud.created: " + str(api_response.body["id"])) al["id"] = api_response.body["id"] al["org_id"] = api_response.body["org_id"] - logging.info("run.activities.saving_local: " + al.get("name")) Activities.get_instance().save(al.get("name"), al) - elif ac is None and syncmode in ['d']: - logging.info("run.activities.deleting_local: " + al.get("name")) + logging.info("activities.create.upstream: " + al.get("name")) + elif ac is None and syncmode in [SYNC_DOWNSTREAM]: Activities.get_instance().delete(al.get("name")) - for ac, k in a_c_m.items(): - if a_l_m.get(k) is None and syncmode in ['d', 'b']: + logging.info("activities.delete.downstream: " + al.get("name")) + for k, ac in a_c_m.items(): + if a_l_m.get(k) is None and syncmode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: Activities.get_instance().save(ac.get("name"), ac) - logging.info("run.activities.local.saved: " + ac.get("name")) + logging.info("activities.create.downstream: " + ac.get("name")) except cloud_api_robot_client.ApiException as e: - logging.warn("Exception when calling RobotSyncApi: %s\n" % e) + logging.warn("Exception when calling activities RobotSyncApi: %s\n" % e) + + def sync_programs(self, api_instance, syncmode): + programs = list() + programs_to_be_deleted = list() + for p in ProgramEngine.get_instance().prog_list(active_only=False): + if not p.get("default"): + if p.get("status") == program.PROGRAM_STATUS_ACTIVE: + programs.append(p) + elif p.get("status") == program.PROGRAM_STATUS_DELETED: + programs_to_be_deleted.append(p) + + try: + # Get robot activities + api_response = api_instance.get_robot_programs() + cloud_programs = api_response.body + # cloud activities + p_c_m = {} # activities_cloud_map + for p in cloud_programs: + if p.get("status") == program.PROGRAM_STATUS_ACTIVE: + p_c_m[p.get("id")] = p + + p_l_m = {} # activities_local_map + # local activities no id + for p in programs: + #logging.info("programs.local: " + str(p.get("id")) + " name: " + p.get("name")) + if p.get("id") is not None: + p_l_m[p.get("id")] = p - def sync_programs(self, api_client, syncmode): - pass + # manage programs present locally and in "active" status + for pl in programs: + pc = p_c_m.get(pl.get("id")) + pc_pl_equals = (pc is not None and + pc.get("name") == pl.get("name") and + pc.get("code") == pl.get("code") and + pc.get("dom_code") == pl.get("dom_code") and + pc.get("status") == pl.get("status")) + logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name")) + + if pc is not None and not pc_pl_equals: + pl["modified"] = pl.get("modified", datetime.now(tz=timezone.utc).isoformat()) + local_program_more_recent = datetime.fromisoformat(pc.get("modified")).timestamp() < datetime.fromisoformat(pl.get("modified")).timestamp() + if syncmode == SYNC_UPSTREAM or (local_program_more_recent and syncmode == SYNC_BIDIRECTIONAL) and not to_be_deleted: + pc["data"] = pl.get("data") + pc["modified"] = pl.get("modified") + body = Program( + id=pc.get("id"), + org_id=pc.get("org_id"), + name=pl.get("name"), + description=pl.get("description"), + code=pl.get("code"), + dom_code=pl.get("dom_code"), + modified=pl.get("modified").isoformat(), + status='active', + ) + #logging.info("run.activities.cloud.saving") + api_response = api_instance.set_robot_program(pc.get("id"), body) + logging.info("programs.update.upstream: " + pl.get("name")) + elif syncmode == "d" or (not local_program_more_recent and syncmode == SYNC_BIDIRECTIONAL): + pl["data"] = pc.get("data") + pl["modified"] = pc.get("modified") + ProgramEngine.get_instance().save(program.Program.from_dict(pl)) + logging.info("programs.update.downstream: " + pl.get("name")) + elif pc is None and syncmode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: + body = Program( + id="", + org_id="", + name=pl.get("name"), + description=pl.get("description", ""), + code=pl.get("code"), + dom_code=pl.get("dom_code"), + modified=pl.get("modified", datetime.now(tz=timezone.utc).isoformat()), + status="active", + ) + api_response = api_instance.create_robot_program(body=body) + pl["id"] = api_response.body["id"] + pl["org_id"] = api_response.body["org_id"] + ProgramEngine.get_instance().save(program.Program.from_dict(pl)) + logging.info("programs.create.upstream: " + pl.get("name")) + elif pc is None and syncmode in [SYNC_DOWNSTREAM]: + ProgramEngine.get_instance().delete(pl.get("name")) + logging.info("programs.delete.downstream: " + pl.get("name")) + + # manage programs not present locally in "active" status + for k, pc in p_c_m.items(): + if p_l_m.get(k) is None and syncmode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: + pl = program.Program(name=pc.get("name"), + code=pc.get("code"), + dom_code=pc.get("dom_code"), + default=False, + id=pc.get("id"), + modified=datetime.fromisoformat(pc.get("modified")), + status=pc.get("status")) + ProgramEngine.get_instance().save(pl) + logging.info("programs.create.downstream: " + pc.get("name")) + + # manage programs to be deleted locally and upstream + for pl in programs_to_be_deleted: + if p.get("id") is not None: + logging.info("programs.delete.upstream: " + pl.get("name")) + api_response = api_instance.delete_robot_program(path_params={"program_id":pl.get("id")}) + # delete locally permanently + ProgramEngine.get_instance().delete(pl.get("name"), logical=False) + + except cloud_api_robot_client.ApiException as e: + logging.warn("Exception when calling programs RobotSyncApi: %s\n" % e) \ No newline at end of file diff --git a/coderbot/program.py b/coderbot/program.py index 6e41afee..ce787bbf 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -43,6 +43,8 @@ PROGRAM_SUFFIX = ".json" PROGRAMS_DB = "data/programs.json" PROGRAMS_PATH_DEFAULTS = "defaults/programs/" +PROGRAM_STATUS_ACTIVE = "active" +PROGRAM_STATUS_DELETED = "deleted" musicPackageManager = musicPackages.MusicPackageManager.get_instance() @@ -80,20 +82,22 @@ def __init__(self): self._program = None self._log = "" self._programs = TinyDB(PROGRAMS_DB) + # initialise DB from default programs query = Query() self.lock = Lock() for dirname, dirnames, filenames, in os.walk(PROGRAMS_PATH_DEFAULTS): - dirnames 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.load(program_name) is None: + 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_dict["status"] = PROGRAM_STATUS_ACTIVE + program = Program.from_dict(program_dict) + self.save(program) @classmethod def get_instance(cls): @@ -101,12 +105,19 @@ def get_instance(cls): cls._instance = ProgramEngine() return cls._instance - def prog_list(self): - return self._programs.all() + def prog_list(self, active_only = True): + programs = None + query = Query() + if active_only: + programs = self._programs.search(query.status == PROGRAM_STATUS_ACTIVE) + else: + programs = self._programs.all() + return programs def save(self, program): with self.lock: query = Query() + program._modified = datetime.now() self._program = program program_db_entry = self._program.as_dict() if self._programs.search(query.name == program.name) != []: @@ -120,19 +131,27 @@ def load(self, name): program_db_entries = self._programs.search(query.name == name) if len(program_db_entries) > 0: prog_db_entry = program_db_entries[0] - logging.debug(prog_db_entry) + #logging.debug(prog_db_entry) self._program = Program.from_dict(prog_db_entry) return self._program return None - def delete(self, name): + def delete(self, name, logical = True): with self.lock: query = Query() - program_db_entries = self._programs.search(query.name == name) - self._programs.remove(query.name == name) + program_db_entries = self._programs.search((query.name == name) & (query.default == False) & (query.status == PROGRAM_STATUS_ACTIVE)) + if len(program_db_entries) > 0: + program_db_entry = program_db_entries[0] + if logical: + program_db_entry["status"] = PROGRAM_STATUS_DELETED + program_db_entry["modified"] = datetime.now().isoformat() + self._programs.update(program_db_entry, query.name == name) + else: + self._programs.remove(query.name == name) + return None def create(self, name, code): - self._program = Program(name, code) + self._program = Program(name, code, modified=datetime.now()) return self._program def is_running(self, name): @@ -160,7 +179,7 @@ class Program: def dom_code(self): return self._dom_code - def __init__(self, name, code=None, dom_code=None, default=False, id=None, modified=None): + def __init__(self, name, code=None, dom_code=None, default=False, id=None, modified=None, status=None): self._thread = None self.name = name self._dom_code = dom_code @@ -168,6 +187,7 @@ def __init__(self, name, code=None, dom_code=None, default=False, id=None, modif self._default = default self._id = id self._modified = modified + self._status = status def execute(self, options={}): if self._running: @@ -247,7 +267,8 @@ def as_dict(self): 'code': self._code, 'default': self._default, 'id': self._id, - 'modified': self._modified.isoformat()} + 'modified': self._modified.isoformat(), + 'status': self._status} @classmethod def from_dict(cls, amap): @@ -256,4 +277,5 @@ def from_dict(cls, amap): code=amap['code'], default=amap.get('default', False), id=amap.get('id', None), - modified=datetime.fromisoformat(amap.get('modified', datetime.now().isoformat()))) + modified=datetime.fromisoformat(amap.get('modified', datetime.now().isoformat())), + status=amap.get('status', None),) diff --git a/defaults/programs/program_demo_ar_tags.json b/defaults/programs/program_demo_ar_tags.json index 329f45d9..86699bac 100644 --- a/defaults/programs/program_demo_ar_tags.json +++ b/defaults/programs/program_demo_ar_tags.json @@ -1 +1 @@ -{"dom_code": "tag_mapcodelistacodespositionsWHILETRUEtag_mapcodescodestag_mappositionspositionstag_mapGTcodes0GTGETLASTGETFIRSTpositions150codeGETFIRSTcodescodesEQcode1FORWARD1001LEFT1001EQcode2FORWARD1003EQcode3FORWARD1001RIGHT1001EQcode42RIGHT1000.5LEFT1000.5itSono arrivato!EQcode5itAttenzione!", "code": "tag_map = None\ncode = None\nlista = None\ncodes = None\npositions = None\n\n\nwhile True:\n get_prog_eng().check_end()\n tag_map = get_cam().find_ar_code()\n codes = tag_map.get('codes')\n positions = tag_map.get('positions')\n if len(codes) > 0:\n if positions[0][-1] > 150:\n code = codes[0]\n get_cam().set_text(codes)\n if code == 1:\n get_bot().forward(speed=100, elapse=1)\n get_bot().left(speed=100, elapse=1)\n elif code == 2:\n get_bot().forward(speed=100, elapse=3)\n elif code == 3:\n get_bot().forward(speed=100, elapse=1)\n get_bot().right(speed=100, elapse=1)\n elif code == 4:\n for count in range(2):\n get_prog_eng().check_end()\n get_bot().right(speed=100, elapse=0.5)\n get_bot().left(speed=100, elapse=0.5)\n get_audio().say('Sono arrivato!', locale=\"it\")\n elif code == 5:\n get_audio().say('Attenzione!', locale=\"it\")\n get_cam().set_text('')\n", "name": "ar_bot"} \ No newline at end of file +{"dom_code": "tag_mapcodelistacodespositionsWHILETRUEtag_mapcodescodestag_mappositionspositionstag_mapGTcodes0GTGETLASTGETFIRSTpositions150codeGETFIRSTcodescodesEQcode1FORWARD1001LEFT1001EQcode2FORWARD1003EQcode3FORWARD1001RIGHT1001EQcode42RIGHT1000.5LEFT1000.5itSono arrivato!EQcode5itAttenzione!", "code": "tag_map = None\ncode = None\nlista = None\ncodes = None\npositions = None\n\n\nwhile True:\n get_prog_eng().check_end()\n tag_map = get_cam().find_ar_code()\n codes = tag_map.get('codes')\n positions = tag_map.get('positions')\n if len(codes) > 0:\n if positions[0][-1] > 150:\n code = codes[0]\n get_cam().set_text(codes)\n if code == 1:\n get_bot().forward(speed=100, elapse=1)\n get_bot().left(speed=100, elapse=1)\n elif code == 2:\n get_bot().forward(speed=100, elapse=3)\n elif code == 3:\n get_bot().forward(speed=100, elapse=1)\n get_bot().right(speed=100, elapse=1)\n elif code == 4:\n for count in range(2):\n get_prog_eng().check_end()\n get_bot().right(speed=100, elapse=0.5)\n get_bot().left(speed=100, elapse=0.5)\n get_audio().say('Sono arrivato!', locale=\"it\")\n elif code == 5:\n get_audio().say('Attenzione!', locale=\"it\")\n get_cam().set_text('')\n", "name": "demo_ar_tags"} \ No newline at end of file diff --git a/defaults/programs/program_demo_cat_follower.json b/defaults/programs/program_demo_cat_follower.json index 2c744cc5..905374e6 100644 --- a/defaults/programs/program_demo_cat_follower.json +++ b/defaults/programs/program_demo_cat_follower.json @@ -1 +1 @@ -{"name": "cat_follower", "dom_code": "objectclasspositionpos_xWHILETRUEobjectGETFIRSTgeneric_object_detectclassGETFIRSTobjectEQclasscatpositionGETFROM_STARTobject3pos_xDIVIDEADDGETFROM_STARTposition1GETFROM_STARTposition32classLTpos_x40LEFT600.1GTpos_x60RIGHT600.1FORWARD1000.2object", "code": "object2 = None\nclass2 = None\nposition = None\npos_x = None\n\n\nwhile True:\n get_prog_eng().check_end()\n object2 = get_cam().cnn_detect_objects(\"generic_object_detect\")[0]\n class2 = object2[0]\n if class2 == 'cat':\n position = object2[2]\n pos_x = (position[0] + position[2]) / 2\n get_cam().set_text(class2)\n if pos_x < 40:\n get_bot().left(speed=60, elapse=0.1)\n elif pos_x > 60:\n get_bot().right(speed=60, elapse=0.1)\n else:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_cam().set_text(object2)\n"} \ No newline at end of file +{"name": "demo_cat_follower", "dom_code": "objectclasspositionpos_xWHILETRUEobjectGETFIRSTgeneric_object_detectclassGETFIRSTobjectEQclasscatpositionGETFROM_STARTobject3pos_xDIVIDEADDGETFROM_STARTposition1GETFROM_STARTposition32classLTpos_x40LEFT600.1GTpos_x60RIGHT600.1FORWARD1000.2object", "code": "object2 = None\nclass2 = None\nposition = None\npos_x = None\n\n\nwhile True:\n get_prog_eng().check_end()\n object2 = get_cam().cnn_detect_objects(\"generic_object_detect\")[0]\n class2 = object2[0]\n if class2 == 'cat':\n position = object2[2]\n pos_x = (position[0] + position[2]) / 2\n get_cam().set_text(class2)\n if pos_x < 40:\n get_bot().left(speed=60, elapse=0.1)\n elif pos_x > 60:\n get_bot().right(speed=60, elapse=0.1)\n else:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_cam().set_text(object2)\n"} \ No newline at end of file diff --git a/defaults/programs/program_demo_color_seeker.json b/defaults/programs/program_demo_color_seeker.json index 75c14d28..5c6e53ae 100644 --- a/defaults/programs/program_demo_color_seeker.json +++ b/defaults/programs/program_demo_color_seeker.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angleGTdist32FORWARD1000.1ANDLTdist28GTEdist0BACKWARD1000.1GTangle5RIGHT300.1LTangle-5LEFT300.1", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n if dist > 32:\n get_bot().forward(speed=100, elapse=0.1)\n elif dist < 28 and dist >= 0:\n get_bot().backward(speed=100, elapse=0.1)\n if angle > 5:\n get_bot().right(speed=30, elapse=0.1)\n elif angle < -5:\n get_bot().left(speed=30, elapse=0.1)\n", "name": "colour_seeker"} \ No newline at end of file +{"dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angleGTdist32FORWARD1000.1ANDLTdist28GTEdist0BACKWARD1000.1GTangle5RIGHT300.1LTangle-5LEFT300.1", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n if dist > 32:\n get_bot().forward(speed=100, elapse=0.1)\n elif dist < 28 and dist >= 0:\n get_bot().backward(speed=100, elapse=0.1)\n if angle > 5:\n get_bot().right(speed=30, elapse=0.1)\n elif angle < -5:\n get_bot().left(speed=30, elapse=0.1)\n", "name": "demo_color_seeker"} \ No newline at end of file diff --git a/defaults/programs/program_demo_io_ext.json b/defaults/programs/program_demo_io_ext.json index 3265bfcb..315d167b 100644 --- a/defaults/programs/program_demo_io_ext.json +++ b/defaults/programs/program_demo_io_ext.json @@ -1 +1 @@ -{"name": "test_io_ext", "dom_code": "Analog_Input_1WHILETRUEAnalog_Input_10Analog Input 1: Analog_Input_1GTAnalog_Input_11000TRUE0FALSE", "code": "Analog_Input_1 = None\n\n\nwhile True:\n get_prog_eng().check_end()\n Analog_Input_1 = get_atmega().get_input(0)\n get_cam().set_text('Analog Input 1: ' + str(Analog_Input_1))\n if Analog_Input_1 > 100:\n get_atmega().set_output(0, True)\n else:\n get_atmega().set_output(0, False)\n"} \ No newline at end of file +{"name": "demo_io_ext", "dom_code": "Analog_Input_1WHILETRUEAnalog_Input_10Analog Input 1: Analog_Input_1GTAnalog_Input_11000TRUE0FALSE", "code": "Analog_Input_1 = None\n\n\nwhile True:\n get_prog_eng().check_end()\n Analog_Input_1 = get_atmega().get_input(0)\n get_cam().set_text('Analog Input 1: ' + str(Analog_Input_1))\n if Analog_Input_1 > 100:\n get_atmega().set_output(0, True)\n else:\n get_atmega().set_output(0, False)\n"} \ No newline at end of file diff --git a/defaults/programs/program_demo_line_follower.json b/defaults/programs/program_demo_line_follower.json index da7ba861..2ad8fb82 100644 --- a/defaults/programs/program_demo_line_follower.json +++ b/defaults/programs/program_demo_line_follower.json @@ -1 +1 @@ -{"dom_code": "line_x_1listaline_x_listline_x_2ar_codear_code_listWHILETRUEline_x_listline_x_1GETFIRSTline_x_listar_code_listcodesar_code_listar_codeGETFIRSTar_code_listar_code0ar_codeEQar_code18080-110001000EQar_code26060-120001550EQar_code66060-115502000GTEline_x_10GTline_x_16040-40-1MINUSline_x_150MINUSline_x_150LTline_x_140-4040-1MINUS50line_x_1MINUS50line_x_15050-1150150", "code": "line_x_1 = None\nlista = None\nline_x_list = None\nline_x_2 = None\nar_code = None\nar_code_list = None\n\n\nwhile True:\n get_prog_eng().check_end()\n line_x_list = get_cam().find_line()\n line_x_1 = line_x_list[0]\n ar_code_list = get_cam().find_ar_code().get('codes')\n if not not len(ar_code_list):\n ar_code = ar_code_list[0]\n else:\n ar_code = 0\n get_cam().set_text(ar_code)\n if ar_code == 1:\n get_bot().motor_control(speed_left=80, speed_right=80, elapse=-1, steps_left=1000, steps_right=1000)\n elif ar_code == 2:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=2000, steps_right=1550)\n elif ar_code == 6:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=1550, steps_right=2000)\n else:\n if line_x_1 >= 0:\n if line_x_1 > 60:\n get_bot().motor_control(speed_left=40, speed_right=-40, elapse=-1, steps_left=line_x_1 - 50, steps_right=line_x_1 - 50)\n elif line_x_1 < 40:\n get_bot().motor_control(speed_left=-40, speed_right=40, elapse=-1, steps_left=50 - line_x_1, steps_right=50 - line_x_1)\n else:\n get_bot().motor_control(speed_left=50, speed_right=50, elapse=-1, steps_left=150, steps_right=150)\n", "name": "line_follower"} \ No newline at end of file +{"dom_code": "line_x_1listaline_x_listline_x_2ar_codear_code_listWHILETRUEline_x_listline_x_1GETFIRSTline_x_listar_code_listcodesar_code_listar_codeGETFIRSTar_code_listar_code0ar_codeEQar_code18080-110001000EQar_code26060-120001550EQar_code66060-115502000GTEline_x_10GTline_x_16040-40-1MINUSline_x_150MINUSline_x_150LTline_x_140-4040-1MINUS50line_x_1MINUS50line_x_15050-1150150", "code": "line_x_1 = None\nlista = None\nline_x_list = None\nline_x_2 = None\nar_code = None\nar_code_list = None\n\n\nwhile True:\n get_prog_eng().check_end()\n line_x_list = get_cam().find_line()\n line_x_1 = line_x_list[0]\n ar_code_list = get_cam().find_ar_code().get('codes')\n if not not len(ar_code_list):\n ar_code = ar_code_list[0]\n else:\n ar_code = 0\n get_cam().set_text(ar_code)\n if ar_code == 1:\n get_bot().motor_control(speed_left=80, speed_right=80, elapse=-1, steps_left=1000, steps_right=1000)\n elif ar_code == 2:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=2000, steps_right=1550)\n elif ar_code == 6:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=1550, steps_right=2000)\n else:\n if line_x_1 >= 0:\n if line_x_1 > 60:\n get_bot().motor_control(speed_left=40, speed_right=-40, elapse=-1, steps_left=line_x_1 - 50, steps_right=line_x_1 - 50)\n elif line_x_1 < 40:\n get_bot().motor_control(speed_left=-40, speed_right=40, elapse=-1, steps_left=50 - line_x_1, steps_right=50 - line_x_1)\n else:\n get_bot().motor_control(speed_left=50, speed_right=50, elapse=-1, steps_left=150, steps_right=150)\n", "name": "demo_line_follower"} \ No newline at end of file diff --git a/defaults/programs/program_demo_roboetologist.json b/defaults/programs/program_demo_roboetologist.json index b53ba7e2..71f717b8 100644 --- a/defaults/programs/program_demo_roboetologist.json +++ b/defaults/programs/program_demo_roboetologist.json @@ -1 +1 @@ -{"name":"roboetologia","dom_code":"attesavelocita_maxvar_marciaIndietrovar_giravoltevar_ritiratavelocitastancoScodinzolaDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaLEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaEvita_OstacoliDescrivi questa funzione...15LT020BACKWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT120LEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT220RIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaFORWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaattesa0.2velocita_max80var_marciaIndietro0var_giravolte0var_ritirata0stanco0velocita1WHILETRUEstanco1var_marciaIndietro1var_giravolte1var_ritirata1EQstanco5stanco0velocita1GTstanco3velocita0.5EQvar_ritirata4var_ritirata0EQvar_giravolte8var_giravolte05EQvar_marciaIndietro6var_marciaIndietro0GiravolteDescrivi questa funzione...RIGHT50MULTIPLY100velocita1100velocita_max20.2attesaPatugliamentoDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max10.5Ritirata_e_fugaDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaLEFT50MULTIPLY100velocita1100velocita_max30.2attesaFORWARD50MULTIPLY100velocita1100velocita_max10.2attesaMarciaIndietro_e_ScartoDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaRIGHT50MULTIPLY100velocita1100velocita_max10.2attesa","code":"from numbers import Number\n\nattesa = None\nvelocita_max = None\nvar_marciaIndietro = None\nvar_giravolte = None\nvar_ritirata = None\nvelocita = None\nstanco = None\n\n# Descrivi questa funzione...\ndef Scodinzola():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Evita_Ostacoli():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count2 in range(15):\n get_prog_eng().check_end()\n if get_bot().get_sonar_distance(0) < 20:\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(1) < 20:\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(2) < 20:\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n else:\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Giravolte():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=2)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Patugliamento():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count3 in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(0.5)\n\n# Descrivi questa funzione...\ndef Ritirata_e_fuga():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=3)\n get_bot().sleep(attesa)\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef MarciaIndietro_e_Scarto():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n\nattesa = 0.2\nvelocita_max = 80\nvar_marciaIndietro = 0\nvar_giravolte = 0\nvar_ritirata = 0\nstanco = 0\nvelocita = 1\nwhile True:\n get_prog_eng().check_end()\n stanco = (stanco if isinstance(stanco, Number) else 0) + 1\n var_marciaIndietro = (var_marciaIndietro if isinstance(var_marciaIndietro, Number) else 0) + 1\n var_giravolte = (var_giravolte if isinstance(var_giravolte, Number) else 0) + 1\n var_ritirata = (var_ritirata if isinstance(var_ritirata, Number) else 0) + 1\n if stanco == 5:\n stanco = 0\n velocita = 1\n elif stanco > 3:\n velocita = 0.5\n Scodinzola()\n Evita_Ostacoli()\n Patugliamento()\n if var_ritirata == 4:\n var_ritirata = 0\n Ritirata_e_fuga()\n if var_giravolte == 8:\n var_giravolte = 0\n Giravolte()\n get_bot().sleep(5)\n if var_marciaIndietro == 6:\n var_marciaIndietro = 0\n MarciaIndietro_e_Scarto()\n","default":""} \ No newline at end of file +{"name":"demo_roboetologist","dom_code":"attesavelocita_maxvar_marciaIndietrovar_giravoltevar_ritiratavelocitastancoScodinzolaDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaLEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaEvita_OstacoliDescrivi questa funzione...15LT020BACKWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT120LEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT220RIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaFORWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaattesa0.2velocita_max80var_marciaIndietro0var_giravolte0var_ritirata0stanco0velocita1WHILETRUEstanco1var_marciaIndietro1var_giravolte1var_ritirata1EQstanco5stanco0velocita1GTstanco3velocita0.5EQvar_ritirata4var_ritirata0EQvar_giravolte8var_giravolte05EQvar_marciaIndietro6var_marciaIndietro0GiravolteDescrivi questa funzione...RIGHT50MULTIPLY100velocita1100velocita_max20.2attesaPatugliamentoDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max10.5Ritirata_e_fugaDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaLEFT50MULTIPLY100velocita1100velocita_max30.2attesaFORWARD50MULTIPLY100velocita1100velocita_max10.2attesaMarciaIndietro_e_ScartoDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaRIGHT50MULTIPLY100velocita1100velocita_max10.2attesa","code":"from numbers import Number\n\nattesa = None\nvelocita_max = None\nvar_marciaIndietro = None\nvar_giravolte = None\nvar_ritirata = None\nvelocita = None\nstanco = None\n\n# Descrivi questa funzione...\ndef Scodinzola():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Evita_Ostacoli():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count2 in range(15):\n get_prog_eng().check_end()\n if get_bot().get_sonar_distance(0) < 20:\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(1) < 20:\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(2) < 20:\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n else:\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Giravolte():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=2)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Patugliamento():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count3 in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(0.5)\n\n# Descrivi questa funzione...\ndef Ritirata_e_fuga():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=3)\n get_bot().sleep(attesa)\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef MarciaIndietro_e_Scarto():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n\nattesa = 0.2\nvelocita_max = 80\nvar_marciaIndietro = 0\nvar_giravolte = 0\nvar_ritirata = 0\nstanco = 0\nvelocita = 1\nwhile True:\n get_prog_eng().check_end()\n stanco = (stanco if isinstance(stanco, Number) else 0) + 1\n var_marciaIndietro = (var_marciaIndietro if isinstance(var_marciaIndietro, Number) else 0) + 1\n var_giravolte = (var_giravolte if isinstance(var_giravolte, Number) else 0) + 1\n var_ritirata = (var_ritirata if isinstance(var_ritirata, Number) else 0) + 1\n if stanco == 5:\n stanco = 0\n velocita = 1\n elif stanco > 3:\n velocita = 0.5\n Scodinzola()\n Evita_Ostacoli()\n Patugliamento()\n if var_ritirata == 4:\n var_ritirata = 0\n Ritirata_e_fuga()\n if var_giravolte == 8:\n var_giravolte = 0\n Giravolte()\n get_bot().sleep(5)\n if var_marciaIndietro == 6:\n var_marciaIndietro = 0\n MarciaIndietro_e_Scarto()\n","default":""} \ No newline at end of file diff --git a/defaults/programs/program_demo_sound_clap_control.json b/defaults/programs/program_demo_sound_clap_control.json index 29f377f3..a735de15 100644 --- a/defaults/programs/program_demo_sound_clap_control.json +++ b/defaults/programs/program_demo_sound_clap_control.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEnoise10000.2noiseEQnoiseFALSEFORWARD100-1RIGHT1000.5", "code": "noise = None\n\n\nwhile True:\n get_prog_eng().check_end()\n noise = get_audio().hear(level=1000, elapse=0.2)\n get_cam().set_text(noise)\n if noise == False:\n get_bot().forward(speed=100, elapse=-1)\n else:\n get_bot().right(speed=100, elapse=0.5)\nget_bot().stop()\n", "name": "clap_control"} \ No newline at end of file +{"dom_code": "WHILETRUEnoise10000.2noiseEQnoiseFALSEFORWARD100-1RIGHT1000.5", "code": "noise = None\n\n\nwhile True:\n get_prog_eng().check_end()\n noise = get_audio().hear(level=1000, elapse=0.2)\n get_cam().set_text(noise)\n if noise == False:\n get_bot().forward(speed=100, elapse=-1)\n else:\n get_bot().right(speed=100, elapse=0.5)\nget_bot().stop()\n", "name": "demo_sound_clap_control"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_code.json b/defaults/programs/program_test_find_code.json index 0f16e779..37b1b610 100644 --- a/defaults/programs/program_test_find_code.json +++ b/defaults/programs/program_test_find_code.json @@ -1 +1 @@ -{"dom_code": "WHILETRUE", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().find_qr_code())\n", "name": "find_code_test"} \ No newline at end of file +{"dom_code": "WHILETRUE", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().find_qr_code())\n", "name": "test_find_code"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_color.json b/defaults/programs/program_test_find_color.json index 68bc4586..293735b1 100644 --- a/defaults/programs/program_test_find_color.json +++ b/defaults/programs/program_test_find_color.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angle", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n", "name": "find_color"} \ No newline at end of file +{"dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angle", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n", "name": "test_find_color"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_face.json b/defaults/programs/program_test_find_face.json index ace65032..4320243b 100644 --- a/defaults/programs/program_test_find_face.json +++ b/defaults/programs/program_test_find_face.json @@ -1 +1 @@ -{"dom_code": "faceface_xface_sizeWHILETRUEfaceALLface_xGETFROM_STARTface1face_sizeGETFROM_STARTface2face: face_xface_xLTface_x-10LEFT800.1GTface_x10RIGHT800.1", "code": "face = None\nface_x = None\nface_size = None\n\n\nwhile True:\n get_prog_eng().check_end()\n face = get_cam().find_face()\n face_x = face[0]\n face_size = face[1]\n get_cam().set_text(str('face: ') + str(face_x))\n if face_x:\n if face_x < -10:\n get_bot().left(speed=80, elapse=0.1)\n elif face_x > 10:\n get_bot().right(speed=80, elapse=0.1)\n else:\n get_bot().stop()\n", "name": "face_find"} \ No newline at end of file +{"dom_code": "faceface_xface_sizeWHILETRUEfaceALLface_xGETFROM_STARTface1face_sizeGETFROM_STARTface2face: face_xface_xLTface_x-10LEFT800.1GTface_x10RIGHT800.1", "code": "face = None\nface_x = None\nface_size = None\n\n\nwhile True:\n get_prog_eng().check_end()\n face = get_cam().find_face()\n face_x = face[0]\n face_size = face[1]\n get_cam().set_text(str('face: ') + str(face_x))\n if face_x:\n if face_x < -10:\n get_bot().left(speed=80, elapse=0.1)\n elif face_x > 10:\n get_bot().right(speed=80, elapse=0.1)\n else:\n get_bot().stop()\n", "name": "test_find_face"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_path_ahead.json b/defaults/programs/program_test_find_path_ahead.json index c494a2d2..703325e2 100644 --- a/defaults/programs/program_test_find_path_ahead.json +++ b/defaults/programs/program_test_find_path_ahead.json @@ -1 +1 @@ -{"dom_code": "spazio_liberoWHILETRUEspazio_liberospazio_liberoANDGTspazio_libero30NEQspazio_libero60FORWARD1000.2RIGHT1000.2", "code": "spazio_libero = None\n\n\nwhile True:\n get_prog_eng().check_end()\n spazio_libero = get_cam().path_ahead()\n get_cam().set_text(spazio_libero)\n if spazio_libero > 30 and spazio_libero != 60:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_bot().right(speed=100, elapse=0.2)\n", "name": "path_ahead"} \ No newline at end of file +{"dom_code": "spazio_liberoWHILETRUEspazio_liberospazio_liberoANDGTspazio_libero30NEQspazio_libero60FORWARD1000.2RIGHT1000.2", "code": "spazio_libero = None\n\n\nwhile True:\n get_prog_eng().check_end()\n spazio_libero = get_cam().path_ahead()\n get_cam().set_text(spazio_libero)\n if spazio_libero > 30 and spazio_libero != 60:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_bot().right(speed=100, elapse=0.2)\n", "name": "test_find_path_ahead"} \ No newline at end of file diff --git a/defaults/programs/program_test_img_average.json b/defaults/programs/program_test_img_average.json index 90bacb94..0112e6cb 100644 --- a/defaults/programs/program_test_img_average.json +++ b/defaults/programs/program_test_img_average.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEV", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().get_average()[2])\n", "name": "img_average_test"} \ No newline at end of file +{"dom_code": "WHILETRUEV", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().get_average()[2])\n", "name": "test_img_average"} \ No newline at end of file diff --git a/defaults/programs/program_test_sound_hear.json b/defaults/programs/program_test_sound_hear.json index 31ff1247..6d9026d6 100644 --- a/defaults/programs/program_test_sound_hear.json +++ b/defaults/programs/program_test_sound_hear.json @@ -1 +1 @@ -{"dom_code": "WHILETRUE1001.0", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_audio().hear(level=100, elapse=1))\n", "name": "hear_test"} \ No newline at end of file +{"dom_code": "WHILETRUE1001.0", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_audio().hear(level=100, elapse=1))\n", "name": "test_sound_hear"} \ No newline at end of file diff --git a/defaults/programs/program_test_sound_rec.json b/defaults/programs/program_test_sound_rec.json index de267400..d6740905 100644 --- a/defaults/programs/program_test_sound_rec.json +++ b/defaults/programs/program_test_sound_rec.json @@ -1 +1 @@ -{"dom_code": "test01.wav5", "code": "get_audio().record_to_file(filename='test01.wav', elapse=5)\n", "name": "sound_rec_test"} \ No newline at end of file +{"dom_code": "test01.wav5", "code": "get_audio().record_to_file(filename='test01.wav', elapse=5)\n", "name": "test_sound_rec"} \ No newline at end of file From a2e6f39d9fe2ebbe69be79d1b593e973ca1a4d94 Mon Sep 17 00:00:00 2001 From: previ Date: Tue, 25 Apr 2023 01:05:04 +0100 Subject: [PATCH 03/60] wip --- coderbot/cloud/__init__.py | 2 +- defaults/config.json | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 7f583fcf..1dc318f8 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -57,7 +57,7 @@ def run(self): logging.info("run.sync.begin") settings = Config.read() syncmodes = settings.get("syncmodes", {"settings": "n", "activities": "n", "programs": "n"}) - sync_period = settings.get("sync_period", 10) + sync_period = int(settings.get("sync_period", "60")) # Enter a context with an instance of the API client with cloud_api_robot_client.ApiClient(self.configuration) as api_client: diff --git a/defaults/config.json b/defaults/config.json index c206cc0a..ab8459d0 100644 --- a/defaults/config.json +++ b/defaults/config.json @@ -50,5 +50,11 @@ "pid_sample_time":"0.05", "movement_use_mpu": "false", "movement_use_motion": "false", - "movement_use_encoder": "true" + "movement_use_encoder": "true", + "sync_modes": { + "activities":"b", + "programs":"b", + "settings":"b" + }, + "sync_period":"60" } From e17230de49bcaf5fae555007b521602bf3999d0a Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 8 May 2023 00:03:53 +0100 Subject: [PATCH 04/60] wip --- coderbot/cloud/__init__.py | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 1dc318f8..78f15356 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -56,7 +56,7 @@ def run(self): while(True): logging.info("run.sync.begin") settings = Config.read() - syncmodes = settings.get("syncmodes", {"settings": "n", "activities": "n", "programs": "n"}) + sync_modes = settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) sync_period = int(settings.get("sync_period", "60")) # Enter a context with an instance of the API client @@ -64,14 +64,14 @@ def run(self): # Create an instance of the API class api_instance = robot_sync_api.RobotSyncApi(api_client) - self.sync_settings(api_instance, syncmodes["settings"]) - self.sync_activities(api_instance, syncmodes["activities"]) - self.sync_programs(api_instance, syncmodes["programs"]) + self.sync_settings(api_instance, sync_modes["settings"]) + self.sync_activities(api_instance, sync_modes["activities"]) + self.sync_programs(api_instance, sync_modes["programs"]) sleep(sync_period) logging.info("run.sync.end") - def sync_settings(self, api_instance, syncmode): + def sync_settings(self, api_instance, sync_mode): try: # Create an instance of the API class api_response = api_instance.get_robot_setting() @@ -81,7 +81,7 @@ def sync_settings(self, api_instance, syncmode): local_most_recent = datetime.fromisoformat(cloud_setting_object["modified"]).timestamp() < Config.modified() logging.info("settings.syncing: " + cloud_setting_object.get("id") + " name: " + cloud_setting_object.get("id")) if cloud_setting != local_setting: - if syncmode == SYNC_UPSTREAM or (syncmode == SYNC_BIDIRECTIONAL and local_most_recent): + if sync_mode == SYNC_UPSTREAM or (sync_mode == SYNC_BIDIRECTIONAL and local_most_recent): body = Setting( id = cloud_setting_object.get('id'), org_id = cloud_setting_object.get('org_id'), @@ -93,13 +93,13 @@ def sync_settings(self, api_instance, syncmode): ) api_response = api_instance.set_robot_setting(body) logging.info("settings.upstream") - if syncmode == SYNC_DOWNSTREAM: # setting, down + if sync_mode == SYNC_DOWNSTREAM: # setting, down Config.write(cloud_setting.data.setting) logging.info("settings.downstream") except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling settings RobotSyncApi: %s\n" % e) - def sync_activities(self, api_instance, syncmode): + def sync_activities(self, api_instance, sync_mode): activities = Activities.get_instance().list() try: # Get robot activities @@ -123,7 +123,7 @@ def sync_activities(self, api_instance, syncmode): if ac is not None and ac.get("data") != al.get("data"): al["modified"] = al.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_activity_more_recent = datetime.fromisoformat(ac.get("modified")).timestamp() < datetime.fromisoformat(al.get("modified")).timestamp() - if syncmode == SYNC_UPSTREAM or (local_activity_more_recent and syncmode == SYNC_BIDIRECTIONAL): + if sync_mode == SYNC_UPSTREAM or (local_activity_more_recent and sync_mode == SYNC_BIDIRECTIONAL): ac["data"] = al.get("data") ac["modified"] = al.get("modified") body = Activity( @@ -138,12 +138,12 @@ def sync_activities(self, api_instance, syncmode): #logging.info("run.activities.cloud.saving") api_response = api_instance.set_robot_activity(ac.get("id"), body) logging.info("activities.update.upstream: " + al.get("name")) - elif syncmode == "d" or (not local_activity_more_recent and syncmode == SYNC_BIDIRECTIONAL): + elif sync_mode == "d" or (not local_activity_more_recent and sync_mode == SYNC_BIDIRECTIONAL): al["data"] = ac.get("data") al["modified"] = ac.get("modified") Activities.get_instance().save(al.get("name"), al) logging.info("activities.update.downstream: " + al.get("name")) - elif ac is None and syncmode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: + elif ac is None and sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: body = Activity( id="", org_id="", @@ -158,18 +158,18 @@ def sync_activities(self, api_instance, syncmode): al["org_id"] = api_response.body["org_id"] Activities.get_instance().save(al.get("name"), al) logging.info("activities.create.upstream: " + al.get("name")) - elif ac is None and syncmode in [SYNC_DOWNSTREAM]: + elif ac is None and sync_mode in [SYNC_DOWNSTREAM]: Activities.get_instance().delete(al.get("name")) logging.info("activities.delete.downstream: " + al.get("name")) for k, ac in a_c_m.items(): - if a_l_m.get(k) is None and syncmode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: + if a_l_m.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: Activities.get_instance().save(ac.get("name"), ac) logging.info("activities.create.downstream: " + ac.get("name")) except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling activities RobotSyncApi: %s\n" % e) - def sync_programs(self, api_instance, syncmode): + def sync_programs(self, api_instance, sync_mode): programs = list() programs_to_be_deleted = list() for p in ProgramEngine.get_instance().prog_list(active_only=False): @@ -204,12 +204,12 @@ def sync_programs(self, api_instance, syncmode): pc.get("code") == pl.get("code") and pc.get("dom_code") == pl.get("dom_code") and pc.get("status") == pl.get("status")) - logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name")) + logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name") + " sync_mode: " + sync_mode + " pc: " + str(pc)) if pc is not None and not pc_pl_equals: pl["modified"] = pl.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_program_more_recent = datetime.fromisoformat(pc.get("modified")).timestamp() < datetime.fromisoformat(pl.get("modified")).timestamp() - if syncmode == SYNC_UPSTREAM or (local_program_more_recent and syncmode == SYNC_BIDIRECTIONAL) and not to_be_deleted: + if sync_mode == SYNC_UPSTREAM or (local_program_more_recent and sync_mode == SYNC_BIDIRECTIONAL) and not to_be_deleted: pc["data"] = pl.get("data") pc["modified"] = pl.get("modified") body = Program( @@ -225,12 +225,12 @@ def sync_programs(self, api_instance, syncmode): #logging.info("run.activities.cloud.saving") api_response = api_instance.set_robot_program(pc.get("id"), body) logging.info("programs.update.upstream: " + pl.get("name")) - elif syncmode == "d" or (not local_program_more_recent and syncmode == SYNC_BIDIRECTIONAL): + elif sync_mode == SYNC_DOWNSTREAM or (not local_program_more_recent and sync_mode == SYNC_BIDIRECTIONAL): pl["data"] = pc.get("data") pl["modified"] = pc.get("modified") ProgramEngine.get_instance().save(program.Program.from_dict(pl)) logging.info("programs.update.downstream: " + pl.get("name")) - elif pc is None and syncmode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: + elif pc is None and sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: body = Program( id="", org_id="", @@ -246,13 +246,13 @@ def sync_programs(self, api_instance, syncmode): pl["org_id"] = api_response.body["org_id"] ProgramEngine.get_instance().save(program.Program.from_dict(pl)) logging.info("programs.create.upstream: " + pl.get("name")) - elif pc is None and syncmode in [SYNC_DOWNSTREAM]: + elif pc is None and sync_mode in [SYNC_DOWNSTREAM]: ProgramEngine.get_instance().delete(pl.get("name")) logging.info("programs.delete.downstream: " + pl.get("name")) # manage programs not present locally in "active" status for k, pc in p_c_m.items(): - if p_l_m.get(k) is None and syncmode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: + if p_l_m.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: pl = program.Program(name=pc.get("name"), code=pc.get("code"), dom_code=pc.get("dom_code"), From 29b0dc4a0d4e870dbce32c202a201645bce3844a Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 21 May 2023 23:44:30 +0100 Subject: [PATCH 05/60] wip --- coderbot/cloud/__init__.py | 62 ++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 78f15356..13fd93c9 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -27,14 +27,38 @@ from cloud_api_robot_client.model.program import Program from cloud_api_robot_client.model.robot_data import RobotData from cloud_api_robot_client.model.setting import Setting +from cloud_api_robot_client.model.robot_register_data import RobotRegisterData +from cloud_api_robot_client.model.robot_credentials import RobotCredentials SYNC_UPSTREAM = 'u' SYNC_DOWNSTREAM = 'd' SYNC_BIDIRECTIONAL = 'b' +AUTH_FILE = "data/auth.json" + class CloudManager(threading.Thread): _instance = None + _auth = {} + + @classmethod + def get_auth(cls): + return cls._auth + + @classmethod + def read_auth(cls): + with open(AUTH_FILE, 'r') as f: + cls._auth = json.load(f) + f.close() + return cls._auth + + @classmethod + def write_auth(cls, auth): + cls._auth = auth + f = open(AUTH_FILE, 'w') + json.dump(cls._auth, f) + return cls._auth + @classmethod def get_instance(cls): if cls._instance is None: @@ -48,17 +72,22 @@ def __init__(self): self.configuration = cloud_api_robot_client.Configuration( host = "http://192.168.1.7:8090/api/v1", ) - # Configure Bearer authorization: coderbot_auth - self.configuration.access_token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkNaMVFtVGM1WGZIV2NfQ1dPVG9kcm1QaXZFNFJ2ckFXaFZ3T28yTm85eDAifQ.eyJpc3MiOiJDb2RlckJvdCBDbG91ZCBBUEkiLCJpYXQiOjE2Nzc3MDI4NjIsImV4cCI6MTcwOTIzODg2MiwiYXVkIjoic3QtYXBpLmNvZGVyYm90Lm9yZyIsInN1YiI6InRwWkpYNFlsNElZd21QSzhEd2JmIiwiZW1haWwiOiJ0cFpKWDRZbDRJWXdtUEs4RHdiZkBib3RzLmNvZGVyYm90Lm9yZyIsInBpY3R1cmUiOiJodHRwczovL3N0LWFwcC5jb2RlcmJvdC5vcmcvcGljdHVyZXMvbm9waWMifQ.WlrYd-n6-WWHUxlz1kqnGl8TkjspVWn1UhKK_RIWyIJVlczD1GkqT4uqkHl2aGnp9I_E2SETUvC3dWkkUBG7qHvUIIZVaVhGpfiQy7WMekEdMnXtsPxK8NsWjHYUTbqz2dyz2Z1eQi5Ydhj4niEWsKCAT2BG-nwTIDxu-uxKrah6AtCGGyGKCQu0qje-qUNCxT5S1Y5RT10XS4Ewl2ROsMr1M6P3EVa0VoSJ26QZlh5jIz-8fhyGspxBHFEnZF-p95vEGCQp6M7epwoesDGVlX4AxEEpPk7c_Pd4c2gNLx1nhpkV26sT_c_NESNTM42tVyH9ZjQ5fxCUOEi_ELJ2vQ' + try: + self.read_auth() + except FileNotFoundError: + self.write_auth({}) self.start() def run(self): while(True): - logging.info("run.sync.begin") settings = Config.read() + logging.info("run.sync.begin") sync_modes = settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) sync_period = int(settings.get("sync_period", "60")) + token = self.get_token_or_register(settings) + self.configuration.access_token = token + # Enter a context with an instance of the API client with cloud_api_robot_client.ApiClient(self.configuration) as api_client: # Create an instance of the API class @@ -71,15 +100,36 @@ def run(self): sleep(sync_period) logging.info("run.sync.end") + def get_token_or_register(self, settings): + logging.info("run.check.token") + token = self.get_auth().get("token") + reg_otp = settings.get("reg_otp") + logging.info("otp_reg:" + reg_otp) + try: + if token is None and reg_otp is not None: + with cloud_api_robot_client.ApiClient(self.configuration) as api_client: + api_instance = robot_sync_api.RobotSyncApi(api_client) + body = RobotRegisterData( + otp=reg_otp, + ) + api_response = api_instance.register_robot(body=body) + logging.info(api_response.body) + token = api_response.body.get("token") + self.write_auth({"token":token}) + return token + except cloud_api_robot_client.ApiException as e: + logging.warn("Exception when calling register_robot RobotSyncApi: %s\n" % e) + def sync_settings(self, api_instance, sync_mode): try: # Create an instance of the API class api_response = api_instance.get_robot_setting() cloud_setting_object = api_response.body cloud_setting = json.loads(cloud_setting_object.get('data')) + local_setting = Config.read() - local_most_recent = datetime.fromisoformat(cloud_setting_object["modified"]).timestamp() < Config.modified() - logging.info("settings.syncing: " + cloud_setting_object.get("id") + " name: " + cloud_setting_object.get("id")) + local_most_recent = datetime.fromisoformat(cloud_setting_object.get("modified", "2000-01-01T00:00:00.000000")).timestamp() < Config.modified() + logging.info("settings.syncing: " + cloud_setting_object.get("id", "") + " name: " + cloud_setting_object.get("name", "")) if cloud_setting != local_setting: if sync_mode == SYNC_UPSTREAM or (sync_mode == SYNC_BIDIRECTIONAL and local_most_recent): body = Setting( @@ -184,7 +234,7 @@ def sync_programs(self, api_instance, sync_mode): api_response = api_instance.get_robot_programs() cloud_programs = api_response.body # cloud activities - p_c_m = {} # activities_cloud_map + p_c_m = {} # programs_cloud_map for p in cloud_programs: if p.get("status") == program.PROGRAM_STATUS_ACTIVE: p_c_m[p.get("id")] = p From 15d215dd1efdbc9def2e6bf238b5cee0874207ef Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 29 May 2023 00:15:53 +0100 Subject: [PATCH 06/60] wip --- coderbot/cloud/__init__.py | 67 +++++++++++++++++++++++++------------- coderbot/program.py | 18 +++++----- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 13fd93c9..76aac01f 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -93,8 +93,8 @@ def run(self): # Create an instance of the API class api_instance = robot_sync_api.RobotSyncApi(api_client) - self.sync_settings(api_instance, sync_modes["settings"]) - self.sync_activities(api_instance, sync_modes["activities"]) + #self.sync_settings(api_instance, sync_modes["settings"]) + #self.sync_activities(api_instance, sync_modes["activities"]) self.sync_programs(api_instance, sync_modes["programs"]) sleep(sync_period) @@ -138,6 +138,7 @@ def sync_settings(self, api_instance, sync_mode): name = cloud_setting_object.get('name'), description = cloud_setting_object.get('description'), data = json.dumps(local_setting), + kind = local_setting.get("kind", "stock"), modified = datetime.now().isoformat(), status = cloud_setting_object.get('status'), ) @@ -182,6 +183,7 @@ def sync_activities(self, api_instance, sync_mode): name=al.get("name"), description=al.get("description"), data=json.dumps(al.get("data")), + kind = al.get("kind", "stock"), modified=al.get("modified").isoformat(), status='active', ) @@ -200,6 +202,7 @@ def sync_activities(self, api_instance, sync_mode): name=al.get("name"), description=al.get("description"), data=json.dumps(al), + kind=al.get("kind", "stock"), modified=al.get("modified", datetime.now(tz=timezone.utc).isoformat()), status="active", ) @@ -220,35 +223,38 @@ def sync_activities(self, api_instance, sync_mode): logging.warn("Exception when calling activities RobotSyncApi: %s\n" % e) def sync_programs(self, api_instance, sync_mode): - programs = list() - programs_to_be_deleted = list() + programs_local_user = list() + programs_local_stock = list() + programs_local_to_be_deleted = list() for p in ProgramEngine.get_instance().prog_list(active_only=False): - if not p.get("default"): + if p.get("kind") == program.PROGRAM_KIND_USER: if p.get("status") == program.PROGRAM_STATUS_ACTIVE: - programs.append(p) + programs_local_user.append(p) elif p.get("status") == program.PROGRAM_STATUS_DELETED: - programs_to_be_deleted.append(p) - + programs_local_to_be_deleted.append(p) + else: + programs_local_stock.append(p) + try: - # Get robot activities + # Get robot programs api_response = api_instance.get_robot_programs() cloud_programs = api_response.body - # cloud activities - p_c_m = {} # programs_cloud_map + # cloud programs + programs_cloud_map = {} # programs_cloud_map for p in cloud_programs: if p.get("status") == program.PROGRAM_STATUS_ACTIVE: - p_c_m[p.get("id")] = p + programs_cloud_map[p.get("id")] = p - p_l_m = {} # activities_local_map + programs_local_stock_map = {} # activities_local_map # local activities no id - for p in programs: + for p in programs_local_stock: #logging.info("programs.local: " + str(p.get("id")) + " name: " + p.get("name")) if p.get("id") is not None: - p_l_m[p.get("id")] = p + programs_local_stock_map[p.get("id")] = p - # manage programs present locally and in "active" status - for pl in programs: - pc = p_c_m.get(pl.get("id")) + # manage user programs present locally and in "active" status + for pl in programs_local_user: + pc = programs_cloud_map.get(pl.get("id")) pc_pl_equals = (pc is not None and pc.get("name") == pl.get("name") and pc.get("code") == pl.get("code") and @@ -257,9 +263,11 @@ def sync_programs(self, api_instance, sync_mode): logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name") + " sync_mode: " + sync_mode + " pc: " + str(pc)) if pc is not None and not pc_pl_equals: + # cloud program exists and is different pl["modified"] = pl.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_program_more_recent = datetime.fromisoformat(pc.get("modified")).timestamp() < datetime.fromisoformat(pl.get("modified")).timestamp() if sync_mode == SYNC_UPSTREAM or (local_program_more_recent and sync_mode == SYNC_BIDIRECTIONAL) and not to_be_deleted: + # cloud program exists and is less recent pc["data"] = pl.get("data") pc["modified"] = pl.get("modified") body = Program( @@ -269,6 +277,7 @@ def sync_programs(self, api_instance, sync_mode): description=pl.get("description"), code=pl.get("code"), dom_code=pl.get("dom_code"), + kind=pl.get("kind"), modified=pl.get("modified").isoformat(), status='active', ) @@ -276,11 +285,13 @@ def sync_programs(self, api_instance, sync_mode): api_response = api_instance.set_robot_program(pc.get("id"), body) logging.info("programs.update.upstream: " + pl.get("name")) elif sync_mode == SYNC_DOWNSTREAM or (not local_program_more_recent and sync_mode == SYNC_BIDIRECTIONAL): + # cloud program exists and is more recent pl["data"] = pc.get("data") pl["modified"] = pc.get("modified") ProgramEngine.get_instance().save(program.Program.from_dict(pl)) logging.info("programs.update.downstream: " + pl.get("name")) elif pc is None and sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: + # cloud program does not exist body = Program( id="", org_id="", @@ -288,6 +299,7 @@ def sync_programs(self, api_instance, sync_mode): description=pl.get("description", ""), code=pl.get("code"), dom_code=pl.get("dom_code"), + kind=pl.get("kind"), modified=pl.get("modified", datetime.now(tz=timezone.utc).isoformat()), status="active", ) @@ -297,16 +309,17 @@ def sync_programs(self, api_instance, sync_mode): ProgramEngine.get_instance().save(program.Program.from_dict(pl)) logging.info("programs.create.upstream: " + pl.get("name")) elif pc is None and sync_mode in [SYNC_DOWNSTREAM]: + # cloud program does not exist, delete locally since sync_mode is downstream ProgramEngine.get_instance().delete(pl.get("name")) logging.info("programs.delete.downstream: " + pl.get("name")) - # manage programs not present locally in "active" status - for k, pc in p_c_m.items(): - if p_l_m.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: + # manage user or stock programs not present locally in "active" status + for k, pc in programs_cloud_map.items(): + if programs_local_stock_map.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: pl = program.Program(name=pc.get("name"), code=pc.get("code"), dom_code=pc.get("dom_code"), - default=False, + kind=pc.get("kind"), id=pc.get("id"), modified=datetime.fromisoformat(pc.get("modified")), status=pc.get("status")) @@ -314,12 +327,20 @@ def sync_programs(self, api_instance, sync_mode): logging.info("programs.create.downstream: " + pc.get("name")) # manage programs to be deleted locally and upstream - for pl in programs_to_be_deleted: + for pl in programs_local_to_be_deleted: if p.get("id") is not None: logging.info("programs.delete.upstream: " + pl.get("name")) api_response = api_instance.delete_robot_program(path_params={"program_id":pl.get("id")}) # delete locally permanently ProgramEngine.get_instance().delete(pl.get("name"), logical=False) + # manage stock programs to be deleted locally + for pl in programs_local_stock: + ##logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) + if pl.get("id") is not None and programs_cloud_map.get(pl.get("id")) is None: + logging.info("programs.delete.stock.locally: " + pl.get("name")) + # delete locally permanently + ProgramEngine.get_instance().delete(pl.get("name"), logical=False) + except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling programs RobotSyncApi: %s\n" % e) \ No newline at end of file diff --git a/coderbot/program.py b/coderbot/program.py index ce787bbf..cc6abed4 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -45,6 +45,8 @@ PROGRAMS_PATH_DEFAULTS = "defaults/programs/" PROGRAM_STATUS_ACTIVE = "active" PROGRAM_STATUS_DELETED = "deleted" +PROGRAM_KIND_STOCK = "stock" +PROGRAM_KIND_USER = "user" musicPackageManager = musicPackages.MusicPackageManager.get_instance() @@ -94,7 +96,7 @@ def __init__(self): 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_dict["kind"] = PROGRAM_KIND_STOCK program_dict["status"] = PROGRAM_STATUS_ACTIVE program = Program.from_dict(program_dict) self.save(program) @@ -139,7 +141,7 @@ def load(self, name): def delete(self, name, logical = True): with self.lock: query = Query() - program_db_entries = self._programs.search((query.name == name) & (query.default == False) & (query.status == PROGRAM_STATUS_ACTIVE)) + program_db_entries = self._programs.search(query.name == name) if len(program_db_entries) > 0: program_db_entry = program_db_entries[0] if logical: @@ -179,12 +181,12 @@ class Program: def dom_code(self): return self._dom_code - def __init__(self, name, code=None, dom_code=None, default=False, id=None, modified=None, status=None): + def __init__(self, name, code=None, dom_code=None, kind=PROGRAM_KIND_USER, id=None, modified=None, status=None): self._thread = None self.name = name self._dom_code = dom_code self._code = code - self._default = default + self._kind = kind self._id = id self._modified = modified self._status = status @@ -218,8 +220,8 @@ def check_end(self): def is_running(self): return self._running - def is_default(self): - return self._default + def is_stock(self): + return self._kind == PROGRAM_KIND_STOCK def run(self, *args): options = args[0] @@ -265,7 +267,7 @@ def as_dict(self): return {'name': self.name, 'dom_code': self._dom_code, 'code': self._code, - 'default': self._default, + 'kind': self._kind, 'id': self._id, 'modified': self._modified.isoformat(), 'status': self._status} @@ -275,7 +277,7 @@ def from_dict(cls, amap): return Program(name=amap['name'], dom_code=amap['dom_code'], code=amap['code'], - default=amap.get('default', False), + kind=amap.get('kind', PROGRAM_KIND_USER), id=amap.get('id', None), modified=datetime.fromisoformat(amap.get('modified', datetime.now().isoformat())), status=amap.get('status', None),) From f5a439515ec2a31e9805f4981b84c27c2f79cd98 Mon Sep 17 00:00:00 2001 From: previ Date: Tue, 30 May 2023 00:17:25 +0100 Subject: [PATCH 07/60] wip --- coderbot/activity.py | 18 ++++++++-- coderbot/cloud/__init__.py | 67 ++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 19e3d102..7b810ccf 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -1,6 +1,12 @@ from tinydb import TinyDB, Query from threading import Lock # Programs and Activities databases + +ACTIVITY_STATUS_DELETED = "deleted" +ACTIVITY_STATUS_ACTIVE = "active" +ACTIVITY_KIND_STOCK = "stock" +ACTIVITY_KIND_USER = "user" + class Activities(): _instance = None @@ -37,15 +43,21 @@ def save(self, name, activity): else: self.activities.update(activity, self.query.name == activity["name"]) - def delete(self, name): + def delete(self, name, logical = True): with self.lock: activities = self.activities.search(self.query.name == name) if len(activities) > 0: activity = activities[0] if activity.get("default", False) is True: self.activities.update({'default': True}, self.query.stock == True) - self.activities.remove(self.query.name == activity["name"]) + if logical: + activity["status"] = ACTIVITY_STATUS_DELETED + activity["modified"] = datetime.now().isoformat() + self.activities.update(activity, query.name == name) + else: + self.activities.remove(self.query.name == activity["name"]) + - def list(self): + def list(self, active_only = True): with self.lock: return self.activities.all() diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 76aac01f..ecb3b359 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -20,6 +20,7 @@ from activity import Activities from program import ProgramEngine import program +import activity import cloud_api_robot_client from cloud_api_robot_client.apis.tags import robot_sync_api @@ -94,7 +95,7 @@ def run(self): api_instance = robot_sync_api.RobotSyncApi(api_client) #self.sync_settings(api_instance, sync_modes["settings"]) - #self.sync_activities(api_instance, sync_modes["activities"]) + self.sync_activities(api_instance, sync_modes["activities"]) self.sync_programs(api_instance, sync_modes["programs"]) sleep(sync_period) @@ -151,26 +152,37 @@ def sync_settings(self, api_instance, sync_mode): logging.warn("Exception when calling settings RobotSyncApi: %s\n" % e) def sync_activities(self, api_instance, sync_mode): - activities = Activities.get_instance().list() + activities_local_user = list() + activities_local_stock = list() + activities_local_to_be_deleted = list() + for p in Activities.get_instance().list(active_only=False): + if p.get("kind") == activity.ACTIVITY_KIND_USER: + if p.get("status") == activity.ACTIVITY_STATUS_ACTIVE: + activities_local_user.append(p) + elif p.get("status") == activity.ACTIVITY_STATUS_ACTIVE: + activities_local_to_be_deleted.append(p) + else: + activities_local_stock.append(p) try: # Get robot activities api_response = api_instance.get_robot_activities() cloud_activities = api_response.body # cloud activities - a_c_m = {} # activities_cloud_map + activities_cloud_map = {} for a in cloud_activities: - a_c_m[a.get("id")] = a + if a.get("status") == activity.ACTIVITY_STATUS_ACTIVE: + activities_cloud_map[a.get("id")] = a - a_l_m = {} # activities_local_map + activities_local_map = {} # local activities no id - for a in activities: + for a in activities_local_user: if a.get("id") is not None: - a_l_m[a.get("id")] = a + activities_local_map[a.get("id")] = a # loop through local - for al in activities: + for al in activities_local_user: logging.info("activities.syncing: " + str(al.get("id")) + " name: " + str(al.get("name"))) - ac = a_c_m.get(al.get("id")) + ac = activities_cloud_map.get(al.get("id")) if ac is not None and ac.get("data") != al.get("data"): al["modified"] = al.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_activity_more_recent = datetime.fromisoformat(ac.get("modified")).timestamp() < datetime.fromisoformat(al.get("modified")).timestamp() @@ -214,11 +226,28 @@ def sync_activities(self, api_instance, sync_mode): elif ac is None and sync_mode in [SYNC_DOWNSTREAM]: Activities.get_instance().delete(al.get("name")) logging.info("activities.delete.downstream: " + al.get("name")) - for k, ac in a_c_m.items(): - if a_l_m.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: + + for k, ac in activities_cloud_map.items(): + if activities_local_map.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: Activities.get_instance().save(ac.get("name"), ac) logging.info("activities.create.downstream: " + ac.get("name")) + # manage local user activities to be deleted locally and upstream + for al in activities_local_to_be_deleted: + if al.get("id") is not None: + logging.info("activities.delete.upstream: " + al.get("name")) + api_response = api_instance.delete_robot_program(path_params={"activity_id":al.get("id")}) + # delete locally permanently + Activities.get_instance().delete(al.get("name"), logical=False) + + # manage local stock activities to be deleted locally + for al in activities_local_stock: + # logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) + if al.get("id") is not None and activities_cloud_map.get(al.get("id")) is None: + logging.info("activities.delete.stock.locally: " + al.get("name")) + # delete locally permanently + Activities.get_instance().delete(al.get("name"), logical=False) + except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling activities RobotSyncApi: %s\n" % e) @@ -236,15 +265,16 @@ def sync_programs(self, api_instance, sync_mode): programs_local_stock.append(p) try: - # Get robot programs + # Get cloud programs api_response = api_instance.get_robot_programs() cloud_programs = api_response.body - # cloud programs + # cloud programs in a map id : program programs_cloud_map = {} # programs_cloud_map for p in cloud_programs: if p.get("status") == program.PROGRAM_STATUS_ACTIVE: programs_cloud_map[p.get("id")] = p + # local programs, stock, in a map id : program programs_local_stock_map = {} # activities_local_map # local activities no id for p in programs_local_stock: @@ -252,6 +282,7 @@ def sync_programs(self, api_instance, sync_mode): if p.get("id") is not None: programs_local_stock_map[p.get("id")] = p + # sync local user programs # manage user programs present locally and in "active" status for pl in programs_local_user: pc = programs_cloud_map.get(pl.get("id")) @@ -263,7 +294,7 @@ def sync_programs(self, api_instance, sync_mode): logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name") + " sync_mode: " + sync_mode + " pc: " + str(pc)) if pc is not None and not pc_pl_equals: - # cloud program exists and is different + # cloud program exists and is different from local pl["modified"] = pl.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_program_more_recent = datetime.fromisoformat(pc.get("modified")).timestamp() < datetime.fromisoformat(pl.get("modified")).timestamp() if sync_mode == SYNC_UPSTREAM or (local_program_more_recent and sync_mode == SYNC_BIDIRECTIONAL) and not to_be_deleted: @@ -313,7 +344,7 @@ def sync_programs(self, api_instance, sync_mode): ProgramEngine.get_instance().delete(pl.get("name")) logging.info("programs.delete.downstream: " + pl.get("name")) - # manage user or stock programs not present locally in "active" status + # manage cloud programs not present locally in "active" status for k, pc in programs_cloud_map.items(): if programs_local_stock_map.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: pl = program.Program(name=pc.get("name"), @@ -326,15 +357,15 @@ def sync_programs(self, api_instance, sync_mode): ProgramEngine.get_instance().save(pl) logging.info("programs.create.downstream: " + pc.get("name")) - # manage programs to be deleted locally and upstream + # manage local user programs to be deleted locally and upstream for pl in programs_local_to_be_deleted: - if p.get("id") is not None: + if pl.get("id") is not None: logging.info("programs.delete.upstream: " + pl.get("name")) api_response = api_instance.delete_robot_program(path_params={"program_id":pl.get("id")}) # delete locally permanently ProgramEngine.get_instance().delete(pl.get("name"), logical=False) - # manage stock programs to be deleted locally + # manage local stock programs to be deleted locally for pl in programs_local_stock: ##logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) if pl.get("id") is not None and programs_cloud_map.get(pl.get("id")) is None: From 57b2f03fc05dc6b48463683adc85bcbfd658c049 Mon Sep 17 00:00:00 2001 From: previ Date: Fri, 2 Jun 2023 00:35:37 +0100 Subject: [PATCH 08/60] wip --- coderbot/activity.py | 8 +++++ coderbot/cloud/__init__.py | 66 ++++++++++++++++++++------------------ coderbot/program.py | 15 +++++---- coderbot/v1.yml | 6 ++-- 4 files changed, 54 insertions(+), 41 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 7b810ccf..d3be07a2 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -7,6 +7,14 @@ ACTIVITY_KIND_STOCK = "stock" ACTIVITY_KIND_USER = "user" +class Activity(): + def __init__(self, name, description, data, kind, status): + self._name = name + self._description = description + self._data = data + self._kind = kind + self._status = status + class Activities(): _instance = None diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index ecb3b359..39e85079 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -17,7 +17,7 @@ from time import sleep from config import Config -from activity import Activities +from activity import Activities, ACTIVITY_KIND_USER, ACTIVITY_KIND_STOCK, ACTIVITY_STATUS_ACTIVE, ACTIVITY_STATUS_DELETED from program import ProgramEngine import program import activity @@ -94,7 +94,7 @@ def run(self): # Create an instance of the API class api_instance = robot_sync_api.RobotSyncApi(api_client) - #self.sync_settings(api_instance, sync_modes["settings"]) + self.sync_settings(api_instance, sync_modes["settings"]) self.sync_activities(api_instance, sync_modes["activities"]) self.sync_programs(api_instance, sync_modes["programs"]) @@ -130,7 +130,7 @@ def sync_settings(self, api_instance, sync_mode): local_setting = Config.read() local_most_recent = datetime.fromisoformat(cloud_setting_object.get("modified", "2000-01-01T00:00:00.000000")).timestamp() < Config.modified() - logging.info("settings.syncing: " + cloud_setting_object.get("id", "") + " name: " + cloud_setting_object.get("name", "")) + # logging.info("settings.syncing: " + cloud_setting_object.get("id", "") + " name: " + cloud_setting_object.get("name", "")) if cloud_setting != local_setting: if sync_mode == SYNC_UPSTREAM or (sync_mode == SYNC_BIDIRECTIONAL and local_most_recent): body = Setting( @@ -155,14 +155,19 @@ def sync_activities(self, api_instance, sync_mode): activities_local_user = list() activities_local_stock = list() activities_local_to_be_deleted = list() - for p in Activities.get_instance().list(active_only=False): - if p.get("kind") == activity.ACTIVITY_KIND_USER: - if p.get("status") == activity.ACTIVITY_STATUS_ACTIVE: - activities_local_user.append(p) - elif p.get("status") == activity.ACTIVITY_STATUS_ACTIVE: - activities_local_to_be_deleted.append(p) + activities_local_map = {} + for a in Activities.get_instance().list(active_only=False): + if a.get("kind") == ACTIVITY_KIND_USER: + if a.get("status") == ACTIVITY_STATUS_ACTIVE: + activities_local_user.append(a) + if a.get("id") is not None: + activities_local_map[a.get("id")] = a + elif a.get("status") == ACTIVITY_STATUS_DELETED: + activities_local_to_be_deleted.append(a) else: - activities_local_stock.append(p) + activities_local_stock.append(a) + if a.get("id") is not None: + activities_local_map[a.get("id")] = a try: # Get robot activities api_response = api_instance.get_robot_activities() @@ -170,20 +175,15 @@ def sync_activities(self, api_instance, sync_mode): # cloud activities activities_cloud_map = {} for a in cloud_activities: - if a.get("status") == activity.ACTIVITY_STATUS_ACTIVE: + if a.get("status") == ACTIVITY_STATUS_ACTIVE: activities_cloud_map[a.get("id")] = a - activities_local_map = {} - # local activities no id - for a in activities_local_user: - if a.get("id") is not None: - activities_local_map[a.get("id")] = a - # loop through local for al in activities_local_user: logging.info("activities.syncing: " + str(al.get("id")) + " name: " + str(al.get("name"))) ac = activities_cloud_map.get(al.get("id")) - if ac is not None and ac.get("data") != al.get("data"): + ac_al_equals = (ac is not None and ac.get("data") == al.get("data")) + if ac is not None and not ac_al_equals: al["modified"] = al.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_activity_more_recent = datetime.fromisoformat(ac.get("modified")).timestamp() < datetime.fromisoformat(al.get("modified")).timestamp() if sync_mode == SYNC_UPSTREAM or (local_activity_more_recent and sync_mode == SYNC_BIDIRECTIONAL): @@ -229,8 +229,15 @@ def sync_activities(self, api_instance, sync_mode): for k, ac in activities_cloud_map.items(): if activities_local_map.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: - Activities.get_instance().save(ac.get("name"), ac) logging.info("activities.create.downstream: " + ac.get("name")) + activity = json.loads(ac.get("data")) + activity["id"] = ac.get("id") + activity["org_id"] = ac.get("org_id") + activity["name"] = ac.get("name") + activity["description"] = ac.get("description") + activity["kind"] = ac.get("kind") + activity["status"] = ac.get("status") + Activities.get_instance().save(ac.get("name"), activity) # manage local user activities to be deleted locally and upstream for al in activities_local_to_be_deleted: @@ -242,7 +249,7 @@ def sync_activities(self, api_instance, sync_mode): # manage local stock activities to be deleted locally for al in activities_local_stock: - # logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) + # logging.info("activities.check.stock.locally: " + al.get("name") + " id: " + str(al.get("id"))) if al.get("id") is not None and activities_cloud_map.get(al.get("id")) is None: logging.info("activities.delete.stock.locally: " + al.get("name")) # delete locally permanently @@ -254,15 +261,20 @@ def sync_activities(self, api_instance, sync_mode): def sync_programs(self, api_instance, sync_mode): programs_local_user = list() programs_local_stock = list() + programs_local_map = {} programs_local_to_be_deleted = list() for p in ProgramEngine.get_instance().prog_list(active_only=False): if p.get("kind") == program.PROGRAM_KIND_USER: if p.get("status") == program.PROGRAM_STATUS_ACTIVE: programs_local_user.append(p) + if p.get("id") is not None: + programs_local_map[p.get("id")] = p elif p.get("status") == program.PROGRAM_STATUS_DELETED: programs_local_to_be_deleted.append(p) else: programs_local_stock.append(p) + if p.get("id") is not None: + programs_local_map[p.get("id")] = p try: # Get cloud programs @@ -274,14 +286,6 @@ def sync_programs(self, api_instance, sync_mode): if p.get("status") == program.PROGRAM_STATUS_ACTIVE: programs_cloud_map[p.get("id")] = p - # local programs, stock, in a map id : program - programs_local_stock_map = {} # activities_local_map - # local activities no id - for p in programs_local_stock: - #logging.info("programs.local: " + str(p.get("id")) + " name: " + p.get("name")) - if p.get("id") is not None: - programs_local_stock_map[p.get("id")] = p - # sync local user programs # manage user programs present locally and in "active" status for pl in programs_local_user: @@ -291,7 +295,7 @@ def sync_programs(self, api_instance, sync_mode): pc.get("code") == pl.get("code") and pc.get("dom_code") == pl.get("dom_code") and pc.get("status") == pl.get("status")) - logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name") + " sync_mode: " + sync_mode + " pc: " + str(pc)) + logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name")) if pc is not None and not pc_pl_equals: # cloud program exists and is different from local @@ -346,7 +350,7 @@ def sync_programs(self, api_instance, sync_mode): # manage cloud programs not present locally in "active" status for k, pc in programs_cloud_map.items(): - if programs_local_stock_map.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: + if programs_local_map.get(k) is None and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: pl = program.Program(name=pc.get("name"), code=pc.get("code"), dom_code=pc.get("dom_code"), @@ -367,7 +371,7 @@ def sync_programs(self, api_instance, sync_mode): # manage local stock programs to be deleted locally for pl in programs_local_stock: - ##logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) + # logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) if pl.get("id") is not None and programs_cloud_map.get(pl.get("id")) is None: logging.info("programs.delete.stock.locally: " + pl.get("name")) # delete locally permanently diff --git a/coderbot/program.py b/coderbot/program.py index cc6abed4..14fa8858 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -122,8 +122,8 @@ def save(self, program): program._modified = datetime.now() self._program = program program_db_entry = self._program.as_dict() - if self._programs.search(query.name == program.name) != []: - self._programs.update(program_db_entry, query.name == program.name) + if self._programs.search(query.name == program._name) != []: + self._programs.update(program_db_entry, query.name == program._name) else: self._programs.insert(program_db_entry) @@ -157,7 +157,7 @@ def create(self, name, code): return self._program def is_running(self, name): - return self._program.is_running() and self._program.name == name + return self._program.is_running() and self._program._name == name def check_end(self): return self._program.check_end() @@ -181,9 +181,10 @@ class Program: def dom_code(self): return self._dom_code - def __init__(self, name, code=None, dom_code=None, kind=PROGRAM_KIND_USER, id=None, modified=None, status=None): + def __init__(self, name, description=None, code=None, dom_code=None, kind=PROGRAM_KIND_USER, id=None, modified=None, status=None): self._thread = None - self.name = name + self._name = name + self._description = description self._dom_code = dom_code self._code = code self._kind = kind @@ -229,7 +230,7 @@ def run(self, *args): program = self try: if options.get("autoRecVideo") == True: - get_cam().video_rec(program.name.replace(" ", "_")) + get_cam().video_rec(program._name.replace(" ", "_")) logging.debug("starting video") except Exception as e: logging.error("Camera not available: " + str(e)) @@ -264,7 +265,7 @@ def run(self, *args): def as_dict(self): - return {'name': self.name, + return {'name': self._name, 'dom_code': self._dom_code, 'code': self._code, 'kind': self._kind, diff --git a/coderbot/v1.yml b/coderbot/v1.yml index a3c8872f..787053d1 100644 --- a/coderbot/v1.yml +++ b/coderbot/v1.yml @@ -715,11 +715,11 @@ components: maxLength: 256 default: type: boolean - stock: - type: boolean + kind: + type: string required: - name - description - default - - stock + - kind From 19a4398bfcac4794d76f337fd822d83f61c09d18 Mon Sep 17 00:00:00 2001 From: previ Date: Sat, 3 Jun 2023 00:24:52 +0100 Subject: [PATCH 09/60] wip --- coderbot/cloud/__init__.py | 73 +++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 39e85079..e5f79e80 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -35,6 +35,9 @@ SYNC_DOWNSTREAM = 'd' SYNC_BIDIRECTIONAL = 'b' +ENTITY_KIND_USER = "user" +ENTITY_KIND_STOCK = "stock" + AUTH_FILE = "data/auth.json" class CloudManager(threading.Thread): @@ -86,17 +89,20 @@ def run(self): sync_modes = settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) sync_period = int(settings.get("sync_period", "60")) - token = self.get_token_or_register(settings) - self.configuration.access_token = token + try: + token = self.get_token_or_register(settings) + self.configuration.access_token = token - # Enter a context with an instance of the API client - with cloud_api_robot_client.ApiClient(self.configuration) as api_client: - # Create an instance of the API class - api_instance = robot_sync_api.RobotSyncApi(api_client) - - self.sync_settings(api_instance, sync_modes["settings"]) - self.sync_activities(api_instance, sync_modes["activities"]) - self.sync_programs(api_instance, sync_modes["programs"]) + # Enter a context with an instance of the API client + with cloud_api_robot_client.ApiClient(self.configuration) as api_client: + # Create an instance of the API class + api_instance = robot_sync_api.RobotSyncApi(api_client) + + self.sync_settings(api_instance, sync_modes["settings"]) + self.sync_activities(api_instance, sync_modes["activities"]) + self.sync_programs(api_instance, sync_modes["programs"]) + except Exception as e: + logging.warn("run.sync.api_not_available: " + str(e)) sleep(sync_period) logging.info("run.sync.end") @@ -105,9 +111,9 @@ def get_token_or_register(self, settings): logging.info("run.check.token") token = self.get_auth().get("token") reg_otp = settings.get("reg_otp") - logging.info("otp_reg:" + reg_otp) try: if token is None and reg_otp is not None: + logging.info("run.get_token_or_register.get_token") with cloud_api_robot_client.ApiClient(self.configuration) as api_client: api_instance = robot_sync_api.RobotSyncApi(api_client) body = RobotRegisterData( @@ -121,33 +127,36 @@ def get_token_or_register(self, settings): except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling register_robot RobotSyncApi: %s\n" % e) + # sync settings def sync_settings(self, api_instance, sync_mode): + # Sync settings is different from syncing other entities, like activities and programs, since there can only + # be a single entity for each robot (both device and cloud twin). + # So the algo is simpler and favorites the "stock" entity over the "user" entity, if available. try: - # Create an instance of the API class api_response = api_instance.get_robot_setting() cloud_setting_object = api_response.body cloud_setting = json.loads(cloud_setting_object.get('data')) local_setting = Config.read() - local_most_recent = datetime.fromisoformat(cloud_setting_object.get("modified", "2000-01-01T00:00:00.000000")).timestamp() < Config.modified() + local_most_recent = datetime.fromisoformat(cloud_setting_object.get("modified")).timestamp() < Config.modified() + cloud_kind_user = cloud_setting_object.get("kind") == ENTITY_KIND_USER # logging.info("settings.syncing: " + cloud_setting_object.get("id", "") + " name: " + cloud_setting_object.get("name", "")) - if cloud_setting != local_setting: - if sync_mode == SYNC_UPSTREAM or (sync_mode == SYNC_BIDIRECTIONAL and local_most_recent): - body = Setting( - id = cloud_setting_object.get('id'), - org_id = cloud_setting_object.get('org_id'), - name = cloud_setting_object.get('name'), - description = cloud_setting_object.get('description'), - data = json.dumps(local_setting), - kind = local_setting.get("kind", "stock"), - modified = datetime.now().isoformat(), - status = cloud_setting_object.get('status'), - ) - api_response = api_instance.set_robot_setting(body) - logging.info("settings.upstream") - if sync_mode == SYNC_DOWNSTREAM: # setting, down - Config.write(cloud_setting.data.setting) - logging.info("settings.downstream") + if cloud_kind_user and cloud_setting != local_setting and local_most_recent: + body = Setting( + id = cloud_setting_object.get('id'), + org_id = cloud_setting_object.get('org_id'), + name = cloud_setting_object.get('name'), + description = cloud_setting_object.get('description'), + data = json.dumps(local_setting), + kind = local_setting.get("kind", ENTITY_KIND_STOCK), + modified = datetime.now().isoformat(), + status = cloud_setting_object.get('status'), + ) + api_response = api_instance.set_robot_setting(body) + logging.info("settings.upstream") + elif cloud_setting != local_setting: # setting, down + Config.write(cloud_setting.data.setting) + logging.info("settings.downstream") except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling settings RobotSyncApi: %s\n" % e) @@ -195,7 +204,7 @@ def sync_activities(self, api_instance, sync_mode): name=al.get("name"), description=al.get("description"), data=json.dumps(al.get("data")), - kind = al.get("kind", "stock"), + kind = al.get("kind", ENTITY_KIND_STOCK), modified=al.get("modified").isoformat(), status='active', ) @@ -214,7 +223,7 @@ def sync_activities(self, api_instance, sync_mode): name=al.get("name"), description=al.get("description"), data=json.dumps(al), - kind=al.get("kind", "stock"), + kind=al.get("kind", ENTITY_KIND_STOCK), modified=al.get("modified", datetime.now(tz=timezone.utc).isoformat()), status="active", ) From 9377a573d922ffb97259dcb785ef5677dfc0a530 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 00:04:27 +0100 Subject: [PATCH 10/60] wip --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 35aad39d..6734cd58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ Flask==2.2.3 Flask-Cors==3.0.10 tinydb==4.7.1 Werkzeug==2.2.3 +cloud_api_robot_client @ git+https://github.com/CoderBotOrg/cloud_api_robot_client.git # Misc utils setuptools==67.4.0 From 1cf11aaa6b1ea87ebbe38e754c28161b92747eff Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 00:09:13 +0100 Subject: [PATCH 11/60] wip --- .github/workflows/build_backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index 0331b4c8..cdd253c5 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -22,7 +22,7 @@ jobs: - run: | export PYTHONPATH=./stub:./coderbot:./test python3 coderbot/main.py > coderbot.log & - sleep 30 + sleep 60 apt-get install -y python3-venv mkdir -p schemathesis python3 -m venv schemathesis From 8f0e3774b7e6f8a6116ff4a0aa83b09d0a075cde Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 00:10:22 +0100 Subject: [PATCH 12/60] wip --- docker/stub/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/stub/requirements.txt b/docker/stub/requirements.txt index 81c1bc21..a758a746 100644 --- a/docker/stub/requirements.txt +++ b/docker/stub/requirements.txt @@ -4,6 +4,7 @@ Flask==2.2.3 Flask-Cors==3.0.10 tinydb==4.7.1 Werkzeug==2.2.3 +cloud_api_robot_client @ git+https://github.com/CoderBotOrg/cloud_api_robot_client.git # Misc utils setuptools==67.4.0 From 060707607f7cf6ec73a207454751b2840e26f2e5 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 22:26:11 +0100 Subject: [PATCH 13/60] wip --- .github/workflows/build_backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index cdd253c5..cd4fdff6 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -8,7 +8,7 @@ on: push jobs: test: runs-on: ubuntu-latest - container: coderbot/coderbot-ci:3.9-bullseye-slim + container: ghcr.io/coderbotorg/coderbot-ci:stub-latest steps: - uses: actions/checkout@v3 # Checking out the repo - run: pip install -r docker/stub/requirements.txt From fba1635434e43a710e89364b50e63d1bbd3e2ea2 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 22:30:54 +0100 Subject: [PATCH 14/60] wip --- .github/workflows/build_backend.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index cd4fdff6..fe547f74 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -8,7 +8,11 @@ on: push jobs: test: runs-on: ubuntu-latest - container: ghcr.io/coderbotorg/coderbot-ci:stub-latest + container: + image: ghcr.io/coderbotorg/coderbot-ci:stub-latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.github_token }} steps: - uses: actions/checkout@v3 # Checking out the repo - run: pip install -r docker/stub/requirements.txt From e5a259292e082b15e7998d758a445e43b638d931 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 22:38:32 +0100 Subject: [PATCH 15/60] wip --- .github/workflows/build_backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index fe547f74..37dc3a9a 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -12,7 +12,7 @@ jobs: image: ghcr.io/coderbotorg/coderbot-ci:stub-latest credentials: username: ${{ github.actor }} - password: ${{ secrets.github_token }} + password: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 # Checking out the repo - run: pip install -r docker/stub/requirements.txt From 870ea4bfe8da96f75729e8a9d9a0222b53a4add7 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 23:44:43 +0200 Subject: [PATCH 16/60] wip --- .github/workflows/build_backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index 37dc3a9a..9cec776a 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -13,6 +13,7 @@ jobs: credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + steps: - uses: actions/checkout@v3 # Checking out the repo - run: pip install -r docker/stub/requirements.txt From ab771592ba38652fac19afe2f1e03e112aea5db6 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 23:51:13 +0200 Subject: [PATCH 17/60] wip --- .github/workflows/build_backend.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index 9cec776a..3b26c239 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -12,8 +12,8 @@ jobs: image: ghcr.io/coderbotorg/coderbot-ci:stub-latest credentials: username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - + password: ${{ secrets.GHCR_READ }} + steps: - uses: actions/checkout@v3 # Checking out the repo - run: pip install -r docker/stub/requirements.txt From 0b84abb0e64ac186ae9956baba12c4e15d678b06 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 22:55:25 +0100 Subject: [PATCH 18/60] wip --- .github/workflows/build_backend.yml | 1 - coderbot/activity.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index 3b26c239..189d0835 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -13,7 +13,6 @@ jobs: credentials: username: ${{ github.actor }} password: ${{ secrets.GHCR_READ }} - steps: - uses: actions/checkout@v3 # Checking out the repo - run: pip install -r docker/stub/requirements.txt diff --git a/coderbot/activity.py b/coderbot/activity.py index d3be07a2..3d4f13f7 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -1,5 +1,6 @@ from tinydb import TinyDB, Query from threading import Lock +from datetime import datetime # Programs and Activities databases ACTIVITY_STATUS_DELETED = "deleted" From cb8f7f9456e33470d7b6e9ab9d82cd2136121693 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 23:05:32 +0100 Subject: [PATCH 19/60] wip --- coderbot/activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 3d4f13f7..b44bd777 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -62,7 +62,7 @@ def delete(self, name, logical = True): if logical: activity["status"] = ACTIVITY_STATUS_DELETED activity["modified"] = datetime.now().isoformat() - self.activities.update(activity, query.name == name) + self.activities.update(activity, self.query.name == name) else: self.activities.remove(self.query.name == activity["name"]) From 064517271e08abe2bd74fac6f1a387cbbf4820a2 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 23:15:13 +0100 Subject: [PATCH 20/60] wip --- coderbot/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderbot/api.py b/coderbot/api.py index a5fe50f5..12d13f23 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -340,7 +340,7 @@ def statusProgram(name): prog = prog_engine.get_current_program() if prog is None: prog = Program("") - return {'name': prog.name, "running": prog.is_running(), "log": prog_engine.get_log()} + return {'name': prog._name, "running": prog.is_running(), "log": prog_engine.get_log()} ## Activities From 304f2210154ba3158ec780a3aca4bf82e25099c6 Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 23:33:21 +0100 Subject: [PATCH 21/60] wip --- docker/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6b88fce8..7f305ebe 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM coderbot/rpi-debian:bullseye-20220923 +FROM coderbot/rpi-debian:bullseye-20230530 ENV QEMU_CPU=max ENV DEBIAN_FRONTEND=noninteractive @@ -19,7 +19,8 @@ RUN install_packages \ libatlas-base-dev \ libhdf5-dev \ alsa-utils \ - espeak + espeak \ + git RUN install_packages \ libharfbuzz-bin \ libwebp6 \ From affa7264f6c455133e238cf23b7988f80c1c038f Mon Sep 17 00:00:00 2001 From: previ Date: Mon, 5 Jun 2023 23:46:03 +0100 Subject: [PATCH 22/60] wip --- coderbot/api.py | 2 +- coderbot/program.py | 12 ++++++++---- docker/stub/Dockerfile | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index 12d13f23..a5fe50f5 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -340,7 +340,7 @@ def statusProgram(name): prog = prog_engine.get_current_program() if prog is None: prog = Program("") - return {'name': prog._name, "running": prog.is_running(), "log": prog_engine.get_log()} + return {'name': prog.name, "running": prog.is_running(), "log": prog_engine.get_log()} ## Activities diff --git a/coderbot/program.py b/coderbot/program.py index 14fa8858..a892950e 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -122,8 +122,8 @@ def save(self, program): program._modified = datetime.now() self._program = program program_db_entry = self._program.as_dict() - if self._programs.search(query.name == program._name) != []: - self._programs.update(program_db_entry, query.name == program._name) + if self._programs.search(query.name == program.name) != []: + self._programs.update(program_db_entry, query.name == program.name) else: self._programs.insert(program_db_entry) @@ -157,7 +157,7 @@ def create(self, name, code): return self._program def is_running(self, name): - return self._program.is_running() and self._program._name == name + return self._program.is_running() and self._program.name == name def check_end(self): return self._program.check_end() @@ -230,7 +230,7 @@ def run(self, *args): program = self try: if options.get("autoRecVideo") == True: - get_cam().video_rec(program._name.replace(" ", "_")) + get_cam().video_rec(program.name.replace(" ", "_")) logging.debug("starting video") except Exception as e: logging.error("Camera not available: " + str(e)) @@ -264,6 +264,10 @@ def run(self, *args): self._running = False + @property + def name(self): + return self._name + def as_dict(self): return {'name': self._name, 'dom_code': self._dom_code, diff --git a/docker/stub/Dockerfile b/docker/stub/Dockerfile index aaf42d81..43180a2a 100644 --- a/docker/stub/Dockerfile +++ b/docker/stub/Dockerfile @@ -18,6 +18,7 @@ RUN apt-get update -y && apt-get install -y \ libhdf5-dev \ alsa-utils \ espeak \ + git \ libharfbuzz-bin \ libwebp6 \ libilmbase25 \ From b2999aa6ca1828ef6986be618cbb261d6b412162 Mon Sep 17 00:00:00 2001 From: previ Date: Wed, 7 Jun 2023 00:20:31 +0100 Subject: [PATCH 23/60] wip --- coderbot/api.py | 10 ++- coderbot/cloud/__init__.py | 133 ++++++++++++++++++++++--------------- coderbot/v1.yml | 31 +++++++++ 3 files changed, 120 insertions(+), 54 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index a5fe50f5..c413f8b6 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -22,6 +22,7 @@ from runtime_test import run_test from musicPackages import MusicPackageManager from program import Program, ProgramEngine +from cloud import CloudManager from balena import Balena from coderbot import CoderBot @@ -404,4 +405,11 @@ def deleteCNNModel(name): cnn = CNNManager.get_instance() model_status = cnn.delete_model(model_name=name) - return model_status \ No newline at end of file + return model_status + +def cloudSyncRequest(): + CloudManager.get_instance().sync() + return 200 + +def cloudSyncStatus(): + return CloudManager.get_instance().sync_status() \ No newline at end of file diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index e5f79e80..e5b353ce 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -70,6 +70,12 @@ def get_instance(cls): return cls._instance def __init__(self): + self._syncing = False + self._sync_status = { + "settings": "", + "activities": "", + "programs": "" + } threading.Thread.__init__(self) # Defining the host is optional and defaults to https://api.coderbot.org/api/v1 # See configuration.py for a list of all supported configuration parameters. @@ -84,28 +90,42 @@ def __init__(self): def run(self): while(True): - settings = Config.read() - logging.info("run.sync.begin") - sync_modes = settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) - sync_period = int(settings.get("sync_period", "60")) + sync_period = int(Config.read().get("sync_period", "60")) + self.sync() + sleep(sync_period) - try: - token = self.get_token_or_register(settings) - self.configuration.access_token = token + def syncing(self): + return self._syncing - # Enter a context with an instance of the API client - with cloud_api_robot_client.ApiClient(self.configuration) as api_client: - # Create an instance of the API class - api_instance = robot_sync_api.RobotSyncApi(api_client) - - self.sync_settings(api_instance, sync_modes["settings"]) - self.sync_activities(api_instance, sync_modes["activities"]) - self.sync_programs(api_instance, sync_modes["programs"]) - except Exception as e: - logging.warn("run.sync.api_not_available: " + str(e)) + def sync_status(self): + return self._sync_status - sleep(sync_period) - logging.info("run.sync.end") + def sync(self): + if self._syncing == True: + return + self._syncing = True + + settings = Config.read() + logging.info("run.sync.begin") + sync_modes = settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) + + try: + token = self.get_token_or_register(settings) + self.configuration.access_token = token + + # Enter a context with an instance of the API client + with cloud_api_robot_client.ApiClient(self.configuration) as api_client: + # Create an instance of the API class + api_instance = robot_sync_api.RobotSyncApi(api_client) + + self.sync_settings(api_instance, sync_modes["settings"]) + self.sync_activities(api_instance, sync_modes["activities"]) + self.sync_programs(api_instance, sync_modes["programs"]) + except Exception as e: + logging.warn("run.sync.api_not_available: " + str(e)) + + logging.info("run.sync.end") + self._syncing = False def get_token_or_register(self, settings): logging.info("run.check.token") @@ -133,6 +153,7 @@ def sync_settings(self, api_instance, sync_mode): # be a single entity for each robot (both device and cloud twin). # So the algo is simpler and favorites the "stock" entity over the "user" entity, if available. try: + self._sync_status["settings"] = "syncing" api_response = api_instance.get_robot_setting() cloud_setting_object = api_response.body cloud_setting = json.loads(cloud_setting_object.get('data')) @@ -155,29 +176,31 @@ def sync_settings(self, api_instance, sync_mode): api_response = api_instance.set_robot_setting(body) logging.info("settings.upstream") elif cloud_setting != local_setting: # setting, down - Config.write(cloud_setting.data.setting) + Config.write(cloud_setting) logging.info("settings.downstream") + self._sync_status["settings"] = "synced" except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling settings RobotSyncApi: %s\n" % e) def sync_activities(self, api_instance, sync_mode): - activities_local_user = list() - activities_local_stock = list() - activities_local_to_be_deleted = list() - activities_local_map = {} - for a in Activities.get_instance().list(active_only=False): - if a.get("kind") == ACTIVITY_KIND_USER: - if a.get("status") == ACTIVITY_STATUS_ACTIVE: - activities_local_user.append(a) + try: + self._sync_status["activities"] = "syncing" + activities_local_user = list() + activities_local_stock = list() + activities_local_to_be_deleted = list() + activities_local_map = {} + for a in Activities.get_instance().list(active_only=False): + if a.get("kind") == ACTIVITY_KIND_USER: + if a.get("status") == ACTIVITY_STATUS_ACTIVE: + activities_local_user.append(a) + if a.get("id") is not None: + activities_local_map[a.get("id")] = a + elif a.get("status") == ACTIVITY_STATUS_DELETED: + activities_local_to_be_deleted.append(a) + else: + activities_local_stock.append(a) if a.get("id") is not None: activities_local_map[a.get("id")] = a - elif a.get("status") == ACTIVITY_STATUS_DELETED: - activities_local_to_be_deleted.append(a) - else: - activities_local_stock.append(a) - if a.get("id") is not None: - activities_local_map[a.get("id")] = a - try: # Get robot activities api_response = api_instance.get_robot_activities() cloud_activities = api_response.body @@ -264,28 +287,31 @@ def sync_activities(self, api_instance, sync_mode): # delete locally permanently Activities.get_instance().delete(al.get("name"), logical=False) + self._sync_status["activities"] = "synced" except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling activities RobotSyncApi: %s\n" % e) + self._sync_status["activities"] = "failed" def sync_programs(self, api_instance, sync_mode): - programs_local_user = list() - programs_local_stock = list() - programs_local_map = {} - programs_local_to_be_deleted = list() - for p in ProgramEngine.get_instance().prog_list(active_only=False): - if p.get("kind") == program.PROGRAM_KIND_USER: - if p.get("status") == program.PROGRAM_STATUS_ACTIVE: - programs_local_user.append(p) + try: + self._sync_status["programs"] = "syncing" + programs_local_user = list() + programs_local_stock = list() + programs_local_map = {} + programs_local_to_be_deleted = list() + for p in ProgramEngine.get_instance().prog_list(active_only=False): + if p.get("kind") == program.PROGRAM_KIND_USER: + if p.get("status") == program.PROGRAM_STATUS_ACTIVE: + programs_local_user.append(p) + if p.get("id") is not None: + programs_local_map[p.get("id")] = p + elif p.get("status") == program.PROGRAM_STATUS_DELETED: + programs_local_to_be_deleted.append(p) + else: + programs_local_stock.append(p) if p.get("id") is not None: programs_local_map[p.get("id")] = p - elif p.get("status") == program.PROGRAM_STATUS_DELETED: - programs_local_to_be_deleted.append(p) - else: - programs_local_stock.append(p) - if p.get("id") is not None: - programs_local_map[p.get("id")] = p - - try: + # Get cloud programs api_response = api_instance.get_robot_programs() cloud_programs = api_response.body @@ -385,6 +411,7 @@ def sync_programs(self, api_instance, sync_mode): logging.info("programs.delete.stock.locally: " + pl.get("name")) # delete locally permanently ProgramEngine.get_instance().delete(pl.get("name"), logical=False) - + self._sync_status["programs"] = "synced" except cloud_api_robot_client.ApiException as e: - logging.warn("Exception when calling programs RobotSyncApi: %s\n" % e) \ No newline at end of file + logging.warn("Exception when calling programs RobotSyncApi: %s\n" % e) + self._sync_status["programs"] = "failed" \ No newline at end of file diff --git a/coderbot/v1.yml b/coderbot/v1.yml index 787053d1..01af71e8 100644 --- a/coderbot/v1.yml +++ b/coderbot/v1.yml @@ -605,6 +605,27 @@ paths: responses: 200: description: "CNN Model deleted" + /cloud/sync: + post: + operationId: "api.cloudSyncRequest" + summary: "request a Sync activity" + tags: + - Cloud Sync + responses: + 200: + description: "Cloud Sync request" + get: + operationId: "api.cloudSyncStatus" + summary: "request a Sync activity" + tags: + - Cloud Sync + responses: + 200: + description: "Cloud Sync request" + content: + application/json: + schema: + $ref: '#/components/schemas/CloudSyncStatus' components: schemas: @@ -723,3 +744,13 @@ components: - default - kind + CloudSyncStatus: + type: object + properties: + settings: + type: string + activities: + type: string + programs: + type: string + From a430f2ad334e1dcafe7c18bcb1aad30afc71c7fa Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 11 Jun 2023 23:14:20 +0100 Subject: [PATCH 24/60] wip --- coderbot/api.py | 20 +++++++++- coderbot/cloud/__init__.py | 60 ++++++++++++++++++++++------- coderbot/v1.yml | 79 +++++++++++++++++++++++++++++++++++++- 3 files changed, 144 insertions(+), 15 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index c413f8b6..e1a3470b 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -412,4 +412,22 @@ def cloudSyncRequest(): return 200 def cloudSyncStatus(): - return CloudManager.get_instance().sync_status() \ No newline at end of file + return CloudManager.get_instance().sync_status() + +def cloudRegistrationRequest(): + CloudManager.get_instance().register() + return 200 + +def cloudRegistrationDelete(): + CloudManager.get_instance().unregister() + return 200 + +def cloudRegistrationStatus(): + return { + "registered": CloudManager.get_instance().registration_status(), + "name": config.get('coderbot_name', ""), + "description": config.get('coderbot_description', ""), + "org_id": config.get('org_id', ""), + "org_name": config.get('org_name', ""), + "org_description": config.get('org_description', "") + } diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index e5b353ce..3e5e5c96 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -88,6 +88,36 @@ def __init__(self): self.write_auth({}) self.start() + def register(self, registration_request): + logging.info("register.check.token") + token = self.get_auth().get("token") + if token: + logging.warn("register.check.token_already_there") + return + reg_otp = registration_request.get("otp") + try: + self._sync_status["registration"] = "registering" + logging.info("register.get_token") + with cloud_api_robot_client.ApiClient(self.configuration) as api_client: + api_instance = robot_sync_api.RobotSyncApi(api_client) + body = RobotRegisterData( + otp=reg_otp, + ) + api_response = api_instance.register_robot(body=body) + logging.info(api_response.body) + token = api_response.body.get("token") + self.write_auth({"token":token}) + self._sync_status["registration"] = "registered" + except cloud_api_robot_client.ApiException as e: + logging.warn("Exception when calling register_robot RobotSyncApi: %s\n" % e) + raise + + def unregister(self): + self.write_auth({ "token": None }) + + def registration_status(self): + return self._auth.get("token") is not None + def run(self): while(True): sync_period = int(Config.read().get("sync_period", "60")) @@ -109,20 +139,21 @@ def sync(self): logging.info("run.sync.begin") sync_modes = settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) - try: - token = self.get_token_or_register(settings) - self.configuration.access_token = token + token = self.get_auth().get("token") + if token is not None: + try: + self.configuration.access_token = token - # Enter a context with an instance of the API client - with cloud_api_robot_client.ApiClient(self.configuration) as api_client: - # Create an instance of the API class - api_instance = robot_sync_api.RobotSyncApi(api_client) - - self.sync_settings(api_instance, sync_modes["settings"]) - self.sync_activities(api_instance, sync_modes["activities"]) - self.sync_programs(api_instance, sync_modes["programs"]) - except Exception as e: - logging.warn("run.sync.api_not_available: " + str(e)) + # Enter a context with an instance of the API client + with cloud_api_robot_client.ApiClient(self.configuration) as api_client: + # Create an instance of the API class + api_instance = robot_sync_api.RobotSyncApi(api_client) + + self.sync_settings(api_instance, sync_modes["settings"]) + self.sync_activities(api_instance, sync_modes["activities"]) + self.sync_programs(api_instance, sync_modes["programs"]) + except Exception as e: + logging.warn("run.sync.api_not_available: " + str(e)) logging.info("run.sync.end") self._syncing = False @@ -133,6 +164,7 @@ def get_token_or_register(self, settings): reg_otp = settings.get("reg_otp") try: if token is None and reg_otp is not None: + self._sync_status["registration"] = "registering" logging.info("run.get_token_or_register.get_token") with cloud_api_robot_client.ApiClient(self.configuration) as api_client: api_instance = robot_sync_api.RobotSyncApi(api_client) @@ -143,6 +175,7 @@ def get_token_or_register(self, settings): logging.info(api_response.body) token = api_response.body.get("token") self.write_auth({"token":token}) + self._sync_status["registration"] = "registered" return token except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling register_robot RobotSyncApi: %s\n" % e) @@ -181,6 +214,7 @@ def sync_settings(self, api_instance, sync_mode): self._sync_status["settings"] = "synced" except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling settings RobotSyncApi: %s\n" % e) + self._sync_status["registration"] = "failed" def sync_activities(self, api_instance, sync_mode): try: diff --git a/coderbot/v1.yml b/coderbot/v1.yml index 01af71e8..8539dbb5 100644 --- a/coderbot/v1.yml +++ b/coderbot/v1.yml @@ -605,6 +605,49 @@ paths: responses: 200: description: "CNN Model deleted" + + /cloud/registration: + post: + operationId: "api.cloudRegistrationRequest" + summary: "request a Registration activity" + tags: + - Cloud Sync + requestBody: + description: Registration Request parameters + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CloudRegistrationRequest' + responses: + 200: + description: "Cloud Sync request" + get: + operationId: "api.cloudRegistrationStatus" + summary: "get status of a Registration activity" + tags: + - Cloud Sync + responses: + 200: + description: "Cloud Sync request" + content: + application/json: + schema: + $ref: '#/components/schemas/CloudRegistrationStatus' + + delete: + operationId: "api.cloudRegistrationDelete" + summary: "get status of a Registration activity" + tags: + - Cloud Sync + responses: + 200: + description: "Cloud Sync request" + content: + application/json: + schema: + $ref: '#/components/schemas/CloudRegistrationStatus' + /cloud/sync: post: operationId: "api.cloudSyncRequest" @@ -743,7 +786,41 @@ components: - description - default - kind - + + CloudRegistrationRequest: + type: object + properties: + name: + type: string + minLength: 4 + maxLength: 64 + description: + type: string + minLength: 4 + maxLength: 256 + otp: + type: string + minLength: 8 + maxLength: 8 + + CloudRegistrationStatus: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 64 + description: + type: string + minLength: 0 + maxLength: 256 + org_id: + type: string + org_name: + type: string + org_description: + type: string + CloudSyncStatus: type: object properties: From 639d4b930e8adbc276e773256d8cbc45f84f14d1 Mon Sep 17 00:00:00 2001 From: previ Date: Wed, 16 Aug 2023 16:58:12 +0100 Subject: [PATCH 25/60] wip --- coderbot/api.py | 52 +++++++++++++++++--------------- coderbot/audio.py | 6 ++-- coderbot/camera.py | 30 +++++++++---------- coderbot/cloud/__init__.py | 59 +++++++++++++++++++++---------------- coderbot/cnn/cnn_manager.py | 6 ++-- coderbot/coderbot.py | 6 ++-- coderbot/main.py | 16 +++++----- coderbot/motion.py | 21 +++++++------ coderbot/music.py | 19 ++++++------ coderbot/musicPackages.py | 8 ++--- coderbot/program.py | 6 ++-- 11 files changed, 121 insertions(+), 108 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index e1a3470b..2dc48efa 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -22,6 +22,7 @@ from runtime_test import run_test from musicPackages import MusicPackageManager from program import Program, ProgramEngine +from motion import Motion from cloud import CloudManager from balena import Balena @@ -29,18 +30,20 @@ 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)))) -audio_device = Audio.get_instance() -cam = Camera.get_instance() +settings = Config.read().get("settings") +bot = CoderBot.get_instance(settings=settings, motor_trim_factor=float(settings.get('move_motor_trim', 1.0)), + motor_max_power=int(settings.get('motor_max_power', 100)), + motor_min_power=int(settings.get('motor_min_power', 0)), + hw_version=settings.get('hardware_version'), + pid_params=(float(settings.get('pid_kp', 1.0)), + float(settings.get('pid_kd', 0.1)), + float(settings.get('pid_ki', 0.01)), + float(settings.get('pid_max_speed', 200)), + float(settings.get('pid_sample_time', 0.01)))) +audio_device = Audio.get_instance(settings) +cam = Camera.get_instance(settings) +Motion.get_instance(settings) +CNNManager.get_instance(settings) def get_serial(): """ @@ -140,7 +143,7 @@ def turn(body): def takePhoto(): try: cam.photo_take() - audio_device.say(config.get("sound_shutter")) + audio_device.say(settings.get("sound_shutter")) return 200 except Exception as e: logging.warning("Error: %s", e) @@ -148,7 +151,7 @@ def takePhoto(): def recVideo(): try: cam.video_rec() - audio_device.say(config.get("sound_shutter")) + audio_device.say(settings.get("sound_shutter")) return 200 except Exception as e: logging.warning("Error: %s", e) @@ -156,7 +159,7 @@ def recVideo(): def stopVideo(): try: cam.video_stop() - audio_device.say(config.get("sound_shutter")) + audio_device.say(settings.get("sound_shutter")) return 200 except Exception as e: logging.warning("Error: %s", e) @@ -173,7 +176,7 @@ def reset(): return 200 def halt(): - audio_device.say(what=config.get("sound_stop")) + audio_device.say(what=settings.get("sound_stop")) Balena.get_instance().shutdown() return 200 @@ -181,7 +184,7 @@ def restart(): Balena.get_instance().restart() def reboot(): - audio_device.say(what=config.get("sound_stop")) + audio_device.say(what=settings.get("sound_stop")) Balena.get_instance().reboot() return 200 @@ -414,8 +417,8 @@ def cloudSyncRequest(): def cloudSyncStatus(): return CloudManager.get_instance().sync_status() -def cloudRegistrationRequest(): - CloudManager.get_instance().register() +def cloudRegistrationRequest(body): + CloudManager.get_instance().register(body) return 200 def cloudRegistrationDelete(): @@ -423,11 +426,12 @@ def cloudRegistrationDelete(): return 200 def cloudRegistrationStatus(): + registration = settings.get('cloud_registration', {}) return { "registered": CloudManager.get_instance().registration_status(), - "name": config.get('coderbot_name', ""), - "description": config.get('coderbot_description', ""), - "org_id": config.get('org_id', ""), - "org_name": config.get('org_name', ""), - "org_description": config.get('org_description', "") + "name": registration.get('name', {}), + "description": registration.get('description', ""), + "org_id": registration.get('org_id', ""), + "org_name": registration.get('org_name', ""), + "org_description": registration.get('org_description', "") } diff --git a/coderbot/audio.py b/coderbot/audio.py index ed1a469e..f67f99ff 100644 --- a/coderbot/audio.py +++ b/coderbot/audio.py @@ -46,12 +46,12 @@ class Audio: _instance = None @classmethod - def get_instance(cls): + def get_instance(cls, settings=None): if cls._instance is None: - cls._instance = Audio() + cls._instance = Audio(settings) return cls._instance - def __init__(self): + def __init__(self, settings): self.pa = pyaudio.PyAudio() try: self.stream_in = self.MicrophoneStream(FORMAT, RATE, CHUNK) diff --git a/coderbot/camera.py b/coderbot/camera.py index 8866b9fd..5247206e 100644 --- a/coderbot/camera.py +++ b/coderbot/camera.py @@ -52,30 +52,30 @@ class Camera(object): _instance = None @classmethod - def get_instance(cls): + def get_instance(cls, settings=None): if cls._instance is None: - cls._instance = Camera() + cls._instance = Camera(settings) #cls._instance.start() return cls._instance - def __init__(self): + def __init__(self, settings): logging.info("starting camera") cam_props = {"width":640, "height":512, - "cv_image_factor": config.Config.get().get("cv_image_factor"), - "exposure_mode": config.Config.get().get("camera_exposure_mode"), - "framerate": config.Config.get().get("camera_framerate"), - "bitrate": config.Config.get().get("camera_jpeg_bitrate"), - "jpeg_quality": int(config.Config.get().get("camera_jpeg_quality"))} + "cv_image_factor": settings.get("cv_image_factor"), + "exposure_mode": settings.get("camera_exposure_mode"), + "framerate": settings.get("camera_framerate"), + "bitrate": settings.get("camera_jpeg_bitrate"), + "jpeg_quality": int(settings.get("camera_jpeg_quality"))} self._camera = camera.Camera(props=cam_props) self.recording = False self.video_start_time = time.time() + 8640000 self._image_time = 0 - self._cv_image_factor = int(config.Config.get().get("cv_image_factor", 4)) - self._image_refresh_timeout = float(config.Config.get().get("camera_refresh_timeout", 0.1)) - self._color_object_size_min = int(config.Config.get().get("camera_color_object_size_min", 80)) / (self._cv_image_factor * self._cv_image_factor) - self._color_object_size_max = int(config.Config.get().get("camera_color_object_size_max", 32000)) / (self._cv_image_factor * self._cv_image_factor) - self._path_object_size_min = int(config.Config.get().get("camera_path_object_size_min", 80)) / (self._cv_image_factor * self._cv_image_factor) - self._path_object_size_max = int(config.Config.get().get("camera_path_object_size_max", 32000)) / (self._cv_image_factor * self._cv_image_factor) + self._cv_image_factor = int(settings.get("cv_image_factor", 4)) + self._image_refresh_timeout = float(settings.get("camera_refresh_timeout", 0.1)) + self._color_object_size_min = int(settings.get("camera_color_object_size_min", 80)) / (self._cv_image_factor * self._cv_image_factor) + self._color_object_size_max = int(settings.get("camera_color_object_size_max", 32000)) / (self._cv_image_factor * self._cv_image_factor) + self._path_object_size_min = int(settings.get("camera_path_object_size_min", 80)) / (self._cv_image_factor * self._cv_image_factor) + self._path_object_size_max = int(settings.get("camera_path_object_size_max", 32000)) / (self._cv_image_factor * self._cv_image_factor) self.load_photo_metadata() if not self._photos: self._photos = [] @@ -86,7 +86,7 @@ def __init__(self): self.save_photo_metadata() self._cnn_classifiers = {} - cnn_model = config.Config.get().get("cnn_default_model", "") + cnn_model = settings.get("cnn_default_model", "") if cnn_model != "": try: self._cnn_classifiers[cnn_model] = CNNManager.get_instance().load_model(cnn_model) diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 3e5e5c96..40a600a3 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -10,6 +10,7 @@ # compare entity, if different, take most recent and push/pull changes # +import os import threading from datetime import datetime, timezone import logging @@ -80,7 +81,7 @@ def __init__(self): # Defining the host is optional and defaults to https://api.coderbot.org/api/v1 # See configuration.py for a list of all supported configuration parameters. self.configuration = cloud_api_robot_client.Configuration( - host = "http://192.168.1.7:8090/api/v1", + host = os.getenv("CODERBOT_CLOUD_API_ENDPOINT") + "/api/v1", ) try: self.read_auth() @@ -136,8 +137,9 @@ def sync(self): self._syncing = True settings = Config.read() + cloud_settings = settings.get("cloud") logging.info("run.sync.begin") - sync_modes = settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) + sync_modes = cloud_settings.get("sync_modes", {"settings": "n", "activities": "n", "programs": "n"}) token = self.get_auth().get("token") if token is not None: @@ -158,27 +160,27 @@ def sync(self): logging.info("run.sync.end") self._syncing = False - def get_token_or_register(self, settings): - logging.info("run.check.token") - token = self.get_auth().get("token") - reg_otp = settings.get("reg_otp") - try: - if token is None and reg_otp is not None: - self._sync_status["registration"] = "registering" - logging.info("run.get_token_or_register.get_token") - with cloud_api_robot_client.ApiClient(self.configuration) as api_client: - api_instance = robot_sync_api.RobotSyncApi(api_client) - body = RobotRegisterData( - otp=reg_otp, - ) - api_response = api_instance.register_robot(body=body) - logging.info(api_response.body) - token = api_response.body.get("token") - self.write_auth({"token":token}) - self._sync_status["registration"] = "registered" - return token - except cloud_api_robot_client.ApiException as e: - logging.warn("Exception when calling register_robot RobotSyncApi: %s\n" % e) + # def get_token_or_register(self, cloud_settings): + # logging.info("run.check.token") + # token = self.get_auth().get("token") + # reg_otp = cloud_settings.get("reg_otp") + # try: + # if token is None and reg_otp is not None: + # self._sync_status["registration"] = "registering" + # logging.info("run.get_token_or_register.get_token") + # with cloud_api_robßot_client.ApiClient(self.configuration) as api_client: + # api_instance = robot_sync_api.RobotSyncApi(api_client) + # body = RobotRegisterData( + # otp=reg_otp, + # ) + # api_response = api_instance.register_robot(body=body) + # logging.info(api_response.body) + # token = api_response.body.get("token") + # self.write_auth({"token":token}) + # self._sync_status["registration"] = "registered" + # return token + # except cloud_api_robot_client.ApiException as e: + # logging.warn("Exception when calling register_robot RobotSyncApi: %s\n" % e) # sync settings def sync_settings(self, api_instance, sync_mode): @@ -191,7 +193,12 @@ def sync_settings(self, api_instance, sync_mode): cloud_setting_object = api_response.body cloud_setting = json.loads(cloud_setting_object.get('data')) - local_setting = Config.read() + # sync only the "settings" and "cloud" sections, do not sync "network" + config = Config.read() + local_setting = { + "settings": config.get("settings"), + "cloud": config.get("cloud") + } local_most_recent = datetime.fromisoformat(cloud_setting_object.get("modified")).timestamp() < Config.modified() cloud_kind_user = cloud_setting_object.get("kind") == ENTITY_KIND_USER # logging.info("settings.syncing: " + cloud_setting_object.get("id", "") + " name: " + cloud_setting_object.get("name", "")) @@ -209,7 +216,9 @@ def sync_settings(self, api_instance, sync_mode): api_response = api_instance.set_robot_setting(body) logging.info("settings.upstream") elif cloud_setting != local_setting: # setting, down - Config.write(cloud_setting) + config["settings"] = cloud_setting["settings"] + config["cloud"] = cloud_setting["cloud"] + Config.write(config) logging.info("settings.downstream") self._sync_status["settings"] = "synced" except cloud_api_robot_client.ApiException as e: diff --git a/coderbot/cnn/cnn_manager.py b/coderbot/cnn/cnn_manager.py index 5ed012d2..6bd16db6 100644 --- a/coderbot/cnn/cnn_manager.py +++ b/coderbot/cnn/cnn_manager.py @@ -44,13 +44,13 @@ class CNNManager(object): instance = None @classmethod - def get_instance(cls): + def get_instance(cls, settings=None): if cls.instance is None: - cls.instance = CNNManager() + cls.instance = CNNManager(settings) return cls.instance - def __init__(self): + def __init__(self, settings): try: f = open(MODEL_METADATA, "r") self._models = json.load(f) diff --git a/coderbot/coderbot.py b/coderbot/coderbot.py index 37877506..44d4d31b 100644 --- a/coderbot/coderbot.py +++ b/coderbot/coderbot.py @@ -101,7 +101,7 @@ class CoderBot(object): # pylint: disable=too-many-instance-attributes - def __init__(self, motor_trim_factor=1.0, motor_min_power=0, motor_max_power=100, hw_version="5", pid_params=(0.8, 0.1, 0.01, 200, 0.01)): + def __init__(self, settings, motor_trim_factor=1.0, motor_min_power=0, motor_max_power=100, hw_version="5", pid_params=(0.8, 0.1, 0.01, 200, 0.01)): try: self._mpu = mpu.AccelGyroMag() logging.info("MPU available") @@ -157,9 +157,9 @@ 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, settings=None, 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)): if not cls.the_bot: - 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) + cls.the_bot = CoderBot(settings=settings, 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 def get_motor_power(self, speed): diff --git a/coderbot/main.py b/coderbot/main.py index 61bb1b24..f6b6f9b5 100644 --- a/coderbot/main.py +++ b/coderbot/main.py @@ -48,7 +48,7 @@ connexionApp.add_api('v1.yml') def button_pushed(): - if app.bot_config.get('button_func') == "startstop": + if app.settings.get('button_func') == "startstop": prog = app.prog_engine.get_current_prog() if prog and prog.is_running(): prog.end() @@ -67,14 +67,14 @@ def run_server(): cam = None try: try: - app.bot_config = Config.read() + app.settings = Config.read().get("settings") bot = CoderBot.get_instance() try: audio_device = Audio.get_instance() - audio_device.set_volume(int(app.bot_config.get('audio_volume_level')), 100) - audio_device.say(app.bot_config.get("sound_start")) + audio_device.set_volume(int(app.settings.get('audio_volume_level')), 100) + audio_device.say(app.settings.get("sound_start")) except Exception: logging.warning("Audio not present") @@ -84,17 +84,17 @@ def run_server(): except picamera.exc.PiCameraError: logging.warning("Camera not present") - CNNManager.get_instance() + CNNManager.get_instance(app.settings) EventManager.get_instance("coderbot") - if app.bot_config.get('load_at_start') and app.bot_config.get('load_at_start'): - prog = app.prog_engine.load(app.bot_config.get('load_at_start')) + if app.settings.get('load_at_start') and app.settings.get('load_at_start'): + prog = app.prog_engine.load(app.settings.get('load_at_start')) prog.execute() CloudManager.get_instance() except ValueError as e: - app.bot_config = {} + app.settings = {} logging.error(e) bot.set_callback(bot.GPIOS.PIN_PUSHBUTTON, button_pushed, 100) diff --git a/coderbot/motion.py b/coderbot/motion.py index 95d66563..8fa5c14d 100644 --- a/coderbot/motion.py +++ b/coderbot/motion.py @@ -43,9 +43,9 @@ class Motion: # pylint: disable=too-many-instance-attributes - def __init__(self): - self.bot = CoderBot.get_instance() - self.cam = Camera.get_instance() + def __init__(self, settings): + self.bot = CoderBot.get_instance(settings) + self.cam = Camera.get_instance(settings) self.track_len = 2 self.detect_interval = 5 self.tracks = [] @@ -59,20 +59,19 @@ def __init__(self): self.target_dist = 0.0 self.delta_angle = 0.0 self.target_angle = 0.0 - cfg = Config.get() - self.power_angles = [[15, (int(cfg.get("move_power_angle_1")), -1)], - [4, (int(cfg.get("move_power_angle_2")), 0.05)], - [1, (int(cfg.get("move_power_angle_3")), 0.02)], + self.power_angles = [[15, (int(settings.get("move_power_angle_1")), -1)], + [4, (int(settings.get("move_power_angle_2")), 0.05)], + [1, (int(settings.get("move_power_angle_3")), 0.02)], [0, (0, 0)]] - self.image_width = 640 / int(cfg.get("cv_image_factor")) - self.image_heigth = 480 / int(cfg.get("cv_image_factor")) + self.image_width = 640 / int(settings.get("cv_image_factor")) + self.image_heigth = 480 / int(settings.get("cv_image_factor")) self.transform = image.Image.get_transform(self.image_width) _motion = None @classmethod - def get_instance(cls): + def get_instance(cls, settings=None): if not cls._motion: - cls._motion = Motion() + cls._motion = Motion(settings) return cls._motion def move(self, dist): diff --git a/coderbot/music.py b/coderbot/music.py index 0544b468..be1ac222 100644 --- a/coderbot/music.py +++ b/coderbot/music.py @@ -29,6 +29,7 @@ import os import sox import time +import logging class Music: _instance = None @@ -42,17 +43,17 @@ class Music: @classmethod - def get_instance(cls,managerPackage): + def get_instance(cls, managerPackage, settings=None): if cls._instance is None: - cls._instance = Music(managerPackage) + cls._instance = Music(managerPackage, settings) return cls._instance - def __init__(self,managerPackage): + def __init__(self, managerPackage, settings): #os.putenv('AUDIODRIVER', 'alsa') #os.putenv('AUDIODEV', 'hw:1,0') self.managerPackage = managerPackage - print("We have create a class: MUSICAL") + logging.info("We have created a class: MUSICAL") def test(self): tfm = sox.Transformer() @@ -71,7 +72,7 @@ def play_pause(self, duration): # @para alteration: if it is a diesis or a bemolle # @param time: duration of the note in seconds def play_note(self, note, instrument='piano', alteration='none', duration=1.0): - print(note) + logging.info("play_note: %s", note) tfm = sox.Transformer() duration = float(duration) @@ -85,7 +86,7 @@ def play_note(self, note, instrument='piano', alteration='none', duration=1.0): if note in self.noteDict : shift = self.noteDict[note]+ alt else: - print('note not exist') + logging.error('note does not exist') return tfm.pitch(shift, quick=False) @@ -93,7 +94,7 @@ def play_note(self, note, instrument='piano', alteration='none', duration=1.0): if self.managerPackage.isPackageAvailable(instrument): tfm.preview('./sounds/notes/' + instrument + '/audio.wav') else: - print("no instrument:"+str(instrument)+" present in this coderbot!") + logging.error("no instrument:"+str(instrument)+" present in this coderbot!") def play_animal(self, instrument, note='G2', alteration='none', duration=1.0): tfm = sox.Transformer() @@ -138,13 +139,13 @@ def play_animal(self, instrument, note='G2', alteration='none', duration=1.0): if note in self.noteDict : shift = self.noteDict[note]+ alt else: - print('note not exist') + logging.error('note does not exist') return if self.managerPackage.isPackageAvailable(instrument): tfm.preview('./sounds/notes/' + instrument + '/audio.wav') else: - print("no animal verse:"+str(instrument)+" present in this coderbot!") + logging.error("no animal verse:"+str(instrument)+" present in this coderbot!") return tfm.pitch(shift, quick=False) tfm.trim(0.0, end_time=0.5*duration) diff --git a/coderbot/musicPackages.py b/coderbot/musicPackages.py index e671b7e7..a96c9ba3 100644 --- a/coderbot/musicPackages.py +++ b/coderbot/musicPackages.py @@ -68,7 +68,7 @@ def addInterface(self, musicPackageInterface): class MusicPackageInterface: - def __init__(self,interfaceName,available,icon): + def __init__(self, interfaceName, available,icon): self.interfaceName = interfaceName self.available = available self.icon = icon @@ -86,12 +86,12 @@ class MusicPackageManager: _instance = None @classmethod - def get_instance(cls): + def get_instance(cls, settings=None): if cls._instance is None: - cls._instance = MusicPackageManager() + cls._instance = MusicPackageManager(settings) return cls._instance - def __init__(self): + def __init__(self, settings): self.packages = dict() with open('./sounds/notes/music_package.json') as json_file: data = json.load(json_file) diff --git a/coderbot/program.py b/coderbot/program.py index a892950e..388a6da9 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -80,7 +80,7 @@ class ProgramEngine: _instance = None - def __init__(self): + def __init__(self, settings): self._program = None self._log = "" self._programs = TinyDB(PROGRAMS_DB) @@ -102,9 +102,9 @@ def __init__(self): self.save(program) @classmethod - def get_instance(cls): + def get_instance(cls, settings=None): if not cls._instance: - cls._instance = ProgramEngine() + cls._instance = ProgramEngine(settings) return cls._instance def prog_list(self, active_only = True): From f23d78f3e42efa8b6c5898bf53500b0dfb35580e Mon Sep 17 00:00:00 2001 From: previ Date: Wed, 16 Aug 2023 23:38:36 +0100 Subject: [PATCH 26/60] wip --- coderbot/api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index 2dc48efa..17e4e9cb 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -31,6 +31,9 @@ BUTTON_PIN = 16 settings = Config.read().get("settings") +network_settings = Config.read().get("network") +cloud_settings = Config.read().get("cloud") + bot = CoderBot.get_instance(settings=settings, motor_trim_factor=float(settings.get('move_motor_trim', 1.0)), motor_max_power=int(settings.get('motor_max_power', 100)), motor_min_power=int(settings.get('motor_min_power', 0)), @@ -426,10 +429,10 @@ def cloudRegistrationDelete(): return 200 def cloudRegistrationStatus(): - registration = settings.get('cloud_registration', {}) + registration = cloud_settings.get('registration', {}) return { "registered": CloudManager.get_instance().registration_status(), - "name": registration.get('name', {}), + "name": registration.get('name', ""), "description": registration.get('description', ""), "org_id": registration.get('org_id', ""), "org_name": registration.get('org_name', ""), From 1ba04562693a3e9a6ad3e05eaedf117f9e45ea37 Mon Sep 17 00:00:00 2001 From: previ Date: Sat, 19 Aug 2023 14:12:24 +0100 Subject: [PATCH 27/60] update default config.json --- defaults/config.json | 118 ++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/defaults/config.json b/defaults/config.json index ab8459d0..518c66fc 100644 --- a/defaults/config.json +++ b/defaults/config.json @@ -1,60 +1,62 @@ { - "move_power_angle_3":"60", - "cnn_default_model":"generic_fast_low", - "prog_maxblocks":"-1", - "camera_jpeg_quality":"5", - "show_page_control":"true", - "camera_framerate":"30", - "prog_scrollbars":"true", - "move_fw_speed":"100", - "motor_min_power":"0", - "motor_max_power":"100", - "prog_level":"adv", - "move_motor_trim":"1", - "cv_image_factor":"2", - "move_power_angle_1":"45", - "camera_path_object_size_min":"4000", - "button_func":"none", - "camera_color_object_size_min":"4000", - "camera_jpeg_bitrate":"1000000", - "move_fw_elapse":"1", - "show_control_move_commands":"true", - "camera_color_object_size_max":"160000", - "show_page_prefs":"true", - "camera_exposure_mode":"auto", - "ctrl_tr_elapse":"-1", - "show_page_program":"true", - "move_tr_elapse":"0.5", - "camera_path_object_size_max":"160000", - "sound_shutter":"$shutter", - "ctrl_fw_elapse":"-1", - "sound_stop":"$shutdown", - "ctrl_tr_speed":"80", - "ctrl_fw_speed":"100", - "move_tr_speed":"85", - "move_power_angle_2":"60", - "ctrl_hud_image":"", - "load_at_start":"", - "sound_start":"$startup", - "hardware_version":"5", - "audio_volume_level":"100", - "wifi_mode":"ap", - "wifi_ssid":"coderbot", - "wifi_psk":"coderbot", - "packages_installed":"", - "admin_password":"", - "pid_kp":"8.0", - "pid_kd":"0.0", - "pid_ki":"0.0", - "pid_max_speed":"200", - "pid_sample_time":"0.05", - "movement_use_mpu": "false", - "movement_use_motion": "false", - "movement_use_encoder": "true", - "sync_modes": { - "activities":"b", - "programs":"b", - "settings":"b" + "settings":{ + "ctrl_hud_image":"", + "cv_image_factor":"2", + "camera_color_object_size_max":"160000", + "camera_color_object_size_min":"4000", + "camera_exposure_mode":"auto", + "camera_framerate":"30", + "camera_jpeg_bitrate":"1000000", + "camera_jpeg_quality":"5", + "camera_path_object_size_max":"160000", + "camera_path_object_size_min":"4000", + "cnn_default_model":"generic_fast_low", + "move_power_angle_1":"45", + "move_power_angle_2":"60", + "move_power_angle_3":"60", + "button_func":"none", + "move_motor_trim":"1", + "motor_min_power":"0", + "motor_max_power":"100", + "sound_start":"$startup", + "sound_stop":"$shutdown", + "sound_shutter":"$shutter", + "load_at_start":"", + "prog_level":"adv", + "move_fw_elapse":"1", + "move_fw_speed":"100", + "move_tr_elapse":"0.5", + "move_tr_speed":"85", + "pid_kp":"8.0", + "pid_kd":"0.0", + "pid_ki":"0.0", + "pid_max_speed":"200", + "pid_sample_time":"0.05", + "ctrl_fw_elapse":"-1", + "ctrl_fw_speed":"100", + "ctrl_tr_elapse":"-1", + "ctrl_tr_speed":"80", + "audio_volume_level":"100", + "admin_password":"", + "hardware_version":"5", + "prog_scrollbars":"true", + "movement_use_mpu":"false", + "movement_use_motion":"false", + "movement_use_encoder":"true", + "locale":"browser" }, - "sync_period":"60" -} + "network":{ + "wifi_mode":"ap", + "wifi_ssid":"coderbot", + "wifi_psk":"coderbot" + }, + "cloud":{ + "sync_modes":{ + "activities":"b", + "programs":"b", + "settings":"b" + }, + "sync_period":"10", + "reg_otp":"AB1234CD" + } +} \ No newline at end of file From bd2ee94fd9ef5ed70889ebef133adf4556855c79 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 16:26:03 +0200 Subject: [PATCH 28/60] increase initial delay for testing --- .github/workflows/build_backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index 189d0835..ca2364e8 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -26,7 +26,7 @@ jobs: - run: | export PYTHONPATH=./stub:./coderbot:./test python3 coderbot/main.py > coderbot.log & - sleep 60 + sleep 120 apt-get install -y python3-venv mkdir -p schemathesis python3 -m venv schemathesis From f1817f8dfa8a7370e7a7e6c034d52a50f03ef0d5 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 16:41:53 +0200 Subject: [PATCH 29/60] debug test api --- .github/workflows/build_backend.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index ca2364e8..95b15017 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -26,18 +26,20 @@ jobs: - run: | export PYTHONPATH=./stub:./coderbot:./test python3 coderbot/main.py > coderbot.log & - sleep 120 + sleep 60 + cat coderbot.log + curl http://localhost:5000/api/v1/openapi.json apt-get install -y python3-venv mkdir -p schemathesis python3 -m venv schemathesis . schemathesis/bin/activate pip install schemathesis - st run --endpoint 'activities' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + #st run --endpoint 'activities' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json #st run --endpoint 'media' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - st run --endpoint 'control/speak' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - st run --endpoint 'control/stop' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - st run --endpoint 'music' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - st run --endpoint 'programs' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + #st run --endpoint 'control/speak' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + #st run --endpoint 'control/stop' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + #st run --endpoint 'music' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + #st run --endpoint 'programs' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json echo "openapi test complete" release-backend: From 16ffbadc87f05a79d88ad2a32e2a9010e7cfc101 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 16:45:47 +0200 Subject: [PATCH 30/60] debug tests --- .github/workflows/build_backend.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index 95b15017..b69d2109 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -25,6 +25,7 @@ jobs: echo "test complete" - run: | export PYTHONPATH=./stub:./coderbot:./test + export CODERBOT_CLOUD_API_ENDPOINT=http://localhost:5000 python3 coderbot/main.py > coderbot.log & sleep 60 cat coderbot.log @@ -34,7 +35,7 @@ jobs: python3 -m venv schemathesis . schemathesis/bin/activate pip install schemathesis - #st run --endpoint 'activities' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + st run --endpoint 'activities' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json #st run --endpoint 'media' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json #st run --endpoint 'control/speak' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json #st run --endpoint 'control/stop' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json From b49c58d3102170b9f0bf317ece22f15717a692f4 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 16:50:04 +0200 Subject: [PATCH 31/60] debug tests --- .github/workflows/build_backend.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index b69d2109..41ea5341 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -37,10 +37,10 @@ jobs: pip install schemathesis st run --endpoint 'activities' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json #st run --endpoint 'media' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - #st run --endpoint 'control/speak' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - #st run --endpoint 'control/stop' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - #st run --endpoint 'music' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json - #st run --endpoint 'programs' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + st run --endpoint 'control/speak' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + st run --endpoint 'control/stop' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + st run --endpoint 'music' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json + st run --endpoint 'programs' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json echo "openapi test complete" release-backend: From 24ef531e7dd322b502f7cc0130d214c23dfc590c Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 16:51:42 +0200 Subject: [PATCH 32/60] Update camera_test.py - fix tests --- test/camera_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/camera_test.py b/test/camera_test.py index 523f2c4b..90fa07d4 100755 --- a/test/camera_test.py +++ b/test/camera_test.py @@ -10,7 +10,7 @@ class CameraTest(unittest.TestCase): def setUp(self): config.Config.read() picamera.PiCamera = picamera_mock.PiCameraMock - self.cam = camera.Camera.get_instance() + self.cam = camera.Camera.get_instance(config.get('settings')) def tearDown(self): self.cam.exit() From 52f5dd1e73709f3bc1230a228cb90065c452ec53 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 16:56:05 +0200 Subject: [PATCH 33/60] Update coderbot_test.py - fix --- test/coderbot_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/coderbot_test.py b/test/coderbot_test.py index dbbd6ee0..e655e34c 100755 --- a/test/coderbot_test.py +++ b/test/coderbot_test.py @@ -1,6 +1,7 @@ import unittest import test.pigpio_mock import coderbot +import config import logging logger = logging.getLogger() @@ -17,7 +18,8 @@ class CoderBotDCMotorTestCase(unittest.TestCase): def setUp(self): coderbot.pigpio.pi = test.pigpio_mock.PIGPIOMock coderbot.CoderBot._instance = None - self.bot = coderbot.CoderBot.get_instance() + settings = config.Config.read().get('settings') + self.bot = coderbot.CoderBot.get_instance(settings) def test_motor_forward(self): self.bot.forward(speed=100, elapse=0.1) From f65bc87c7302c51d3dbf6cdd26a6faed07923307 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 16:56:27 +0200 Subject: [PATCH 34/60] Update camera_test.py fix --- test/camera_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/camera_test.py b/test/camera_test.py index 90fa07d4..b074aff4 100755 --- a/test/camera_test.py +++ b/test/camera_test.py @@ -8,9 +8,9 @@ class CameraTest(unittest.TestCase): def setUp(self): - config.Config.read() + settings = config.Config.read().get('settings') picamera.PiCamera = picamera_mock.PiCameraMock - self.cam = camera.Camera.get_instance(config.get('settings')) + self.cam = camera.Camera.get_instance(settings) def tearDown(self): self.cam.exit() From 7c13bb3e5a6af97c830bbef0d6209233f0aa850f Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 17:04:05 +0200 Subject: [PATCH 35/60] Update coderbot_test.py fix --- test/coderbot_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/coderbot_test.py b/test/coderbot_test.py index e655e34c..04285b93 100755 --- a/test/coderbot_test.py +++ b/test/coderbot_test.py @@ -45,7 +45,8 @@ class CoderBotServoMotorTestCase(unittest.TestCase): def setUp(self): coderbot.pigpio.pi = test.pigpio_mock.PIGPIOMock coderbot.CoderBot._instance = None - self.bot = coderbot.CoderBot.get_instance(servo=True) + settings = config.Config.read().get('settings') + self.bot = coderbot.CoderBot.get_instance(settings, Servo=True) def test_motor_forward(self): self.bot.forward(speed=100, elapse=0.1) @@ -72,7 +73,8 @@ class CoderBotSonarTestCase(unittest.TestCase): def setUp(self): coderbot.pigpio.pi = test.pigpio_mock.PIGPIOMock coderbot.CoderBot._instance = None - self.bot = coderbot.CoderBot.get_instance() + settings = config.Config.read().get('settings') + self.bot = coderbot.CoderBot.get_instance(settings) def test_sonar(self): for i in range(0, 3): From 7070b18622e21bf3395c887ed9187b6f3b318cd9 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 17:08:00 +0200 Subject: [PATCH 36/60] Update coderbot_test.py -fix --- test/coderbot_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/coderbot_test.py b/test/coderbot_test.py index 04285b93..69562831 100755 --- a/test/coderbot_test.py +++ b/test/coderbot_test.py @@ -46,7 +46,7 @@ def setUp(self): coderbot.pigpio.pi = test.pigpio_mock.PIGPIOMock coderbot.CoderBot._instance = None settings = config.Config.read().get('settings') - self.bot = coderbot.CoderBot.get_instance(settings, Servo=True) + self.bot = coderbot.CoderBot.get_instance(settings, servo=True) def test_motor_forward(self): self.bot.forward(speed=100, elapse=0.1) From 14f6478c2e350cef0c494df406ca9bd4748ca4b6 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 17:23:04 +0200 Subject: [PATCH 37/60] Update coderbot_test.py fix servo test --- test/coderbot_test.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/test/coderbot_test.py b/test/coderbot_test.py index 69562831..d8a651f2 100755 --- a/test/coderbot_test.py +++ b/test/coderbot_test.py @@ -46,27 +46,25 @@ def setUp(self): coderbot.pigpio.pi = test.pigpio_mock.PIGPIOMock coderbot.CoderBot._instance = None settings = config.Config.read().get('settings') - self.bot = coderbot.CoderBot.get_instance(settings, servo=True) + self.bot = coderbot.CoderBot.get_instance(settings) - def test_motor_forward(self): - self.bot.forward(speed=100, elapse=0.1) + def servo_0_0(self): + self.bot.forward(servo=0, angle=0) - def test_motor_backward(self): - self.bot.backward(speed=100, elapse=0.1) + def servo_0_90(self): + self.bot.forward(servo=0, angle=90) - def test_motor_left(self): - self.bot.left(speed=100, elapse=0.1) + def servo_0_180(self): + self.bot.forward(servo=0, angle=180) - def test_motor_right(self): - self.bot.left(speed=100, elapse=0.1) + def servo_1_0(self): + self.bot.forward(servo=1, angle=0) - def test_motor_move(self): - self.bot.move(speed=100, elapse=0.1) - self.bot.move(speed=-100, elapse=0.1) + def servo_1_90(self): + self.bot.forward(servo=1, angle=90) - def test_motor_turn(self): - self.bot.turn(speed=100, elapse=0.1) - self.bot.turn(speed=-100, elapse=0.1) + def servo_1_180(self): + self.bot.forward(servo=1, angle=180) class CoderBotSonarTestCase(unittest.TestCase): From 164da91ce1f485a7038b4d9a4ac62355b427e747 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 19 Aug 2023 17:28:54 +0200 Subject: [PATCH 38/60] Update Dockerfile stub add CODERBOT_CLOUD_API_ENDPOINT --- docker/stub/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/stub/Dockerfile b/docker/stub/Dockerfile index 43180a2a..2cd2fd2d 100644 --- a/docker/stub/Dockerfile +++ b/docker/stub/Dockerfile @@ -62,5 +62,6 @@ ADD docker/stub/start.sh /coderbot/. ARG CODERBOT_VERSION ENV CODERBOT_VERSION=${CODERBOT_VERSION} +ENV CODERBOT_CLOUD_API_ENDPOINT=http://localhost:5000 ENTRYPOINT /coderbot/start.sh From 45e0bcebc41751e1a0f185fd85518d1f461adfd4 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 27 Aug 2023 00:48:36 +0100 Subject: [PATCH 39/60] fixes --- coderbot/activity.py | 2 +- coderbot/api.py | 2 +- coderbot/program.py | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index b44bd777..0477447b 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -69,4 +69,4 @@ def delete(self, name, logical = True): def list(self, active_only = True): with self.lock: - return self.activities.all() + return self.activities.search(self.query.status == ACTIVITY_STATUS_ACTIVE) diff --git a/coderbot/api.py b/coderbot/api.py index 17e4e9cb..0f377c64 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -301,7 +301,7 @@ def saveProgram(name, body): logging.info("saving - name: %s, body: %s", name, str(existing_program)) if existing_program is not None and not overwrite: return "askOverwrite" - elif existing_program is not None and existing_program.is_default() == True: + elif existing_program is not None and existing_program.is_stock() == True: return "defaultCannotOverwrite", 400 program = Program(name=body.get("name"), code=body.get("code"), dom_code=body.get("dom_code"), modified=datetime.now(), status="active") prog_engine.save(program) diff --git a/coderbot/program.py b/coderbot/program.py index 388a6da9..76dd7900 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -127,10 +127,14 @@ def save(self, program): else: self._programs.insert(program_db_entry) - def load(self, name): + def load(self, name, active_only=True): with self.lock: query = Query() - program_db_entries = self._programs.search(query.name == name) + program_db_entries = None + if active_only: + program_db_entries = self._programs.search((query.name == name) & (query.status == PROGRAM_STATUS_ACTIVE)) + else: + program_db_entries = self._programs.search(query.name == name) if len(program_db_entries) > 0: prog_db_entry = program_db_entries[0] #logging.debug(prog_db_entry) From 8198e097068aed7e2c0a2a5b451c43f93dadd6b6 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 27 Aug 2023 11:51:43 +0100 Subject: [PATCH 40/60] fix --- coderbot/activity.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 0477447b..5e92a830 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -1,3 +1,4 @@ +import logging from tinydb import TinyDB, Query from threading import Lock from datetime import datetime @@ -29,11 +30,16 @@ def __init__(self): self.activities = TinyDB("data/activities.json") self.query = Query() self.lock = Lock() + self.permanentlyRemoveDeletedActivities() - def load(self, name, default): + def load(self, name, default, active_only=True): with self.lock: if name and default is None: - activities = self.activities.search(self.query.name == name) + activities = [] + if active_only: + self.activities.search((self.query.name == name) & (self.query.status == ACTIVITY_STATUS_ACTIVE)) + else: + self.activities.search(self.query.name == name) if len(activities) > 0: return activities[0] elif default is not None: @@ -66,7 +72,16 @@ def delete(self, name, logical = True): else: self.activities.remove(self.query.name == activity["name"]) + def permanentlyRemoveDeletedActivities(self): + for a in self.list(active_only=False): + logging.info("checking: " + a["name"]) + if a["status"] == ACTIVITY_STATUS_DELETED: + logging.info("deleting: " + a["name"]) + self.delete(a["name"], logical=False) def list(self, active_only = True): with self.lock: - return self.activities.search(self.query.status == ACTIVITY_STATUS_ACTIVE) + if active_only: + return self.activities.search(self.query.status == ACTIVITY_STATUS_ACTIVE) + else: + return self.activities.all() From bfaa25874225a290cbf67bc851ed08477fd61e5d Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 27 Aug 2023 17:33:58 +0100 Subject: [PATCH 41/60] fix --- coderbot/activity.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 5e92a830..68b30d8b 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -37,9 +37,9 @@ def load(self, name, default, active_only=True): if name and default is None: activities = [] if active_only: - self.activities.search((self.query.name == name) & (self.query.status == ACTIVITY_STATUS_ACTIVE)) + activities = self.activities.search((self.query.name == name) & (self.query.status == ACTIVITY_STATUS_ACTIVE)) else: - self.activities.search(self.query.name == name) + activities = self.activities.search(self.query.name == name) if len(activities) > 0: return activities[0] elif default is not None: @@ -51,26 +51,26 @@ def load(self, name, default, active_only=True): def save(self, name, activity): with self.lock: # if saved activity is "default", reset existing default activity to "non-default" - if activity.get("default", False) is True: + if self.query.get("default", False) is True: self.activities.update({'default': False}) if self.activities.search(self.query.name == name) == []: self.activities.insert(activity) else: - self.activities.update(activity, self.query.name == activity["name"]) + self.activities.update(activity, self.query.name == self.query._name) def delete(self, name, logical = True): with self.lock: activities = self.activities.search(self.query.name == name) if len(activities) > 0: activity = activities[0] - if activity.get("default", False) is True: + if self.query.get("default", False) is True: self.activities.update({'default': True}, self.query.stock == True) if logical: - activity["status"] = ACTIVITY_STATUS_DELETED + self.query._status = ACTIVITY_STATUS_DELETED activity["modified"] = datetime.now().isoformat() - self.activities.update(activity, self.query.name == name) + self.activities.update(activity, self.self.query.name == name) else: - self.activities.remove(self.query.name == activity["name"]) + self.activities.remove(self.query.name == self.query._name) def permanentlyRemoveDeletedActivities(self): for a in self.list(active_only=False): From 6969fea8c4c3578d40022c3842044843a2231c59 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 27 Aug 2023 17:44:37 +0100 Subject: [PATCH 42/60] fix --- coderbot/activity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 68b30d8b..1c48b3e7 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -56,7 +56,7 @@ def save(self, name, activity): if self.activities.search(self.query.name == name) == []: self.activities.insert(activity) else: - self.activities.update(activity, self.query.name == self.query._name) + self.activities.update(activity, self.query.name == name) def delete(self, name, logical = True): with self.lock: @@ -70,7 +70,7 @@ def delete(self, name, logical = True): activity["modified"] = datetime.now().isoformat() self.activities.update(activity, self.self.query.name == name) else: - self.activities.remove(self.query.name == self.query._name) + self.activities.remove(self.query.name == name) def permanentlyRemoveDeletedActivities(self): for a in self.list(active_only=False): From f63918411dbbab2655aeeb7e67ea3689f10e804d Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 27 Aug 2023 17:55:33 +0100 Subject: [PATCH 43/60] fix --- coderbot/activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 1c48b3e7..3935566c 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -51,7 +51,7 @@ def load(self, name, default, active_only=True): def save(self, name, activity): with self.lock: # if saved activity is "default", reset existing default activity to "non-default" - if self.query.get("default", False) is True: + if activity.get("default", False) is True: self.activities.update({'default': False}) if self.activities.search(self.query.name == name) == []: self.activities.insert(activity) From 24dde58a51f5b99c5d9c080eef814ab2617ada72 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 27 Aug 2023 19:07:46 +0100 Subject: [PATCH 44/60] fix --- coderbot/activity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 3935566c..b0906e12 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -63,12 +63,12 @@ def delete(self, name, logical = True): activities = self.activities.search(self.query.name == name) if len(activities) > 0: activity = activities[0] - if self.query.get("default", False) is True: + if activity.get("default", False) is True: self.activities.update({'default': True}, self.query.stock == True) if logical: - self.query._status = ACTIVITY_STATUS_DELETED + activity["status"] = ACTIVITY_STATUS_DELETED activity["modified"] = datetime.now().isoformat() - self.activities.update(activity, self.self.query.name == name) + self.activities.update(activity, self.query.name == name) else: self.activities.remove(self.query.name == name) From f50165b60feea193ded918a31870b1c872564017 Mon Sep 17 00:00:00 2001 From: previ Date: Sat, 23 Dec 2023 17:33:25 +0000 Subject: [PATCH 45/60] wip --- coderbot/activity.py | 2 +- coderbot/api.py | 48 ++++++++++++++++++++++---------------------- coderbot/main.py | 2 +- coderbot/v1.yml | 4 ++++ 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index b0906e12..18f24c3c 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -64,7 +64,7 @@ def delete(self, name, logical = True): if len(activities) > 0: activity = activities[0] if activity.get("default", False) is True: - self.activities.update({'default': True}, self.query.stock == True) + self.activities.update({'default': True}, self.query.kind == ACTIVITY_KIND_STOCK) if logical: activity["status"] = ACTIVITY_STATUS_DELETED activity["modified"] = datetime.now().isoformat() diff --git a/coderbot/api.py b/coderbot/api.py index 0f377c64..91b73624 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -123,31 +123,31 @@ def get_info(): def stop(): bot.stop() - return 200 + return {} def move(body): speed=body.get("speed") elapse=body.get("elapse") distance=body.get("distance") if (speed is None or speed == 0) or (elapse is not None and distance is not None): - return 400 + return None, 400 bot.move(speed=speed, elapse=elapse, distance=distance) - return 200 + return {} def turn(body): speed=body.get("speed") elapse=body.get("elapse") distance=body.get("distance") if (speed is None or speed == 0) or (elapse is not None and distance is not None): - return 400 + return None, 400 bot.turn(speed=speed, elapse=elapse, distance=distance) - return 200 + return {} def takePhoto(): try: cam.photo_take() audio_device.say(settings.get("sound_shutter")) - return 200 + return {} except Exception as e: logging.warning("Error: %s", e) @@ -155,7 +155,7 @@ def recVideo(): try: cam.video_rec() audio_device.say(settings.get("sound_shutter")) - return 200 + return {} except Exception as e: logging.warning("Error: %s", e) @@ -163,7 +163,7 @@ def stopVideo(): try: cam.video_stop() audio_device.say(settings.get("sound_shutter")) - return 200 + return {} except Exception as e: logging.warning("Error: %s", e) @@ -172,16 +172,16 @@ def speak(body): locale = body.get("locale", "") logging.debug("say: " + text + " in: " + locale) audio_device.say(text, locale) - return 200 + return {} def reset(): Balena.get_instance().purge() - return 200 + return {} def halt(): audio_device.say(what=settings.get("sound_stop")) Balena.get_instance().shutdown() - return 200 + return {} def restart(): Balena.get_instance().restart() @@ -189,7 +189,7 @@ def restart(): def reboot(): audio_device.say(what=settings.get("sound_stop")) Balena.get_instance().reboot() - return 200 + return {} def video_stream(a_cam): while True: @@ -223,22 +223,22 @@ def getPhoto(name): return send_file(media_file, mimetype=mimetype.get(name[:-3], 'image/jpeg'), max_age=0) except picamera.exc.PiCameraError as e: logging.error("Error: %s", str(e)) - return 503 + return {"exception": str(e)}, 503 except FileNotFoundError: - return 404 + return None, 404 def savePhoto(name, body): try: cam.update_photo({"name": name, "tag": body.get("tag")}) except FileNotFoundError: - return 404 + return None, 404 def deletePhoto(name): logging.debug("photo delete") try: cam.delete_photo(name) except FileNotFoundError: - return 404 + return None, 404 def restoreSettings(): Config.restore() @@ -249,14 +249,14 @@ def loadSettings(): def saveSettings(body): Config.write(body) - return 200 + return Config.write(body) def updateFromPackage(): os.system('sudo bash /home/pi/clean-update.sh') file_to_upload = connexion.request.files['file_to_upload'] file_to_upload.save(os.path.join('/home/pi/', 'update.tar')) os.system('sudo reboot') - return 200 + return {} def listMusicPackages(): """ @@ -291,7 +291,7 @@ def deleteMusicPackage(name): """ musicPkg = MusicPackageManager.get_instance() musicPkg.deletePackage(name) - return 200 + return {} ## Programs @@ -305,14 +305,14 @@ def saveProgram(name, body): return "defaultCannotOverwrite", 400 program = Program(name=body.get("name"), code=body.get("code"), dom_code=body.get("dom_code"), modified=datetime.now(), status="active") prog_engine.save(program) - return 200 + return {} def loadProgram(name): existing_program = prog_engine.load(name) if existing_program: return existing_program.as_dict(), 200 else: - return 404 + return None, 404 def deleteProgram(name): prog_engine.delete(name, logical=True) @@ -415,18 +415,18 @@ def deleteCNNModel(name): def cloudSyncRequest(): CloudManager.get_instance().sync() - return 200 + return {} def cloudSyncStatus(): return CloudManager.get_instance().sync_status() def cloudRegistrationRequest(body): CloudManager.get_instance().register(body) - return 200 + return {} def cloudRegistrationDelete(): CloudManager.get_instance().unregister() - return 200 + return {} def cloudRegistrationStatus(): registration = cloud_settings.get('registration', {}) diff --git a/coderbot/main.py b/coderbot/main.py index f6b6f9b5..43b85144 100644 --- a/coderbot/main.py +++ b/coderbot/main.py @@ -32,7 +32,7 @@ # 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} +options = {"swagger_ui": True} connexionApp = connexion.App(__name__, options=options) # Connexion wraps FlaskApp, so app becomes connexionApp.app diff --git a/coderbot/v1.yml b/coderbot/v1.yml index 8539dbb5..8bb36c5e 100644 --- a/coderbot/v1.yml +++ b/coderbot/v1.yml @@ -30,6 +30,10 @@ paths: responses: 200: description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Settings' tags: - CoderBot configuration /settings/restore: From 4967d4d734bb93c55e6fabff0106957f3b795ab3 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 24 Dec 2023 10:14:16 +0000 Subject: [PATCH 46/60] bumo deps --- coderbot/api.py | 18 ------------- coderbot/main.py | 69 ++++++++++++++++++++++++++---------------------- requirements.txt | 27 +++++++++---------- 3 files changed, 50 insertions(+), 64 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index 91b73624..f41723fe 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -30,24 +30,6 @@ BUTTON_PIN = 16 -settings = Config.read().get("settings") -network_settings = Config.read().get("network") -cloud_settings = Config.read().get("cloud") - -bot = CoderBot.get_instance(settings=settings, motor_trim_factor=float(settings.get('move_motor_trim', 1.0)), - motor_max_power=int(settings.get('motor_max_power', 100)), - motor_min_power=int(settings.get('motor_min_power', 0)), - hw_version=settings.get('hardware_version'), - pid_params=(float(settings.get('pid_kp', 1.0)), - float(settings.get('pid_kd', 0.1)), - float(settings.get('pid_ki', 0.01)), - float(settings.get('pid_max_speed', 200)), - float(settings.get('pid_sample_time', 0.01)))) -audio_device = Audio.get_instance(settings) -cam = Camera.get_instance(settings) -Motion.get_instance(settings) -CNNManager.get_instance(settings) - def get_serial(): """ Extract serial from cpuinfo file diff --git a/coderbot/main.py b/coderbot/main.py index 43b85144..681f18c4 100644 --- a/coderbot/main.py +++ b/coderbot/main.py @@ -7,8 +7,9 @@ import logging.handlers 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 @@ -23,29 +24,26 @@ # 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": True} -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.settings.get('button_func') == "startstop": @@ -67,41 +65,50 @@ def run_server(): cam = None try: try: - app.settings = Config.read().get("settings") - - bot = CoderBot.get_instance() - + settings = Config.read().get("settings") + app.settings = settings + network_settings = Config.read().get("network") + cloud_settings = Config.read().get("cloud") + + bot = CoderBot.get_instance(settings=settings, motor_trim_factor=float(settings.get('move_motor_trim', 1.0)), + motor_max_power=int(settings.get('motor_max_power', 100)), + motor_min_power=int(settings.get('motor_min_power', 0)), + hw_version=settings.get('hardware_version'), + pid_params=(float(settings.get('pid_kp', 1.0)), + float(settings.get('pid_kd', 0.1)), + float(settings.get('pid_ki', 0.01)), + float(settings.get('pid_max_speed', 200)), + float(settings.get('pid_sample_time', 0.01)))) try: - audio_device = Audio.get_instance() - audio_device.set_volume(int(app.settings.get('audio_volume_level')), 100) - audio_device.say(app.settings.get("sound_start")) + audio_device = Audio.get_instance(settings) + audio_device.set_volume(int(settings.get('audio_volume_level')), 100) + audio_device.say(settings.get("sound_start")) except Exception: logging.warning("Audio not present") - try: - cam = Camera.get_instance() - Motion.get_instance() + cam = Camera.get_instance(settings) + Motion.get_instance(settings) except picamera.exc.PiCameraError: logging.warning("Camera not present") - CNNManager.get_instance(app.settings) + CNNManager.get_instance(settings) EventManager.get_instance("coderbot") - if app.settings.get('load_at_start') and app.settings.get('load_at_start'): - prog = app.prog_engine.load(app.settings.get('load_at_start')) + if settings.get('load_at_start') and settings.get('load_at_start'): + prog = app.prog_engine.load(settings.get('load_at_start')) prog.execute() CloudManager.get_instance() except ValueError as e: - app.settings = {} + settings = {} logging.error(e) bot.set_callback(bot.GPIOS.PIN_PUSHBUTTON, button_pushed, 100) 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() diff --git a/requirements.txt b/requirements.txt index 6734cd58..6f8eae55 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,32 +1,29 @@ # API framework -connexion==2.14.2 -Flask==2.2.3 -Flask-Cors==3.0.10 -tinydb==4.7.1 -Werkzeug==2.2.3 +connexion[uvicorn,swagger-ui]==3.0.5 +tinydb==4.8.0 cloud_api_robot_client @ git+https://github.com/CoderBotOrg/cloud_api_robot_client.git # Misc utils -setuptools==67.4.0 +setuptools==69.0.3 event-channel==0.4.3 # IO extensions pigpio==1.78 -smbus2==0.4.2 -spidev==3.5 +smbus2==0.4.3 +spidev==3.6 # Audio sox==1.4.1 -PyAudio==0.2.12 -pyalsaaudio==0.9.2 +PyAudio==0.2.14 +pyalsaaudio==0.10.0 # Computer Vision -grpcio==1.48.1 -numpy==1.24.2 -Pillow==9.4.0 -protobuf==4.22.0 +grpcio==1.60.0 +numpy==1.24.3 +Pillow==10.1.0 +protobuf==4.25.1 opencv-contrib-python==4.5.5.62 -tflite-runtime==2.11.0 +tflite-runtime==2.12.0 pytesseract==0.3.10 picamera==1.13 pyzbar==0.1.9 From ffcb801f86890784b473daf70f96a8c25d78e39d Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 24 Dec 2023 10:21:11 +0000 Subject: [PATCH 47/60] bump deps --- docker/stub/requirements.txt | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/docker/stub/requirements.txt b/docker/stub/requirements.txt index a758a746..7913b788 100644 --- a/docker/stub/requirements.txt +++ b/docker/stub/requirements.txt @@ -1,27 +1,24 @@ # API framework -connexion==2.14.2 -Flask==2.2.3 -Flask-Cors==3.0.10 -tinydb==4.7.1 -Werkzeug==2.2.3 +connexion[uvicorn,swagger-ui]==3.0.5 +tinydb==4.8.0 cloud_api_robot_client @ git+https://github.com/CoderBotOrg/cloud_api_robot_client.git # Misc utils -setuptools==67.4.0 +setuptools==69.0.3 event-channel==0.4.3 # IO extensions -spidev==3.5 +spidev==3.6 # Audio sox==1.4.1 # Computer Vision -grpcio==1.48.1 -numpy==1.24.2 -Pillow==9.4.0 -protobuf==4.22.0 +grpcio==1.60.0 +numpy==1.24.3 +Pillow==10.1.0 +protobuf==4.25.1 opencv-contrib-python==4.5.5.62 -tflite-runtime==2.11.0 +tflite-runtime==2.12.0 pytesseract==0.3.10 pyzbar==0.1.9 From b6a4401e42f5d4e60671226322ec530bac185b02 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 24 Dec 2023 10:29:48 +0000 Subject: [PATCH 48/60] bump deps --- docker/stub/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/stub/requirements.txt b/docker/stub/requirements.txt index 7913b788..662a4dc3 100644 --- a/docker/stub/requirements.txt +++ b/docker/stub/requirements.txt @@ -1,5 +1,5 @@ # API framework -connexion[uvicorn,swagger-ui]==3.0.5 +connexion[uvicorn,flask,swagger-ui]==3.0.5 tinydb==4.8.0 cloud_api_robot_client @ git+https://github.com/CoderBotOrg/cloud_api_robot_client.git diff --git a/requirements.txt b/requirements.txt index 6f8eae55..dc2210a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # API framework -connexion[uvicorn,swagger-ui]==3.0.5 +connexion[uvicorn,flask,swagger-ui]==3.0.5 tinydb==4.8.0 cloud_api_robot_client @ git+https://github.com/CoderBotOrg/cloud_api_robot_client.git From d8ef62223eda29e67e8539922273b2bfbdb1a126 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 24 Dec 2023 14:39:21 +0000 Subject: [PATCH 49/60] fix audio_devide --- coderbot/api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index f41723fe..f82d7e03 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -128,7 +128,7 @@ def turn(body): def takePhoto(): try: cam.photo_take() - audio_device.say(settings.get("sound_shutter")) + Audio.get_instance().say(settings.get("sound_shutter")) return {} except Exception as e: logging.warning("Error: %s", e) @@ -136,7 +136,7 @@ def takePhoto(): def recVideo(): try: cam.video_rec() - audio_device.say(settings.get("sound_shutter")) + Audio.get_instance().say(settings.get("sound_shutter")) return {} except Exception as e: logging.warning("Error: %s", e) @@ -144,7 +144,7 @@ def recVideo(): def stopVideo(): try: cam.video_stop() - audio_device.say(settings.get("sound_shutter")) + Audio.get_instance().say(settings.get("sound_shutter")) return {} except Exception as e: logging.warning("Error: %s", e) @@ -153,7 +153,7 @@ def speak(body): text = body.get("text", "") locale = body.get("locale", "") logging.debug("say: " + text + " in: " + locale) - audio_device.say(text, locale) + Audio.get_instance().say(text, locale) return {} def reset(): @@ -161,7 +161,7 @@ def reset(): return {} def halt(): - audio_device.say(what=settings.get("sound_stop")) + Audio.get_instance().say(what=settings.get("sound_stop")) Balena.get_instance().shutdown() return {} @@ -169,7 +169,7 @@ def restart(): Balena.get_instance().restart() def reboot(): - audio_device.say(what=settings.get("sound_stop")) + Audio.get_instance().say(what=settings.get("sound_stop")) Balena.get_instance().reboot() return {} From e929c0e960644b138d45cb07156d0a929aa5054e Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 24 Dec 2023 14:44:36 +0000 Subject: [PATCH 50/60] fix bot, cam --- coderbot/api.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/coderbot/api.py b/coderbot/api.py index f82d7e03..6344bdd7 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -67,7 +67,7 @@ def get_status(): internet_status = False try: - urllib.request.urlopen("https://coderbot.org") + urllib.request.urlopen("https://coderCoderBot.get_instance().org") internet_status = True except Exception: pass @@ -104,7 +104,7 @@ def get_info(): ## Robot control def stop(): - bot.stop() + CoderBot.get_instance().stop() return {} def move(body): @@ -113,7 +113,7 @@ def move(body): distance=body.get("distance") if (speed is None or speed == 0) or (elapse is not None and distance is not None): return None, 400 - bot.move(speed=speed, elapse=elapse, distance=distance) + CoderBot.get_instance().move(speed=speed, elapse=elapse, distance=distance) return {} def turn(body): @@ -122,12 +122,12 @@ def turn(body): distance=body.get("distance") if (speed is None or speed == 0) or (elapse is not None and distance is not None): return None, 400 - bot.turn(speed=speed, elapse=elapse, distance=distance) + CoderBot.get_instance().turn(speed=speed, elapse=elapse, distance=distance) return {} def takePhoto(): try: - cam.photo_take() + Camera.get_instance().photo_take() Audio.get_instance().say(settings.get("sound_shutter")) return {} except Exception as e: @@ -135,7 +135,7 @@ def takePhoto(): def recVideo(): try: - cam.video_rec() + Camera.get_instance().video_rec() Audio.get_instance().say(settings.get("sound_shutter")) return {} except Exception as e: @@ -143,7 +143,7 @@ def recVideo(): def stopVideo(): try: - cam.video_stop() + Camera.get_instance().video_stop() Audio.get_instance().say(settings.get("sound_shutter")) return {} except Exception as e: @@ -196,12 +196,12 @@ def listPhotos(): """ Expose the list of taken photos """ - return cam.get_photo_list() + return Camera.get_instance().get_photo_list() def getPhoto(name): mimetype = {'jpg': 'image/jpeg', 'mp4': 'video/mp4'} try: - media_file = cam.get_photo_file(name) + media_file = Camera.get_instance().get_photo_file(name) return send_file(media_file, mimetype=mimetype.get(name[:-3], 'image/jpeg'), max_age=0) except picamera.exc.PiCameraError as e: logging.error("Error: %s", str(e)) @@ -211,14 +211,14 @@ def getPhoto(name): def savePhoto(name, body): try: - cam.update_photo({"name": name, "tag": body.get("tag")}) + Camera.get_instance().update_photo({"name": name, "tag": body.get("tag")}) except FileNotFoundError: return None, 404 def deletePhoto(name): logging.debug("photo delete") try: - cam.delete_photo(name) + Camera.get_instance().delete_photo(name) except FileNotFoundError: return None, 404 @@ -377,7 +377,7 @@ def trainCNNModel(body): cnn.train_new_model(model_name=body.get("model_name"), architecture=body.get("architecture"), image_tags=body.get("image_tags"), - photos_meta=cam.get_photo_list(), + photos_meta=Camera.get_instance().get_photo_list(), training_steps=body.get("training_steps"), learning_rate=body.get("learning_rate")) From c89f25c835d203665bb7ea6924339bc3ed13cd6b Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 24 Dec 2023 19:36:21 +0000 Subject: [PATCH 51/60] fix video stream --- coderbot/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderbot/api.py b/coderbot/api.py index 6344bdd7..3487fb69 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -188,7 +188,7 @@ def streamVideo(): h.add('Age', 0) h.add('Cache-Control', 'no-cache, private') h.add('Pragma', 'no-cache') - return Response(video_stream(cam), headers=h, mimetype="multipart/x-mixed-replace; boundary=--BOUNDARYSTRING") + return Response(video_stream(Camera.get_instance()), headers=h, mimetype="multipart/x-mixed-replace; boundary=--BOUNDARYSTRING") except Exception: pass From 9ad105105a7a3d1ea4d377bbfd56d1b99794e91e Mon Sep 17 00:00:00 2001 From: previ Date: Thu, 28 Dec 2023 22:30:20 +0000 Subject: [PATCH 52/60] add uuid --- coderbot/activity.py | 41 +++++---- coderbot/api.py | 58 +++++++----- coderbot/cloud/__init__.py | 18 ++-- coderbot/main.py | 1 - coderbot/program.py | 43 +++++---- coderbot/v1.yml | 89 ++++++++++++++----- defaults/config.json | 6 +- defaults/programs/program_demo_ar_tags.json | 2 +- .../programs/program_demo_cat_follower.json | 2 +- .../programs/program_demo_color_seeker.json | 2 +- defaults/programs/program_demo_io_ext.json | 2 +- .../programs/program_demo_line_follower.json | 2 +- .../program_demo_obstacle_avoidance.json | 2 +- .../programs/program_demo_roboetologist.json | 2 +- .../program_demo_sound_clap_control.json | 2 +- .../programs/program_test_cnn_classifier.json | 2 +- .../program_test_cnn_object_detect.json | 2 +- defaults/programs/program_test_find_code.json | 2 +- .../programs/program_test_find_color.json | 2 +- defaults/programs/program_test_find_face.json | 2 +- .../program_test_find_path_ahead.json | 2 +- .../programs/program_test_img_average.json | 2 +- defaults/programs/program_test_input.json | 2 +- defaults/programs/program_test_led.json | 2 +- defaults/programs/program_test_music.json | 2 +- defaults/programs/program_test_output.json | 2 +- defaults/programs/program_test_sonars.json | 2 +- .../programs/program_test_sound_hear.json | 2 +- defaults/programs/program_test_sound_rec.json | 2 +- 29 files changed, 185 insertions(+), 115 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index 18f24c3c..b7e8ab73 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -1,4 +1,5 @@ import logging +import uuid from tinydb import TinyDB, Query from threading import Lock from datetime import datetime @@ -9,13 +10,6 @@ ACTIVITY_KIND_STOCK = "stock" ACTIVITY_KIND_USER = "user" -class Activity(): - def __init__(self, name, description, data, kind, status): - self._name = name - self._description = description - self._data = data - self._kind = kind - self._status = status class Activities(): _instance = None @@ -32,14 +26,14 @@ def __init__(self): self.lock = Lock() self.permanentlyRemoveDeletedActivities() - def load(self, name, default, active_only=True): + def load(self, id, default, active_only=True): with self.lock: - if name and default is None: + if id and default is None: activities = [] if active_only: - activities = self.activities.search((self.query.name == name) & (self.query.status == ACTIVITY_STATUS_ACTIVE)) + activities = self.activities.search((self.query.id == id) & (self.query.status == ACTIVITY_STATUS_ACTIVE)) else: - activities = self.activities.search(self.query.name == name) + activities = self.activities.search(self.query.id == id) if len(activities) > 0: return activities[0] elif default is not None: @@ -48,19 +42,24 @@ def load(self, name, default, active_only=True): return None return None - def save(self, name, activity): + def save(self, activity): + if activity.get("id") is None: + activity["id"] = str(uuid.uuid4()) with self.lock: # if saved activity is "default", reset existing default activity to "non-default" if activity.get("default", False) is True: self.activities.update({'default': False}) - if self.activities.search(self.query.name == name) == []: - self.activities.insert(activity) + if self.activities.search(self.query.id == activity.get("id")) == []: + activity = self.activities.insert(activity) else: - self.activities.update(activity, self.query.name == name) + self.activities.update(activity, self.query.id == activity.get("id")) + activity = self.activities.search(self.query.id == activity.get("id"))[0] + logging.info("updating/creating activity: %s", str(activity)) + return activity - def delete(self, name, logical = True): + def delete(self, id, logical = True): with self.lock: - activities = self.activities.search(self.query.name == name) + activities = self.activities.search(self.query.id == id) if len(activities) > 0: activity = activities[0] if activity.get("default", False) is True: @@ -68,16 +67,16 @@ def delete(self, name, logical = True): if logical: activity["status"] = ACTIVITY_STATUS_DELETED activity["modified"] = datetime.now().isoformat() - self.activities.update(activity, self.query.name == name) + self.activities.update(activity, self.query.id == id) else: - self.activities.remove(self.query.name == name) + self.activities.remove(self.query.id == id) def permanentlyRemoveDeletedActivities(self): for a in self.list(active_only=False): - logging.info("checking: " + a["name"]) + logging.info("checking: " + a["id"]) if a["status"] == ACTIVITY_STATUS_DELETED: logging.info("deleting: " + a["name"]) - self.delete(a["name"], logical=False) + self.delete(a["id"], logical=False) def list(self, active_only = True): with self.lock: diff --git a/coderbot/api.py b/coderbot/api.py index 3487fb69..9b381f6c 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -277,41 +277,58 @@ def deleteMusicPackage(name): ## Programs -def saveProgram(name, body): +def saveNewProgram(body): overwrite = body.get("overwrite") - existing_program = prog_engine.load(name) + name = body["name"] + existing_program = prog_engine.load_by_name(name) logging.info("saving - name: %s, body: %s", name, str(existing_program)) if existing_program is not None and not overwrite: return "askOverwrite" elif existing_program is not None and existing_program.is_stock() == True: return "defaultCannotOverwrite", 400 program = Program(name=body.get("name"), code=body.get("code"), dom_code=body.get("dom_code"), modified=datetime.now(), status="active") + program_db_entry = prog_engine.save(program) + return program_db_entry + +def saveProgram(id, body): + overwrite = body.get("overwrite") + name = body["name"] + existing_program = prog_engine.load(id) + logging.info("saving - id: %s - name: %s - existing: %s", id, name, str(existing_program is not None)) + if existing_program is not None and existing_program.is_stock() == True: + return "defaultCannotOverwrite", 400 + program = Program( + id=existing_program._id, + name=existing_program._name, + code=body.get("code"), + dom_code=body.get("dom_code"), + modified=datetime.now(), + status="active") prog_engine.save(program) - return {} + return program.as_dict() -def loadProgram(name): - existing_program = prog_engine.load(name) +def loadProgram(id): + existing_program = prog_engine.load(id) if existing_program: return existing_program.as_dict(), 200 else: return None, 404 -def deleteProgram(name): - prog_engine.delete(name, logical=True) +def deleteProgram(id): + prog_engine.delete(id, logical=True) def listPrograms(): return prog_engine.prog_list(active_only=True) -def runProgram(name, body): +def runProgram(id): """ Execute the given program """ logging.debug("program_exec") - code = body.get('code') - prog = prog_engine.create(name, code) + prog = prog_engine.load(id) return prog.execute() -def stopProgram(name): +def stopProgram(id): """ Stop the program execution """ @@ -321,7 +338,7 @@ def stopProgram(name): prog.stop() return "ok" -def statusProgram(name): +def statusProgram(id): """ Expose the program status """ @@ -334,19 +351,20 @@ def statusProgram(name): ## Activities -def saveActivity(name, body): +def saveActivity(id, body): activity = body - activities.save(activity.get("name"), activity) + activity["id"] = id + return activities.save(activity) def saveAsNewActivity(body): activity = body - activities.save(activity.get("name"), activity) + return activities.save(activity) -def loadActivity(name=None, default=None): - return activities.load(name, default) +def loadActivity(id=None, default=None): + return activities.load(id, default) -def deleteActivity(name): - activities.delete(name), 200 +def deleteActivity(id): + activities.delete(id), 200 def listActivities(): return activities.list() @@ -411,7 +429,7 @@ def cloudRegistrationDelete(): return {} def cloudRegistrationStatus(): - registration = cloud_settings.get('registration', {}) + registration = Config.read().get("cloud").get('registration', {}) return { "registered": CloudManager.get_instance().registration_status(), "name": registration.get('name', ""), diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 40a600a3..7d767859 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -35,6 +35,7 @@ SYNC_UPSTREAM = 'u' SYNC_DOWNSTREAM = 'd' SYNC_BIDIRECTIONAL = 'b' +SYNC_DISABLED = 'n' ENTITY_KIND_USER = "user" ENTITY_KIND_STOCK = "stock" @@ -197,7 +198,7 @@ def sync_settings(self, api_instance, sync_mode): config = Config.read() local_setting = { "settings": config.get("settings"), - "cloud": config.get("cloud") + #"cloud": config.get("cloud") } local_most_recent = datetime.fromisoformat(cloud_setting_object.get("modified")).timestamp() < Config.modified() cloud_kind_user = cloud_setting_object.get("kind") == ENTITY_KIND_USER @@ -217,7 +218,7 @@ def sync_settings(self, api_instance, sync_mode): logging.info("settings.upstream") elif cloud_setting != local_setting: # setting, down config["settings"] = cloud_setting["settings"] - config["cloud"] = cloud_setting["cloud"] + #config["cloud"] = cloud_setting["cloud"] Config.write(config) logging.info("settings.downstream") self._sync_status["settings"] = "synced" @@ -280,8 +281,8 @@ def sync_activities(self, api_instance, sync_mode): elif sync_mode == "d" or (not local_activity_more_recent and sync_mode == SYNC_BIDIRECTIONAL): al["data"] = ac.get("data") al["modified"] = ac.get("modified") - Activities.get_instance().save(al.get("name"), al) - logging.info("activities.update.downstream: " + al.get("name")) + Activities.get_instance().save(al) + logging.info("activities.update.downstream: " + al.get("id")) elif ac is None and sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: body = Activity( id="", @@ -296,7 +297,7 @@ def sync_activities(self, api_instance, sync_mode): api_response = api_instance.create_robot_activity(body=body) al["id"] = api_response.body["id"] al["org_id"] = api_response.body["org_id"] - Activities.get_instance().save(al.get("name"), al) + Activities.get_instance().save(al) logging.info("activities.create.upstream: " + al.get("name")) elif ac is None and sync_mode in [SYNC_DOWNSTREAM]: Activities.get_instance().delete(al.get("name")) @@ -312,7 +313,7 @@ def sync_activities(self, api_instance, sync_mode): activity["description"] = ac.get("description") activity["kind"] = ac.get("kind") activity["status"] = ac.get("status") - Activities.get_instance().save(ac.get("name"), activity) + Activities.get_instance().save(activity) # manage local user activities to be deleted locally and upstream for al in activities_local_to_be_deleted: @@ -320,7 +321,7 @@ def sync_activities(self, api_instance, sync_mode): logging.info("activities.delete.upstream: " + al.get("name")) api_response = api_instance.delete_robot_program(path_params={"activity_id":al.get("id")}) # delete locally permanently - Activities.get_instance().delete(al.get("name"), logical=False) + Activities.get_instance().delete(al.get("id"), logical=False) # manage local stock activities to be deleted locally for al in activities_local_stock: @@ -328,7 +329,7 @@ def sync_activities(self, api_instance, sync_mode): if al.get("id") is not None and activities_cloud_map.get(al.get("id")) is None: logging.info("activities.delete.stock.locally: " + al.get("name")) # delete locally permanently - Activities.get_instance().delete(al.get("name"), logical=False) + Activities.get_instance().delete(al.get("id"), logical=False) self._sync_status["activities"] = "synced" except cloud_api_robot_client.ApiException as e: @@ -369,6 +370,7 @@ def sync_programs(self, api_instance, sync_mode): for pl in programs_local_user: pc = programs_cloud_map.get(pl.get("id")) pc_pl_equals = (pc is not None and + pc.get("id") == pl.get("id") and pc.get("name") == pl.get("name") and pc.get("code") == pl.get("code") and pc.get("dom_code") == pl.get("dom_code") and diff --git a/coderbot/main.py b/coderbot/main.py index 681f18c4..9a715aa6 100644 --- a/coderbot/main.py +++ b/coderbot/main.py @@ -68,7 +68,6 @@ def run_server(): settings = Config.read().get("settings") app.settings = settings network_settings = Config.read().get("network") - cloud_settings = Config.read().get("cloud") bot = CoderBot.get_instance(settings=settings, motor_trim_factor=float(settings.get('move_motor_trim', 1.0)), motor_max_power=int(settings.get('motor_max_power', 100)), diff --git a/coderbot/program.py b/coderbot/program.py index 76dd7900..d78e9d4d 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -20,6 +20,7 @@ import os import threading import json +import uuid import shutil import logging from datetime import datetime @@ -92,7 +93,7 @@ def __init__(self, settings): for filename in filenames: if PROGRAM_PREFIX in filename: program_name = filename[len(PROGRAM_PREFIX):-len(PROGRAM_SUFFIX)] - if self.load(program_name) is None: + if self.load_by_name(program_name) is None: 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) @@ -122,19 +123,20 @@ def save(self, program): program._modified = datetime.now() self._program = program program_db_entry = self._program.as_dict() - if self._programs.search(query.name == program.name) != []: - self._programs.update(program_db_entry, query.name == program.name) + if self._programs.search(query.id == program._id) != []: + self._programs.update(program_db_entry, query.id == program._id) else: self._programs.insert(program_db_entry) + return program_db_entry - def load(self, name, active_only=True): + def load(self, id, active_only=True): with self.lock: query = Query() program_db_entries = None if active_only: - program_db_entries = self._programs.search((query.name == name) & (query.status == PROGRAM_STATUS_ACTIVE)) + program_db_entries = self._programs.search((query.id == id) & (query.status == PROGRAM_STATUS_ACTIVE)) else: - program_db_entries = self._programs.search(query.name == name) + program_db_entries = self._programs.search(query.id == id) if len(program_db_entries) > 0: prog_db_entry = program_db_entries[0] #logging.debug(prog_db_entry) @@ -142,26 +144,31 @@ def load(self, name, active_only=True): return self._program return None - def delete(self, name, logical = True): + def load_by_name(self, name): + with self.lock: + program = None + query = Query() + programs = self._programs.search((query.name == name) & (query.status == PROGRAM_STATUS_ACTIVE)) + if len(programs) > 0: + program = Program.from_dict(programs[0]) + return program + + def delete(self, id, logical = True): with self.lock: query = Query() - program_db_entries = self._programs.search(query.name == name) + program_db_entries = self._programs.search(query.id == id) if len(program_db_entries) > 0: program_db_entry = program_db_entries[0] if logical: program_db_entry["status"] = PROGRAM_STATUS_DELETED program_db_entry["modified"] = datetime.now().isoformat() - self._programs.update(program_db_entry, query.name == name) + self._programs.update(program_db_entry, query.id == id) else: - self._programs.remove(query.name == name) + self._programs.remove(query.id == id) return None - def create(self, name, code): - self._program = Program(name, code, modified=datetime.now()) - return self._program - - def is_running(self, name): - return self._program.is_running() and self._program.name == name + def is_running(self, id): + return self._program.is_running() and self._program.id == id def check_end(self): return self._program.check_end() @@ -185,14 +192,14 @@ class Program: def dom_code(self): return self._dom_code - def __init__(self, name, description=None, code=None, dom_code=None, kind=PROGRAM_KIND_USER, id=None, modified=None, status=None): + def __init__(self, name, id=str(uuid.uuid4()), description=None, code=None, dom_code=None, kind=PROGRAM_KIND_USER, modified=None, status=None): self._thread = None + self._id = id self._name = name self._description = description self._dom_code = dom_code self._code = code self._kind = kind - self._id = id self._modified = modified self._status = status diff --git a/coderbot/v1.yml b/coderbot/v1.yml index 8bb36c5e..86677cdd 100644 --- a/coderbot/v1.yml +++ b/coderbot/v1.yml @@ -155,6 +155,27 @@ paths: description: "ok" /programs: + post: + operationId: "api.saveNewProgram" + summary: "Save a new program" + tags: + - Program management + requestBody: + description: Program object + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Program' + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Program' + 400: + description: "Failed to save the program" get: operationId: "api.listPrograms" summary: "Get the list of all the saved programs" @@ -164,12 +185,12 @@ paths: 200: description: "ok" - /programs/{name}: + /programs/{id}: get: operationId: "api.loadProgram" summary: "Get the program with the specified name" parameters: - - name: name + - name: id in: path required: true schema: @@ -183,7 +204,7 @@ paths: operationId: "api.deleteProgram" summary: "Delete a program" parameters: - - name: name + - name: id in: path required: true schema: @@ -199,7 +220,7 @@ paths: tags: - Program management parameters: - - name: name + - name: id in: path required: true schema: @@ -217,37 +238,30 @@ paths: 400: description: "Failed to save the program" - /programs/{name}/run: + /programs/{id}/run: post: operationId: "api.runProgram" summary: "Execute the given program" tags: - Program management parameters: - - name: name + - name: id in: path required: true schema: type: string - requestBody: - description: Program object - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Program' responses: 200: description: "ok" - /programs/{name}/status: + /programs/{id}/status: get: operationId: "api.statusProgram" summary: "Get the status of the given program" tags: - Program management parameters: - - name: name + - name: id in: path required: true schema: @@ -256,14 +270,14 @@ paths: 200: description: "ok" - /programs/{name}/stop: + /programs/{id}/stop: patch: operationId: "api.stopProgram" summary: "Stop the given program" tags: - Program management parameters: - - name: name + - name: id in: path required: true schema: @@ -281,6 +295,13 @@ paths: responses: 200: description: "ok" + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Activity' + post: operationId: "api.saveAsNewActivity" summary: "Save a new activity" @@ -296,14 +317,18 @@ paths: responses: 200: description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Activity' 400: description: "Failed to save the activity" - /activities/{name}: + /activities/{id}: get: operationId: "api.loadActivity" summary: "Get the activity with the specified name" parameters: - - name: name + - name: id in: path required: true schema: @@ -318,11 +343,16 @@ paths: responses: 200: description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Activity' + put: operationId: "api.saveActivity" - summary: "Save the activity with the specified name" + summary: "Save the activity with the specified id" parameters: - - name: name + - name: id in: path required: true schema: @@ -339,13 +369,18 @@ paths: responses: 200: description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Activity' + delete: operationId: "api.deleteActivity" summary: "Delete an activity" tags: - Activity management parameters: - - name: name + - name: id in: path required: true schema: @@ -752,6 +787,11 @@ components: Program: type: object properties: + id: + type: string + #pattern: '^[[:xdigit:]]{8}(?:\-[[:xdigit:]]{4}){3}\-[[:xdigit:]]{12}$' + minLength: 36 + maxLength: 36 name: type: string pattern: '^[a-zA-ZA-zÀ-ú0-9-_ ]+$' @@ -773,6 +813,11 @@ components: Activity: type: object properties: + id: + type: string + #pattern: '^[[:xdigit:]]{8}(?:\-[[:xdigit:]]{4}){3}\-[[:xdigit:]]{12}$/ + minLength: 36 + maxLength: 36 name: type: string minLength: 1 diff --git a/defaults/config.json b/defaults/config.json index 518c66fc..6c43815a 100644 --- a/defaults/config.json +++ b/defaults/config.json @@ -52,9 +52,9 @@ }, "cloud":{ "sync_modes":{ - "activities":"b", - "programs":"b", - "settings":"b" + "activities":"n", + "programs":"n", + "settings":"n" }, "sync_period":"10", "reg_otp":"AB1234CD" diff --git a/defaults/programs/program_demo_ar_tags.json b/defaults/programs/program_demo_ar_tags.json index 86699bac..901ff1bc 100644 --- a/defaults/programs/program_demo_ar_tags.json +++ b/defaults/programs/program_demo_ar_tags.json @@ -1 +1 @@ -{"dom_code": "tag_mapcodelistacodespositionsWHILETRUEtag_mapcodescodestag_mappositionspositionstag_mapGTcodes0GTGETLASTGETFIRSTpositions150codeGETFIRSTcodescodesEQcode1FORWARD1001LEFT1001EQcode2FORWARD1003EQcode3FORWARD1001RIGHT1001EQcode42RIGHT1000.5LEFT1000.5itSono arrivato!EQcode5itAttenzione!", "code": "tag_map = None\ncode = None\nlista = None\ncodes = None\npositions = None\n\n\nwhile True:\n get_prog_eng().check_end()\n tag_map = get_cam().find_ar_code()\n codes = tag_map.get('codes')\n positions = tag_map.get('positions')\n if len(codes) > 0:\n if positions[0][-1] > 150:\n code = codes[0]\n get_cam().set_text(codes)\n if code == 1:\n get_bot().forward(speed=100, elapse=1)\n get_bot().left(speed=100, elapse=1)\n elif code == 2:\n get_bot().forward(speed=100, elapse=3)\n elif code == 3:\n get_bot().forward(speed=100, elapse=1)\n get_bot().right(speed=100, elapse=1)\n elif code == 4:\n for count in range(2):\n get_prog_eng().check_end()\n get_bot().right(speed=100, elapse=0.5)\n get_bot().left(speed=100, elapse=0.5)\n get_audio().say('Sono arrivato!', locale=\"it\")\n elif code == 5:\n get_audio().say('Attenzione!', locale=\"it\")\n get_cam().set_text('')\n", "name": "demo_ar_tags"} \ No newline at end of file +{"id": "5b4cf283-1666-4ada-87c8-234ff0ed37e8", "dom_code": "tag_mapcodelistacodespositionsWHILETRUEtag_mapcodescodestag_mappositionspositionstag_mapGTcodes0GTGETLASTGETFIRSTpositions150codeGETFIRSTcodescodesEQcode1FORWARD1001LEFT1001EQcode2FORWARD1003EQcode3FORWARD1001RIGHT1001EQcode42RIGHT1000.5LEFT1000.5itSono arrivato!EQcode5itAttenzione!", "code": "tag_map = None\ncode = None\nlista = None\ncodes = None\npositions = None\n\n\nwhile True:\n get_prog_eng().check_end()\n tag_map = get_cam().find_ar_code()\n codes = tag_map.get('codes')\n positions = tag_map.get('positions')\n if len(codes) > 0:\n if positions[0][-1] > 150:\n code = codes[0]\n get_cam().set_text(codes)\n if code == 1:\n get_bot().forward(speed=100, elapse=1)\n get_bot().left(speed=100, elapse=1)\n elif code == 2:\n get_bot().forward(speed=100, elapse=3)\n elif code == 3:\n get_bot().forward(speed=100, elapse=1)\n get_bot().right(speed=100, elapse=1)\n elif code == 4:\n for count in range(2):\n get_prog_eng().check_end()\n get_bot().right(speed=100, elapse=0.5)\n get_bot().left(speed=100, elapse=0.5)\n get_audio().say('Sono arrivato!', locale=\"it\")\n elif code == 5:\n get_audio().say('Attenzione!', locale=\"it\")\n get_cam().set_text('')\n", "name": "demo_ar_tags"} \ No newline at end of file diff --git a/defaults/programs/program_demo_cat_follower.json b/defaults/programs/program_demo_cat_follower.json index 905374e6..bf72dae0 100644 --- a/defaults/programs/program_demo_cat_follower.json +++ b/defaults/programs/program_demo_cat_follower.json @@ -1 +1 @@ -{"name": "demo_cat_follower", "dom_code": "objectclasspositionpos_xWHILETRUEobjectGETFIRSTgeneric_object_detectclassGETFIRSTobjectEQclasscatpositionGETFROM_STARTobject3pos_xDIVIDEADDGETFROM_STARTposition1GETFROM_STARTposition32classLTpos_x40LEFT600.1GTpos_x60RIGHT600.1FORWARD1000.2object", "code": "object2 = None\nclass2 = None\nposition = None\npos_x = None\n\n\nwhile True:\n get_prog_eng().check_end()\n object2 = get_cam().cnn_detect_objects(\"generic_object_detect\")[0]\n class2 = object2[0]\n if class2 == 'cat':\n position = object2[2]\n pos_x = (position[0] + position[2]) / 2\n get_cam().set_text(class2)\n if pos_x < 40:\n get_bot().left(speed=60, elapse=0.1)\n elif pos_x > 60:\n get_bot().right(speed=60, elapse=0.1)\n else:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_cam().set_text(object2)\n"} \ No newline at end of file +{"id": "a4e429dd-b5b4-4f5f-8c2e-5e5f5d557aff", "name": "demo_cat_follower", "dom_code": "objectclasspositionpos_xWHILETRUEobjectGETFIRSTgeneric_object_detectclassGETFIRSTobjectEQclasscatpositionGETFROM_STARTobject3pos_xDIVIDEADDGETFROM_STARTposition1GETFROM_STARTposition32classLTpos_x40LEFT600.1GTpos_x60RIGHT600.1FORWARD1000.2object", "code": "object2 = None\nclass2 = None\nposition = None\npos_x = None\n\n\nwhile True:\n get_prog_eng().check_end()\n object2 = get_cam().cnn_detect_objects(\"generic_object_detect\")[0]\n class2 = object2[0]\n if class2 == 'cat':\n position = object2[2]\n pos_x = (position[0] + position[2]) / 2\n get_cam().set_text(class2)\n if pos_x < 40:\n get_bot().left(speed=60, elapse=0.1)\n elif pos_x > 60:\n get_bot().right(speed=60, elapse=0.1)\n else:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_cam().set_text(object2)\n"} \ No newline at end of file diff --git a/defaults/programs/program_demo_color_seeker.json b/defaults/programs/program_demo_color_seeker.json index 5c6e53ae..c5eae413 100644 --- a/defaults/programs/program_demo_color_seeker.json +++ b/defaults/programs/program_demo_color_seeker.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angleGTdist32FORWARD1000.1ANDLTdist28GTEdist0BACKWARD1000.1GTangle5RIGHT300.1LTangle-5LEFT300.1", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n if dist > 32:\n get_bot().forward(speed=100, elapse=0.1)\n elif dist < 28 and dist >= 0:\n get_bot().backward(speed=100, elapse=0.1)\n if angle > 5:\n get_bot().right(speed=30, elapse=0.1)\n elif angle < -5:\n get_bot().left(speed=30, elapse=0.1)\n", "name": "demo_color_seeker"} \ No newline at end of file +{"id": "d1601ee4-bc8c-49f4-a79e-0015b7c7b1d7", "dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angleGTdist32FORWARD1000.1ANDLTdist28GTEdist0BACKWARD1000.1GTangle5RIGHT300.1LTangle-5LEFT300.1", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n if dist > 32:\n get_bot().forward(speed=100, elapse=0.1)\n elif dist < 28 and dist >= 0:\n get_bot().backward(speed=100, elapse=0.1)\n if angle > 5:\n get_bot().right(speed=30, elapse=0.1)\n elif angle < -5:\n get_bot().left(speed=30, elapse=0.1)\n", "name": "demo_color_seeker"} \ No newline at end of file diff --git a/defaults/programs/program_demo_io_ext.json b/defaults/programs/program_demo_io_ext.json index 315d167b..91632161 100644 --- a/defaults/programs/program_demo_io_ext.json +++ b/defaults/programs/program_demo_io_ext.json @@ -1 +1 @@ -{"name": "demo_io_ext", "dom_code": "Analog_Input_1WHILETRUEAnalog_Input_10Analog Input 1: Analog_Input_1GTAnalog_Input_11000TRUE0FALSE", "code": "Analog_Input_1 = None\n\n\nwhile True:\n get_prog_eng().check_end()\n Analog_Input_1 = get_atmega().get_input(0)\n get_cam().set_text('Analog Input 1: ' + str(Analog_Input_1))\n if Analog_Input_1 > 100:\n get_atmega().set_output(0, True)\n else:\n get_atmega().set_output(0, False)\n"} \ No newline at end of file +{"id": "30e8ea4b-967d-41bb-8d6a-67e12f7f0008", "name": "demo_io_ext", "dom_code": "Analog_Input_1WHILETRUEAnalog_Input_10Analog Input 1: Analog_Input_1GTAnalog_Input_11000TRUE0FALSE", "code": "Analog_Input_1 = None\n\n\nwhile True:\n get_prog_eng().check_end()\n Analog_Input_1 = get_atmega().get_input(0)\n get_cam().set_text('Analog Input 1: ' + str(Analog_Input_1))\n if Analog_Input_1 > 100:\n get_atmega().set_output(0, True)\n else:\n get_atmega().set_output(0, False)\n"} \ No newline at end of file diff --git a/defaults/programs/program_demo_line_follower.json b/defaults/programs/program_demo_line_follower.json index 2ad8fb82..2dc76af8 100644 --- a/defaults/programs/program_demo_line_follower.json +++ b/defaults/programs/program_demo_line_follower.json @@ -1 +1 @@ -{"dom_code": "line_x_1listaline_x_listline_x_2ar_codear_code_listWHILETRUEline_x_listline_x_1GETFIRSTline_x_listar_code_listcodesar_code_listar_codeGETFIRSTar_code_listar_code0ar_codeEQar_code18080-110001000EQar_code26060-120001550EQar_code66060-115502000GTEline_x_10GTline_x_16040-40-1MINUSline_x_150MINUSline_x_150LTline_x_140-4040-1MINUS50line_x_1MINUS50line_x_15050-1150150", "code": "line_x_1 = None\nlista = None\nline_x_list = None\nline_x_2 = None\nar_code = None\nar_code_list = None\n\n\nwhile True:\n get_prog_eng().check_end()\n line_x_list = get_cam().find_line()\n line_x_1 = line_x_list[0]\n ar_code_list = get_cam().find_ar_code().get('codes')\n if not not len(ar_code_list):\n ar_code = ar_code_list[0]\n else:\n ar_code = 0\n get_cam().set_text(ar_code)\n if ar_code == 1:\n get_bot().motor_control(speed_left=80, speed_right=80, elapse=-1, steps_left=1000, steps_right=1000)\n elif ar_code == 2:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=2000, steps_right=1550)\n elif ar_code == 6:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=1550, steps_right=2000)\n else:\n if line_x_1 >= 0:\n if line_x_1 > 60:\n get_bot().motor_control(speed_left=40, speed_right=-40, elapse=-1, steps_left=line_x_1 - 50, steps_right=line_x_1 - 50)\n elif line_x_1 < 40:\n get_bot().motor_control(speed_left=-40, speed_right=40, elapse=-1, steps_left=50 - line_x_1, steps_right=50 - line_x_1)\n else:\n get_bot().motor_control(speed_left=50, speed_right=50, elapse=-1, steps_left=150, steps_right=150)\n", "name": "demo_line_follower"} \ No newline at end of file +{"id": "f893bc1c-9d22-4bbb-ac3c-05637c49d307", "dom_code": "line_x_1listaline_x_listline_x_2ar_codear_code_listWHILETRUEline_x_listline_x_1GETFIRSTline_x_listar_code_listcodesar_code_listar_codeGETFIRSTar_code_listar_code0ar_codeEQar_code18080-110001000EQar_code26060-120001550EQar_code66060-115502000GTEline_x_10GTline_x_16040-40-1MINUSline_x_150MINUSline_x_150LTline_x_140-4040-1MINUS50line_x_1MINUS50line_x_15050-1150150", "code": "line_x_1 = None\nlista = None\nline_x_list = None\nline_x_2 = None\nar_code = None\nar_code_list = None\n\n\nwhile True:\n get_prog_eng().check_end()\n line_x_list = get_cam().find_line()\n line_x_1 = line_x_list[0]\n ar_code_list = get_cam().find_ar_code().get('codes')\n if not not len(ar_code_list):\n ar_code = ar_code_list[0]\n else:\n ar_code = 0\n get_cam().set_text(ar_code)\n if ar_code == 1:\n get_bot().motor_control(speed_left=80, speed_right=80, elapse=-1, steps_left=1000, steps_right=1000)\n elif ar_code == 2:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=2000, steps_right=1550)\n elif ar_code == 6:\n get_bot().motor_control(speed_left=60, speed_right=60, elapse=-1, steps_left=1550, steps_right=2000)\n else:\n if line_x_1 >= 0:\n if line_x_1 > 60:\n get_bot().motor_control(speed_left=40, speed_right=-40, elapse=-1, steps_left=line_x_1 - 50, steps_right=line_x_1 - 50)\n elif line_x_1 < 40:\n get_bot().motor_control(speed_left=-40, speed_right=40, elapse=-1, steps_left=50 - line_x_1, steps_right=50 - line_x_1)\n else:\n get_bot().motor_control(speed_left=50, speed_right=50, elapse=-1, steps_left=150, steps_right=150)\n", "name": "demo_line_follower"} \ No newline at end of file diff --git a/defaults/programs/program_demo_obstacle_avoidance.json b/defaults/programs/program_demo_obstacle_avoidance.json index ab3e4a8e..899ffe38 100644 --- a/defaults/programs/program_demo_obstacle_avoidance.json +++ b/defaults/programs/program_demo_obstacle_avoidance.json @@ -1 +1 @@ -{"name": "demo_obstacle_avoidance", "code": "while True:\n get_prog_eng().check_end()\n if get_bot().get_sonar_distance(0) < 15:\n get_bot().right(speed=100, elapse=0.5)\n else:\n get_bot().forward(speed=100, elapse=1)\n", "dom_code": "WHILETRUELT015RIGHT1000.5FORWARD1001"} \ No newline at end of file +{"id": "0cfd0dd1-7ec3-4f34-9720-41f42b0209e1", "name": "demo_obstacle_avoidance", "code": "while True:\n get_prog_eng().check_end()\n if get_bot().get_sonar_distance(0) < 15:\n get_bot().right(speed=100, elapse=0.5)\n else:\n get_bot().forward(speed=100, elapse=1)\n", "dom_code": "WHILETRUELT015RIGHT1000.5FORWARD1001"} \ No newline at end of file diff --git a/defaults/programs/program_demo_roboetologist.json b/defaults/programs/program_demo_roboetologist.json index 71f717b8..1a50d542 100644 --- a/defaults/programs/program_demo_roboetologist.json +++ b/defaults/programs/program_demo_roboetologist.json @@ -1 +1 @@ -{"name":"demo_roboetologist","dom_code":"attesavelocita_maxvar_marciaIndietrovar_giravoltevar_ritiratavelocitastancoScodinzolaDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaLEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaEvita_OstacoliDescrivi questa funzione...15LT020BACKWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT120LEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT220RIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaFORWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaattesa0.2velocita_max80var_marciaIndietro0var_giravolte0var_ritirata0stanco0velocita1WHILETRUEstanco1var_marciaIndietro1var_giravolte1var_ritirata1EQstanco5stanco0velocita1GTstanco3velocita0.5EQvar_ritirata4var_ritirata0EQvar_giravolte8var_giravolte05EQvar_marciaIndietro6var_marciaIndietro0GiravolteDescrivi questa funzione...RIGHT50MULTIPLY100velocita1100velocita_max20.2attesaPatugliamentoDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max10.5Ritirata_e_fugaDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaLEFT50MULTIPLY100velocita1100velocita_max30.2attesaFORWARD50MULTIPLY100velocita1100velocita_max10.2attesaMarciaIndietro_e_ScartoDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaRIGHT50MULTIPLY100velocita1100velocita_max10.2attesa","code":"from numbers import Number\n\nattesa = None\nvelocita_max = None\nvar_marciaIndietro = None\nvar_giravolte = None\nvar_ritirata = None\nvelocita = None\nstanco = None\n\n# Descrivi questa funzione...\ndef Scodinzola():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Evita_Ostacoli():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count2 in range(15):\n get_prog_eng().check_end()\n if get_bot().get_sonar_distance(0) < 20:\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(1) < 20:\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(2) < 20:\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n else:\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Giravolte():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=2)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Patugliamento():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count3 in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(0.5)\n\n# Descrivi questa funzione...\ndef Ritirata_e_fuga():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=3)\n get_bot().sleep(attesa)\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef MarciaIndietro_e_Scarto():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n\nattesa = 0.2\nvelocita_max = 80\nvar_marciaIndietro = 0\nvar_giravolte = 0\nvar_ritirata = 0\nstanco = 0\nvelocita = 1\nwhile True:\n get_prog_eng().check_end()\n stanco = (stanco if isinstance(stanco, Number) else 0) + 1\n var_marciaIndietro = (var_marciaIndietro if isinstance(var_marciaIndietro, Number) else 0) + 1\n var_giravolte = (var_giravolte if isinstance(var_giravolte, Number) else 0) + 1\n var_ritirata = (var_ritirata if isinstance(var_ritirata, Number) else 0) + 1\n if stanco == 5:\n stanco = 0\n velocita = 1\n elif stanco > 3:\n velocita = 0.5\n Scodinzola()\n Evita_Ostacoli()\n Patugliamento()\n if var_ritirata == 4:\n var_ritirata = 0\n Ritirata_e_fuga()\n if var_giravolte == 8:\n var_giravolte = 0\n Giravolte()\n get_bot().sleep(5)\n if var_marciaIndietro == 6:\n var_marciaIndietro = 0\n MarciaIndietro_e_Scarto()\n","default":""} \ No newline at end of file +{"id": "a3a01609-abb9-471b-b1fb-9cbb126d67c8", "name":"demo_roboetologist","dom_code":"attesavelocita_maxvar_marciaIndietrovar_giravoltevar_ritiratavelocitastancoScodinzolaDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaLEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.30.2attesaEvita_OstacoliDescrivi questa funzione...15LT020BACKWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaRIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT120LEFT50MULTIPLY100velocita1100velocita_max0.50.2attesaLT220RIGHT50MULTIPLY100velocita1100velocita_max0.50.2attesaFORWARD50MULTIPLY100velocita1100velocita_max0.50.2attesaattesa0.2velocita_max80var_marciaIndietro0var_giravolte0var_ritirata0stanco0velocita1WHILETRUEstanco1var_marciaIndietro1var_giravolte1var_ritirata1EQstanco5stanco0velocita1GTstanco3velocita0.5EQvar_ritirata4var_ritirata0EQvar_giravolte8var_giravolte05EQvar_marciaIndietro6var_marciaIndietro0GiravolteDescrivi questa funzione...RIGHT50MULTIPLY100velocita1100velocita_max20.2attesaPatugliamentoDescrivi questa funzione...3RIGHT50MULTIPLY100velocita1100velocita_max10.5Ritirata_e_fugaDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaLEFT50MULTIPLY100velocita1100velocita_max30.2attesaFORWARD50MULTIPLY100velocita1100velocita_max10.2attesaMarciaIndietro_e_ScartoDescrivi questa funzione...BACKWARD50MULTIPLY100velocita1100velocita_max10.2attesaRIGHT50MULTIPLY100velocita1100velocita_max10.2attesa","code":"from numbers import Number\n\nattesa = None\nvelocita_max = None\nvar_marciaIndietro = None\nvar_giravolte = None\nvar_ritirata = None\nvelocita = None\nstanco = None\n\n# Descrivi questa funzione...\ndef Scodinzola():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.3)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Evita_Ostacoli():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count2 in range(15):\n get_prog_eng().check_end()\n if get_bot().get_sonar_distance(0) < 20:\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(1) < 20:\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n elif get_bot().get_sonar_distance(2) < 20:\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n else:\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=0.5)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Giravolte():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=2)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef Patugliamento():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n for count3 in range(3):\n get_prog_eng().check_end()\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(0.5)\n\n# Descrivi questa funzione...\ndef Ritirata_e_fuga():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().left(speed=min(max(100 * velocita, 1), velocita_max), elapse=3)\n get_bot().sleep(attesa)\n get_bot().forward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n# Descrivi questa funzione...\ndef MarciaIndietro_e_Scarto():\n global attesa, velocita_max, var_marciaIndietro, var_giravolte, var_ritirata, velocita, stanco\n get_prog_eng().check_end()\n get_bot().backward(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n get_bot().right(speed=min(max(100 * velocita, 1), velocita_max), elapse=1)\n get_bot().sleep(attesa)\n\n\nattesa = 0.2\nvelocita_max = 80\nvar_marciaIndietro = 0\nvar_giravolte = 0\nvar_ritirata = 0\nstanco = 0\nvelocita = 1\nwhile True:\n get_prog_eng().check_end()\n stanco = (stanco if isinstance(stanco, Number) else 0) + 1\n var_marciaIndietro = (var_marciaIndietro if isinstance(var_marciaIndietro, Number) else 0) + 1\n var_giravolte = (var_giravolte if isinstance(var_giravolte, Number) else 0) + 1\n var_ritirata = (var_ritirata if isinstance(var_ritirata, Number) else 0) + 1\n if stanco == 5:\n stanco = 0\n velocita = 1\n elif stanco > 3:\n velocita = 0.5\n Scodinzola()\n Evita_Ostacoli()\n Patugliamento()\n if var_ritirata == 4:\n var_ritirata = 0\n Ritirata_e_fuga()\n if var_giravolte == 8:\n var_giravolte = 0\n Giravolte()\n get_bot().sleep(5)\n if var_marciaIndietro == 6:\n var_marciaIndietro = 0\n MarciaIndietro_e_Scarto()\n","default":""} \ No newline at end of file diff --git a/defaults/programs/program_demo_sound_clap_control.json b/defaults/programs/program_demo_sound_clap_control.json index a735de15..c42d2efd 100644 --- a/defaults/programs/program_demo_sound_clap_control.json +++ b/defaults/programs/program_demo_sound_clap_control.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEnoise10000.2noiseEQnoiseFALSEFORWARD100-1RIGHT1000.5", "code": "noise = None\n\n\nwhile True:\n get_prog_eng().check_end()\n noise = get_audio().hear(level=1000, elapse=0.2)\n get_cam().set_text(noise)\n if noise == False:\n get_bot().forward(speed=100, elapse=-1)\n else:\n get_bot().right(speed=100, elapse=0.5)\nget_bot().stop()\n", "name": "demo_sound_clap_control"} \ No newline at end of file +{"id": "e4dada78-915e-4916-931a-e96f754812c4", "dom_code": "WHILETRUEnoise10000.2noiseEQnoiseFALSEFORWARD100-1RIGHT1000.5", "code": "noise = None\n\n\nwhile True:\n get_prog_eng().check_end()\n noise = get_audio().hear(level=1000, elapse=0.2)\n get_cam().set_text(noise)\n if noise == False:\n get_bot().forward(speed=100, elapse=-1)\n else:\n get_bot().right(speed=100, elapse=0.5)\nget_bot().stop()\n", "name": "demo_sound_clap_control"} \ No newline at end of file diff --git a/defaults/programs/program_test_cnn_classifier.json b/defaults/programs/program_test_cnn_classifier.json index e79a8329..ac8cbdc2 100644 --- a/defaults/programs/program_test_cnn_classifier.json +++ b/defaults/programs/program_test_cnn_classifier.json @@ -1 +1 @@ -{"name": "test_cnn_classifier", "dom_code": "WHILETRUEgeneric_fast_low", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().cnn_classify(\"generic_fast_low\"))\n"} \ No newline at end of file +{"id": "4b19dbc7-55a8-4888-9e2e-e05a0e65d29e", "name": "test_cnn_classifier", "dom_code": "WHILETRUEgeneric_fast_low", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().cnn_classify(\"generic_fast_low\"))\n"} \ No newline at end of file diff --git a/defaults/programs/program_test_cnn_object_detect.json b/defaults/programs/program_test_cnn_object_detect.json index 9723235d..f7e994bd 100644 --- a/defaults/programs/program_test_cnn_object_detect.json +++ b/defaults/programs/program_test_cnn_object_detect.json @@ -1 +1 @@ -{"name": "test_cnn_object_detect", "dom_code": "WHILETRUEgeneric_object_detect", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().cnn_detect_objects(\"generic_object_detect\"))\n"} \ No newline at end of file +{"id": "9622b72c-c7d4-4643-9c76-3148f16572a7", "name": "test_cnn_object_detect", "dom_code": "WHILETRUEgeneric_object_detect", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().cnn_detect_objects(\"generic_object_detect\"))\n"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_code.json b/defaults/programs/program_test_find_code.json index 37b1b610..346a4b04 100644 --- a/defaults/programs/program_test_find_code.json +++ b/defaults/programs/program_test_find_code.json @@ -1 +1 @@ -{"dom_code": "WHILETRUE", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().find_qr_code())\n", "name": "test_find_code"} \ No newline at end of file +{"id": "6cc16350-c47c-4e02-b442-be3fa7ce5942", "dom_code": "WHILETRUE", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().find_qr_code())\n", "name": "test_find_code"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_color.json b/defaults/programs/program_test_find_color.json index 293735b1..9cd9e0ee 100644 --- a/defaults/programs/program_test_find_color.json +++ b/defaults/programs/program_test_find_color.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angle", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n", "name": "test_find_color"} \ No newline at end of file +{"id": "9fb15bbd-97a6-490f-89c9-67a1ae0f1598", "dom_code": "WHILETRUEdistDIST#96b73cangleANGLE#96b73cDistance: dist angle: angle", "code": "dist = None\nangle = None\n\n\nwhile True:\n get_prog_eng().check_end()\n dist = get_cam().find_color('#96b73c')[0]\n angle = get_cam().find_color('#96b73c')[1]\n get_cam().set_text(''.join([str(temp_value) for temp_value in ['Distance: ', dist, ' angle: ', angle]]))\n", "name": "test_find_color"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_face.json b/defaults/programs/program_test_find_face.json index 4320243b..44008f5f 100644 --- a/defaults/programs/program_test_find_face.json +++ b/defaults/programs/program_test_find_face.json @@ -1 +1 @@ -{"dom_code": "faceface_xface_sizeWHILETRUEfaceALLface_xGETFROM_STARTface1face_sizeGETFROM_STARTface2face: face_xface_xLTface_x-10LEFT800.1GTface_x10RIGHT800.1", "code": "face = None\nface_x = None\nface_size = None\n\n\nwhile True:\n get_prog_eng().check_end()\n face = get_cam().find_face()\n face_x = face[0]\n face_size = face[1]\n get_cam().set_text(str('face: ') + str(face_x))\n if face_x:\n if face_x < -10:\n get_bot().left(speed=80, elapse=0.1)\n elif face_x > 10:\n get_bot().right(speed=80, elapse=0.1)\n else:\n get_bot().stop()\n", "name": "test_find_face"} \ No newline at end of file +{"id": "504eb5f8-650f-4385-8cb4-d30ea09c9ef7", "dom_code": "faceface_xface_sizeWHILETRUEfaceALLface_xGETFROM_STARTface1face_sizeGETFROM_STARTface2face: face_xface_xLTface_x-10LEFT800.1GTface_x10RIGHT800.1", "code": "face = None\nface_x = None\nface_size = None\n\n\nwhile True:\n get_prog_eng().check_end()\n face = get_cam().find_face()\n face_x = face[0]\n face_size = face[1]\n get_cam().set_text(str('face: ') + str(face_x))\n if face_x:\n if face_x < -10:\n get_bot().left(speed=80, elapse=0.1)\n elif face_x > 10:\n get_bot().right(speed=80, elapse=0.1)\n else:\n get_bot().stop()\n", "name": "test_find_face"} \ No newline at end of file diff --git a/defaults/programs/program_test_find_path_ahead.json b/defaults/programs/program_test_find_path_ahead.json index 703325e2..8875ed39 100644 --- a/defaults/programs/program_test_find_path_ahead.json +++ b/defaults/programs/program_test_find_path_ahead.json @@ -1 +1 @@ -{"dom_code": "spazio_liberoWHILETRUEspazio_liberospazio_liberoANDGTspazio_libero30NEQspazio_libero60FORWARD1000.2RIGHT1000.2", "code": "spazio_libero = None\n\n\nwhile True:\n get_prog_eng().check_end()\n spazio_libero = get_cam().path_ahead()\n get_cam().set_text(spazio_libero)\n if spazio_libero > 30 and spazio_libero != 60:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_bot().right(speed=100, elapse=0.2)\n", "name": "test_find_path_ahead"} \ No newline at end of file +{"id": "ced77927-df6b-475a-b41e-d36f3976a2a0", "dom_code": "spazio_liberoWHILETRUEspazio_liberospazio_liberoANDGTspazio_libero30NEQspazio_libero60FORWARD1000.2RIGHT1000.2", "code": "spazio_libero = None\n\n\nwhile True:\n get_prog_eng().check_end()\n spazio_libero = get_cam().path_ahead()\n get_cam().set_text(spazio_libero)\n if spazio_libero > 30 and spazio_libero != 60:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_bot().right(speed=100, elapse=0.2)\n", "name": "test_find_path_ahead"} \ No newline at end of file diff --git a/defaults/programs/program_test_img_average.json b/defaults/programs/program_test_img_average.json index 0112e6cb..08e30d5b 100644 --- a/defaults/programs/program_test_img_average.json +++ b/defaults/programs/program_test_img_average.json @@ -1 +1 @@ -{"dom_code": "WHILETRUEV", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().get_average()[2])\n", "name": "test_img_average"} \ No newline at end of file +{"id": "4c448688-2d4f-424c-ac95-85417f30a447", "dom_code": "WHILETRUEV", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().get_average()[2])\n", "name": "test_img_average"} \ No newline at end of file diff --git a/defaults/programs/program_test_input.json b/defaults/programs/program_test_input.json index 781dc25a..15fa7f9f 100644 --- a/defaults/programs/program_test_input.json +++ b/defaults/programs/program_test_input.json @@ -1 +1 @@ -{"name": "test_input", "dom_code": "WHILETRUEanalog 1: 0 analog 2: 1 digital 1: 2", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(''.join([str(x) for x in ['analog 1: ', get_atmega().get_input(0), ' analog 2: ', get_atmega().get_input(1), ' digital 1: ', get_atmega().get_input(2)]]))\n"} \ No newline at end of file +{"id": "c4cec8dc-f0e9-4aa0-ba2d-e8eb368a0f88", "name": "test_input", "dom_code": "WHILETRUEanalog 1: 0 analog 2: 1 digital 1: 2", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(''.join([str(x) for x in ['analog 1: ', get_atmega().get_input(0), ' analog 2: ', get_atmega().get_input(1), ' digital 1: ', get_atmega().get_input(2)]]))\n"} \ No newline at end of file diff --git a/defaults/programs/program_test_led.json b/defaults/programs/program_test_led.json index 7246e80d..58f1534c 100644 --- a/defaults/programs/program_test_led.json +++ b/defaults/programs/program_test_led.json @@ -1 +1 @@ -{"name": "test_led", "dom_code": "ledsicleds6031leds000i1leds11MINUSi1000ADDi5leds000c11005iADDi5MULTIPLYc0MULTIPLYc0MULTIPLYc31leds000", "code": "leds = None\ni = None\nc = None\n\ndef upRange(start, stop, step):\n while start <= stop:\n yield start\n start += abs(step)\n\ndef downRange(start, stop, step):\n while start >= stop:\n yield start\n start -= abs(step)\n\n\nleds = 60\nfor count in range(3):\n get_prog_eng().check_end()\n get_atmega().set_led(1, leds, 0, 0, 0)\n for i in (1 <= float(leds)) and upRange(1, float(leds), 1) or downRange(1, float(leds), 1):\n get_prog_eng().check_end()\n get_atmega().set_led(1, i - 1, 0, 0, 0)\n get_atmega().set_led(i + 5, leds, 0, 0, 0)\n for c in range(1, 101, 5):\n get_prog_eng().check_end()\n get_atmega().set_led(i, i + 5, c * 0, c * 0, c * 3)\n get_atmega().set_led(1, leds, 0, 0, 0)\n"} \ No newline at end of file +{"id": "4bf5a8a8-eb06-44aa-8450-e73b62e11f8c", "name": "test_led", "dom_code": "ledsicleds6031leds000i1leds11MINUSi1000ADDi5leds000c11005iADDi5MULTIPLYc0MULTIPLYc0MULTIPLYc31leds000", "code": "leds = None\ni = None\nc = None\n\ndef upRange(start, stop, step):\n while start <= stop:\n yield start\n start += abs(step)\n\ndef downRange(start, stop, step):\n while start >= stop:\n yield start\n start -= abs(step)\n\n\nleds = 60\nfor count in range(3):\n get_prog_eng().check_end()\n get_atmega().set_led(1, leds, 0, 0, 0)\n for i in (1 <= float(leds)) and upRange(1, float(leds), 1) or downRange(1, float(leds), 1):\n get_prog_eng().check_end()\n get_atmega().set_led(1, i - 1, 0, 0, 0)\n get_atmega().set_led(i + 5, leds, 0, 0, 0)\n for c in range(1, 101, 5):\n get_prog_eng().check_end()\n get_atmega().set_led(i, i + 5, c * 0, c * 0, c * 3)\n get_atmega().set_led(1, leds, 0, 0, 0)\n"} \ No newline at end of file diff --git a/defaults/programs/program_test_music.json b/defaults/programs/program_test_music.json index a3d0f3fd..053a21a2 100644 --- a/defaults/programs/program_test_music.json +++ b/defaults/programs/program_test_music.json @@ -1 +1 @@ -{"name": "test_music", "dom_code": "C2nonedog1D2nonecat1E2nonepig1F2noneelephant1G2nonesnake1A2noneduck1B2nonecat1", "code": "get_music().play_note(note=\"C2\", alteration=\"none\" ,instrument=\"dog\" ,duration=1)\nget_music().play_note(note=\"D2\", alteration=\"none\" ,instrument=\"cat\" ,duration=1)\nget_music().play_note(note=\"E2\", alteration=\"none\" ,instrument=\"pig\" ,duration=1)\nget_music().play_note(note=\"F2\", alteration=\"none\" ,instrument=\"elephant\" ,duration=1)\nget_music().play_note(note=\"G2\", alteration=\"none\" ,instrument=\"snake\" ,duration=1)\nget_music().play_note(note=\"A2\", alteration=\"none\" ,instrument=\"duck\" ,duration=1)\nget_music().play_note(note=\"B2\", alteration=\"none\" ,instrument=\"cat\" ,duration=1)\n"} \ No newline at end of file +{"id": "8281a5f5-f9a4-4128-bd3d-bef41fb2c30b", "name": "test_music", "dom_code": "C2nonedog1D2nonecat1E2nonepig1F2noneelephant1G2nonesnake1A2noneduck1B2nonecat1", "code": "get_music().play_note(note=\"C2\", alteration=\"none\" ,instrument=\"dog\" ,duration=1)\nget_music().play_note(note=\"D2\", alteration=\"none\" ,instrument=\"cat\" ,duration=1)\nget_music().play_note(note=\"E2\", alteration=\"none\" ,instrument=\"pig\" ,duration=1)\nget_music().play_note(note=\"F2\", alteration=\"none\" ,instrument=\"elephant\" ,duration=1)\nget_music().play_note(note=\"G2\", alteration=\"none\" ,instrument=\"snake\" ,duration=1)\nget_music().play_note(note=\"A2\", alteration=\"none\" ,instrument=\"duck\" ,duration=1)\nget_music().play_note(note=\"B2\", alteration=\"none\" ,instrument=\"cat\" ,duration=1)\n"} \ No newline at end of file diff --git a/defaults/programs/program_test_output.json b/defaults/programs/program_test_output.json index dbd31e04..1fcb8a4a 100644 --- a/defaults/programs/program_test_output.json +++ b/defaults/programs/program_test_output.json @@ -1 +1 @@ -{"name": "test_output", "dom_code": "WHILETRUE0TRUE0.11TRUE0.12TRUE0.10FALSE0.11FALSE0.12FALSE0.1", "code": "while True:\n get_prog_eng().check_end()\n get_atmega().set_output(0, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(1, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(2, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(0, False)\n get_bot().sleep(0.1)\n get_atmega().set_output(1, False)\n get_bot().sleep(0.1)\n get_atmega().set_output(2, False)\n get_bot().sleep(0.1)\n"} \ No newline at end of file +{"id": "604ca7a1-f2e2-45f0-92b2-afd3c621921f", "name": "test_output", "dom_code": "WHILETRUE0TRUE0.11TRUE0.12TRUE0.10FALSE0.11FALSE0.12FALSE0.1", "code": "while True:\n get_prog_eng().check_end()\n get_atmega().set_output(0, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(1, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(2, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(0, False)\n get_bot().sleep(0.1)\n get_atmega().set_output(1, False)\n get_bot().sleep(0.1)\n get_atmega().set_output(2, False)\n get_bot().sleep(0.1)\n"} \ No newline at end of file diff --git a/defaults/programs/program_test_sonars.json b/defaults/programs/program_test_sonars.json index 6664424a..7c58bec3 100644 --- a/defaults/programs/program_test_sonars.json +++ b/defaults/programs/program_test_sonars.json @@ -1 +1 @@ -{"name": "test_sonars", "dom_code": "WHILETRUEFront: 0 Right: 1 Left: 2", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(''.join([str(x) for x in ['Front: ', get_bot().get_sonar_distance(0), ' Right: ', get_bot().get_sonar_distance(1), ' Left: ', get_bot().get_sonar_distance(2)]]))\n"} \ No newline at end of file +{"id": "ddab8e33-eba7-4f02-93af-823c523d63de", "name": "test_sonars", "dom_code": "WHILETRUEFront: 0 Right: 1 Left: 2", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(''.join([str(x) for x in ['Front: ', get_bot().get_sonar_distance(0), ' Right: ', get_bot().get_sonar_distance(1), ' Left: ', get_bot().get_sonar_distance(2)]]))\n"} \ No newline at end of file diff --git a/defaults/programs/program_test_sound_hear.json b/defaults/programs/program_test_sound_hear.json index 6d9026d6..e08fe560 100644 --- a/defaults/programs/program_test_sound_hear.json +++ b/defaults/programs/program_test_sound_hear.json @@ -1 +1 @@ -{"dom_code": "WHILETRUE1001.0", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_audio().hear(level=100, elapse=1))\n", "name": "test_sound_hear"} \ No newline at end of file +{"id": "458186a6-1285-4da2-b447-5db266b89c45", "dom_code": "WHILETRUE1001.0", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_audio().hear(level=100, elapse=1))\n", "name": "test_sound_hear"} \ No newline at end of file diff --git a/defaults/programs/program_test_sound_rec.json b/defaults/programs/program_test_sound_rec.json index d6740905..9efd1c1f 100644 --- a/defaults/programs/program_test_sound_rec.json +++ b/defaults/programs/program_test_sound_rec.json @@ -1 +1 @@ -{"dom_code": "test01.wav5", "code": "get_audio().record_to_file(filename='test01.wav', elapse=5)\n", "name": "test_sound_rec"} \ No newline at end of file +{"id": "522b2754-65ba-4fa7-8264-e15e819c587b", "dom_code": "test01.wav5", "code": "get_audio().record_to_file(filename='test01.wav', elapse=5)\n", "name": "test_sound_rec"} \ No newline at end of file From f561d460154776956df5f8b386ef5b434a24b8f8 Mon Sep 17 00:00:00 2001 From: previ Date: Thu, 28 Dec 2023 22:34:56 +0000 Subject: [PATCH 53/60] add uuid --- coderbot/activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index b7e8ab73..c275154b 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -50,7 +50,7 @@ def save(self, activity): if activity.get("default", False) is True: self.activities.update({'default': False}) if self.activities.search(self.query.id == activity.get("id")) == []: - activity = self.activities.insert(activity) + self.activities.insert(activity) else: self.activities.update(activity, self.query.id == activity.get("id")) activity = self.activities.search(self.query.id == activity.get("id"))[0] From 5880e8794e2bf0ed9a83ddf023c2b4f157094fca Mon Sep 17 00:00:00 2001 From: previ Date: Thu, 28 Dec 2023 22:39:05 +0000 Subject: [PATCH 54/60] add uuid --- coderbot/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/coderbot/api.py b/coderbot/api.py index 9b381f6c..8569f869 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -326,7 +326,10 @@ def runProgram(id): """ logging.debug("program_exec") prog = prog_engine.load(id) - return prog.execute() + if prog is not None: + return prog.execute() + else: + return {}, 404 def stopProgram(id): """ From 2b1be1954497dbb2f0816936ce7e2327d0d24a8e Mon Sep 17 00:00:00 2001 From: previ Date: Thu, 28 Dec 2023 23:53:27 +0000 Subject: [PATCH 55/60] add uuid --- coderbot/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderbot/api.py b/coderbot/api.py index 8569f869..a0b900d8 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -294,6 +294,8 @@ def saveProgram(id, body): overwrite = body.get("overwrite") name = body["name"] existing_program = prog_engine.load(id) + if existing_program is None: + return {}, 404 logging.info("saving - id: %s - name: %s - existing: %s", id, name, str(existing_program is not None)) if existing_program is not None and existing_program.is_stock() == True: return "defaultCannotOverwrite", 400 From ca68f8f3a9b5c1585915c408be6a03ff4c097aaf Mon Sep 17 00:00:00 2001 From: previ Date: Sat, 30 Dec 2023 23:05:51 +0000 Subject: [PATCH 56/60] fix sync activity, program --- coderbot/activity.py | 2 +- coderbot/cloud/__init__.py | 55 +++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/coderbot/activity.py b/coderbot/activity.py index c275154b..79c87a81 100644 --- a/coderbot/activity.py +++ b/coderbot/activity.py @@ -54,7 +54,7 @@ def save(self, activity): else: self.activities.update(activity, self.query.id == activity.get("id")) activity = self.activities.search(self.query.id == activity.get("id"))[0] - logging.info("updating/creating activity: %s", str(activity)) + logging.info("updating/creating activity - id: %s, name: %s", str(activity.get("id")), str(activity.get("name"))) return activity def delete(self, id, logical = True): diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/__init__.py index 7d767859..db507018 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/__init__.py @@ -152,7 +152,7 @@ def sync(self): # Create an instance of the API class api_instance = robot_sync_api.RobotSyncApi(api_client) - self.sync_settings(api_instance, sync_modes["settings"]) + #self.sync_settings(api_instance, sync_modes["settings"]) self.sync_activities(api_instance, sync_modes["activities"]) self.sync_programs(api_instance, sync_modes["programs"]) except Exception as e: @@ -223,7 +223,7 @@ def sync_settings(self, api_instance, sync_mode): logging.info("settings.downstream") self._sync_status["settings"] = "synced" except cloud_api_robot_client.ApiException as e: - logging.warn("Exception when calling settings RobotSyncApi: %s\n" % e) + logging.warn("Exception when calling settings RobotSyncApi: %s\n", e) self._sync_status["registration"] = "failed" def sync_activities(self, api_instance, sync_mode): @@ -251,14 +251,16 @@ def sync_activities(self, api_instance, sync_mode): # cloud activities activities_cloud_map = {} for a in cloud_activities: - if a.get("status") == ACTIVITY_STATUS_ACTIVE: + logging.info("cloud_activities %s, %s", str(a.get("status")), a.get("id")) + if a.get("status") == ACTIVITY_STATUS_ACTIVE: activities_cloud_map[a.get("id")] = a # loop through local for al in activities_local_user: logging.info("activities.syncing: " + str(al.get("id")) + " name: " + str(al.get("name"))) ac = activities_cloud_map.get(al.get("id")) - ac_al_equals = (ac is not None and ac.get("data") == al.get("data")) + ac_al_equals = (ac is not None and ac.get("id") == al.get("id")) + if ac is not None and not ac_al_equals: al["modified"] = al.get("modified", datetime.now(tz=timezone.utc).isoformat()) local_activity_more_recent = datetime.fromisoformat(ac.get("modified")).timestamp() < datetime.fromisoformat(al.get("modified")).timestamp() @@ -285,7 +287,7 @@ def sync_activities(self, api_instance, sync_mode): logging.info("activities.update.downstream: " + al.get("id")) elif ac is None and sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: body = Activity( - id="", + id=al.get("id"), org_id="", name=al.get("name"), description=al.get("description"), @@ -294,13 +296,14 @@ def sync_activities(self, api_instance, sync_mode): modified=al.get("modified", datetime.now(tz=timezone.utc).isoformat()), status="active", ) + logging.info("activities.create.upstream - id %s, name: %s", al.get("id"), al.get("name")) api_response = api_instance.create_robot_activity(body=body) al["id"] = api_response.body["id"] al["org_id"] = api_response.body["org_id"] Activities.get_instance().save(al) - logging.info("activities.create.upstream: " + al.get("name")) + #logging.info("activities.create.upstream: " + al.get("name")) elif ac is None and sync_mode in [SYNC_DOWNSTREAM]: - Activities.get_instance().delete(al.get("name")) + Activities.get_instance().delete(al.get("id")) logging.info("activities.delete.downstream: " + al.get("name")) for k, ac in activities_cloud_map.items(): @@ -319,17 +322,17 @@ def sync_activities(self, api_instance, sync_mode): for al in activities_local_to_be_deleted: if al.get("id") is not None: logging.info("activities.delete.upstream: " + al.get("name")) - api_response = api_instance.delete_robot_program(path_params={"activity_id":al.get("id")}) + api_response = api_instance.delete_robot_activity(path_params={"activity_id":al.get("id")}) # delete locally permanently Activities.get_instance().delete(al.get("id"), logical=False) # manage local stock activities to be deleted locally - for al in activities_local_stock: - # logging.info("activities.check.stock.locally: " + al.get("name") + " id: " + str(al.get("id"))) - if al.get("id") is not None and activities_cloud_map.get(al.get("id")) is None: - logging.info("activities.delete.stock.locally: " + al.get("name")) - # delete locally permanently - Activities.get_instance().delete(al.get("id"), logical=False) + # for al in activities_local_stock: + # # logging.info("activities.check.stock.locally: " + al.get("name") + " id: " + str(al.get("id"))) + # if al.get("id") is not None and activities_cloud_map.get(al.get("id")) is None: + # logging.info("activities.delete.stock.locally: " + al.get("name")) + # # delete locally permanently + # Activities.get_instance().delete(al.get("id"), logical=False) self._sync_status["activities"] = "synced" except cloud_api_robot_client.ApiException as e: @@ -370,11 +373,7 @@ def sync_programs(self, api_instance, sync_mode): for pl in programs_local_user: pc = programs_cloud_map.get(pl.get("id")) pc_pl_equals = (pc is not None and - pc.get("id") == pl.get("id") and - pc.get("name") == pl.get("name") and - pc.get("code") == pl.get("code") and - pc.get("dom_code") == pl.get("dom_code") and - pc.get("status") == pl.get("status")) + pc.get("id") == pl.get("id")) logging.info("programs.syncing: " + str(pl.get("id")) + " name: " + pl.get("name")) if pc is not None and not pc_pl_equals: @@ -408,7 +407,7 @@ def sync_programs(self, api_instance, sync_mode): elif pc is None and sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: # cloud program does not exist body = Program( - id="", + id=pl.get("id"), org_id="", name=pl.get("name"), description=pl.get("description", ""), @@ -425,7 +424,7 @@ def sync_programs(self, api_instance, sync_mode): logging.info("programs.create.upstream: " + pl.get("name")) elif pc is None and sync_mode in [SYNC_DOWNSTREAM]: # cloud program does not exist, delete locally since sync_mode is downstream - ProgramEngine.get_instance().delete(pl.get("name")) + ProgramEngine.get_instance().delete(pl.get("id")) logging.info("programs.delete.downstream: " + pl.get("name")) # manage cloud programs not present locally in "active" status @@ -447,15 +446,15 @@ def sync_programs(self, api_instance, sync_mode): logging.info("programs.delete.upstream: " + pl.get("name")) api_response = api_instance.delete_robot_program(path_params={"program_id":pl.get("id")}) # delete locally permanently - ProgramEngine.get_instance().delete(pl.get("name"), logical=False) + ProgramEngine.get_instance().delete(pl.get("id"), logical=False) # manage local stock programs to be deleted locally - for pl in programs_local_stock: - # logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) - if pl.get("id") is not None and programs_cloud_map.get(pl.get("id")) is None: - logging.info("programs.delete.stock.locally: " + pl.get("name")) - # delete locally permanently - ProgramEngine.get_instance().delete(pl.get("name"), logical=False) + # for pl in programs_local_stock: + # # logging.info("programs.check.stock.locally: " + pl.get("name") + " id: " + str(pl.get("id"))) + # if pl.get("id") is not None and programs_cloud_map.get(pl.get("id")) is None: + # logging.info("programs.delete.stock.locally: " + pl.get("name")) + # # delete locally permanently + # ProgramEngine.get_instance().delete(pl.get("name"), logical=False) self._sync_status["programs"] = "synced" except cloud_api_robot_client.ApiException as e: logging.warn("Exception when calling programs RobotSyncApi: %s\n" % e) From e0582ace78aecf6f978ef754ac41f05682be00ab Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 31 Dec 2023 15:00:47 +0000 Subject: [PATCH 57/60] fix daveProgram case of null body --- coderbot/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderbot/api.py b/coderbot/api.py index a0b900d8..176265cc 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -292,7 +292,7 @@ def saveNewProgram(body): def saveProgram(id, body): overwrite = body.get("overwrite") - name = body["name"] + name = body.get("name", None) existing_program = prog_engine.load(id) if existing_program is None: return {}, 404 From e214e5993401a9ef10404e5682f16f78e673b433 Mon Sep 17 00:00:00 2001 From: previ Date: Sun, 31 Dec 2023 22:26:37 +0000 Subject: [PATCH 58/60] sync settings --- coderbot/api.py | 2 +- coderbot/cloud/{__init__.py => sync.py} | 17 ++++++++--------- coderbot/main.py | 6 +++++- coderbot/program.py | 4 ++-- requirements.txt | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) rename coderbot/cloud/{__init__.py => sync.py} (96%) diff --git a/coderbot/api.py b/coderbot/api.py index 176265cc..c150f44e 100644 --- a/coderbot/api.py +++ b/coderbot/api.py @@ -23,7 +23,7 @@ from musicPackages import MusicPackageManager from program import Program, ProgramEngine from motion import Motion -from cloud import CloudManager +from cloud.sync import CloudManager from balena import Balena from coderbot import CoderBot diff --git a/coderbot/cloud/__init__.py b/coderbot/cloud/sync.py similarity index 96% rename from coderbot/cloud/__init__.py rename to coderbot/cloud/sync.py index db507018..93ed8779 100644 --- a/coderbot/cloud/__init__.py +++ b/coderbot/cloud/sync.py @@ -122,7 +122,7 @@ def registration_status(self): def run(self): while(True): - sync_period = int(Config.read().get("sync_period", "60")) + sync_period = int(Config.read().get("cloud").get("sync_period", "60")) self.sync() sleep(sync_period) @@ -152,11 +152,12 @@ def sync(self): # Create an instance of the API class api_instance = robot_sync_api.RobotSyncApi(api_client) - #self.sync_settings(api_instance, sync_modes["settings"]) + self.sync_settings(api_instance, sync_modes["settings"]) self.sync_activities(api_instance, sync_modes["activities"]) self.sync_programs(api_instance, sync_modes["programs"]) except Exception as e: logging.warn("run.sync.api_not_available: " + str(e)) + raise e logging.info("run.sync.end") self._syncing = False @@ -193,32 +194,30 @@ def sync_settings(self, api_instance, sync_mode): api_response = api_instance.get_robot_setting() cloud_setting_object = api_response.body cloud_setting = json.loads(cloud_setting_object.get('data')) - # sync only the "settings" and "cloud" sections, do not sync "network" config = Config.read() local_setting = { - "settings": config.get("settings"), - #"cloud": config.get("cloud") + "settings": config.get("settings") } local_most_recent = datetime.fromisoformat(cloud_setting_object.get("modified")).timestamp() < Config.modified() cloud_kind_user = cloud_setting_object.get("kind") == ENTITY_KIND_USER + # logging.info(f"cloud_kind_user: {cloud_kind_user}, cloud_setting != local_setting: {cloud_setting != local_setting}, local_most_recent: {local_most_recent}, sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: {sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]}") # logging.info("settings.syncing: " + cloud_setting_object.get("id", "") + " name: " + cloud_setting_object.get("name", "")) - if cloud_kind_user and cloud_setting != local_setting and local_most_recent: + if cloud_kind_user and cloud_setting != local_setting and local_most_recent and sync_mode in [SYNC_UPSTREAM, SYNC_BIDIRECTIONAL]: body = Setting( id = cloud_setting_object.get('id'), org_id = cloud_setting_object.get('org_id'), name = cloud_setting_object.get('name'), description = cloud_setting_object.get('description'), data = json.dumps(local_setting), - kind = local_setting.get("kind", ENTITY_KIND_STOCK), + kind = cloud_setting_object.get("kind"), modified = datetime.now().isoformat(), status = cloud_setting_object.get('status'), ) api_response = api_instance.set_robot_setting(body) logging.info("settings.upstream") - elif cloud_setting != local_setting: # setting, down + elif cloud_setting != local_setting and sync_mode in [SYNC_DOWNSTREAM, SYNC_BIDIRECTIONAL]: # setting, down config["settings"] = cloud_setting["settings"] - #config["cloud"] = cloud_setting["cloud"] Config.write(config) logging.info("settings.downstream") self._sync_status["settings"] = "synced" diff --git a/coderbot/main.py b/coderbot/main.py index 9a715aa6..fb001fb4 100644 --- a/coderbot/main.py +++ b/coderbot/main.py @@ -19,7 +19,7 @@ from cnn.cnn_manager import CNNManager from event import EventManager from coderbot import CoderBot -from cloud import CloudManager +from cloud.sync import CloudManager # Logging configuration logger = logging.getLogger() @@ -66,6 +66,10 @@ def run_server(): try: try: settings = Config.read().get("settings") + # if settings.get("id") is None: + # settings["id"] = str(uuid.uuid4()) # init uuid for local settings + # Config.write() + app.settings = settings network_settings = Config.read().get("network") diff --git a/coderbot/program.py b/coderbot/program.py index d78e9d4d..0cf275ff 100644 --- a/coderbot/program.py +++ b/coderbot/program.py @@ -192,9 +192,9 @@ class Program: def dom_code(self): return self._dom_code - def __init__(self, name, id=str(uuid.uuid4()), description=None, code=None, dom_code=None, kind=PROGRAM_KIND_USER, modified=None, status=None): + def __init__(self, name, id=None, description=None, code=None, dom_code=None, kind=PROGRAM_KIND_USER, modified=None, status=None): self._thread = None - self._id = id + self._id = id if id is not None else str(uuid.uuid4()) self._name = name self._description = description self._dom_code = dom_code diff --git a/requirements.txt b/requirements.txt index dc2210a3..fe28f3bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ numpy==1.24.3 Pillow==10.1.0 protobuf==4.25.1 opencv-contrib-python==4.5.5.62 -tflite-runtime==2.12.0 +tflite-runtime==2.13.0 pytesseract==0.3.10 picamera==1.13 pyzbar==0.1.9 From 93735f79af9fead604dcf3b3f59331d1a15b9182 Mon Sep 17 00:00:00 2001 From: previ Date: Wed, 28 Feb 2024 23:13:31 +0100 Subject: [PATCH 59/60] fix detectMultiScale --- coderbot/cv/image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coderbot/cv/image.py b/coderbot/cv/image.py index 24dd9efb..e9c6f12e 100644 --- a/coderbot/cv/image.py +++ b/coderbot/cv/image.py @@ -39,8 +39,7 @@ class Image(): _aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_ARUCO_ORIGINAL) _aruco_parameters = cv2.aruco.DetectorParameters_create() - #_face_cascade = cv2.CascadeClassifier('/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml') - _face_cascade = cv2.CascadeClassifier('/usr/share/opencv/lbpcascades/lbpcascade_frontalface.xml') + _face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') def __init__(self, array): self._data = array @@ -85,7 +84,8 @@ def get_transform(cls, image_size_x): return tx def find_faces(self): - faces = self._face_cascade.detectMultiScale(self._data) + gray = cv2.cvtColor(self._data, cv2.COLOR_BGR2GRAY) + faces = self._face_cascade.detectMultiScale(gray) return faces def filter_color(self, color): From 13e081aab3878921fd0074328e0b181ee2f4e4a1 Mon Sep 17 00:00:00 2001 From: previ Date: Thu, 28 Mar 2024 23:02:03 +0100 Subject: [PATCH 60/60] schemathesis==3.24.3 --- .github/workflows/build_backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_backend.yml b/.github/workflows/build_backend.yml index 41ea5341..e78c948a 100644 --- a/.github/workflows/build_backend.yml +++ b/.github/workflows/build_backend.yml @@ -34,7 +34,7 @@ jobs: mkdir -p schemathesis python3 -m venv schemathesis . schemathesis/bin/activate - pip install schemathesis + pip install schemathesis==3.24.3 st run --endpoint 'activities' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json #st run --endpoint 'media' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json st run --endpoint 'control/speak' --hypothesis-max-examples=10 --request-timeout=20 http://localhost:5000/api/v1/openapi.json pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy