diff --git a/.circleci/config.yml b/.circleci/config.yml index 3d191acd..76de3154 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ jobs: build: docker: # specify the version you desire here - - image: coderbot/python-gpac:3.5-tf2 + - image: coderbot/coderbot-ci:3.9-bullseye-ffmpeg working_directory: ~/repo diff --git a/.gitignore b/.gitignore index ed0fd582..d59dc0e5 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,6 @@ Thumbs.db # Swap files *.swp - # Python3 Virtual Environment folders bin/ @@ -94,3 +93,7 @@ photos/metadata.json # Uploaded updates folder updatePackages/ + +# firmware +firmware/ + diff --git a/README.md b/README.md index 9f21cece..b618a798 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # backend +[![CoderBotOrg](https://circleci.com/gh/CoderBotOrg/backend.svg?style=svg)](https://circleci.com/gh/CoderBotOrg/backend/tree/master) > CoderBot is a RaspberryPI-based programmable robot for educational purposes. Check the [project website](https://www.coderbot.org) for more information. > diff --git a/activity.py b/activity.py new file mode 100644 index 00000000..7c46ac88 --- /dev/null +++ b/activity.py @@ -0,0 +1,45 @@ +from tinydb import TinyDB, Query +from tinydb.operations import delete +import json + +# Programs and Activities databases +class Activities(): + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance == None: + cls._instance = Activities() + return cls._instance + + def __init__(self): + self.activities = TinyDB("data/activities.json") + self.query = Query() + + def load(self, name, default): + if name: + return self.activities.search(self.query.name == name)[0] + elif default is not None: + default_Activities = self.activities.search(self.query.default == True) + if len(self.activities.search(self.query.default == True)) > 0: + return self.activities.search(self.query.default == True)[0] + else: + return None + + def save(self, activity): + if self.activities.search(self.query.name == activity["name"]) == []: + self.activities.insert(activity) + else: + if activity.get("default", False) == True: + self.activities.update({'default': False}) + self.activities.update(activity, self.query.name == activity["name"]) + + def delete(self, activity): + activity = self.activities.search(self.query.name == activity["name"])[0] + if activity.get("default", False) == True: + self.activities.update({'default': True}, self.query.stock == True) + self.activities.remove(self.query.name == activity["name"]) + + def list(self): + return self.activities.all() + diff --git a/api.py b/api.py index 08c22c1d..c03cf5b0 100644 --- a/api.py +++ b/api.py @@ -6,15 +6,17 @@ import os import subprocess import json +import logging import connexion -from tinydb import TinyDB, Query -from tinydb.operations import delete +import pigpio from cachetools import cached, TTLCache from coderbot import CoderBot from program import ProgramEngine, Program from config import Config +from activity import Activities from coderbotTestUnit import run_test as runCoderbotTestUnit -import pigpio +from cnn_manager import CNNManager +from musicPackages import MusicPackageManager BUTTON_PIN = 16 @@ -24,8 +26,6 @@ encoder=bool(bot_config.get("encoder")) ) -query = Query() - def get_serial(): """ Extract serial from cpuinfo file @@ -107,8 +107,7 @@ def get_info(): prog = None prog_engine = ProgramEngine.get_instance() -# Programs and Activities databases -activities = TinyDB("data/activities.json") +activities = Activities.get_instance() ## Robot control @@ -132,7 +131,8 @@ def turn(data): def exec(data): program = prog_engine.create(data["name"], data["code"]) - return json.dumps(program.execute()) + options = data["options"] + return json.dumps(program.execute(options)) ## System @@ -172,6 +172,7 @@ def restoreSettings(): Config.get() return "ok" + def updateFromPackage(): os.system('sudo bash /home/pi/clean-update.sh') file_to_upload = connexion.request.files['file_to_upload'] @@ -179,11 +180,47 @@ def updateFromPackage(): os.system('sudo reboot') return 200 +def listMusicPackages(): + """ + list available music packages + """ + musicPkg = MusicPackageManager.get_instance() + response = musicPkg.listPackages() + return json.dumps(response) +def updateMusicPackages(): + """ + Add a musical package an save the list of available packages on disk + also add sounds and directory + """ + """zipName = request.args.get("zipname") + """ + file_to_upload = connexion.request.files['file_to_upload'] + print("adding " +str(file_to_upload)) + print("adding " + file_to_upload.filename) + file_to_upload.save(os.path.join('./updatePackages/', file_to_upload.filename)) + musicPkg = MusicPackageManager.get_instance() + response = musicPkg.addPackage(file_to_upload.filename) + if response == 1: + return 200 + elif response == 2: + return 400 + elif response == 3: + return 400 + +def deleteMusicPackage(package_data): + """ + Delete a musical package an save the list of available packages on disk + also delete package sounds and directory + """ + musicPkg = MusicPackageManager.get_instance() + musicPkg.deletePackage(package_data['package_name']) + return 200 ## Programs -def saveProgram(data, overwrite): +def saveProgram(data): + overwrite = data["overwrite"] existing_program = prog_engine.load(data["name"]) if existing_program and not overwrite: return "askOverwrite" @@ -207,23 +244,17 @@ def listPrograms(): ## Activities def saveActivity(data): - data = data["activity"] - if activities.search(query.name == data["name"]) == []: - activities.insert(data) - return 200 - else: - activities.update(data, query.name == data["name"]) - return 200 + activity = data["activity"] + activities.save(activity) -def loadActivity(name): - return activities.search(query.name == name)[0], 200 +def loadActivity(name=None, default=None): + return activities.load(name, default) def deleteActivity(data): - activities.remove(query.name == data["name"]) - + activities.delete(data), 200 def listActivities(): - return activities.all() + return activities.list() def resetDefaultPrograms(): """ @@ -252,4 +283,10 @@ def reset(): def testCoderbot(data): # taking first JSON key value (varargin) tests_state = runCoderbotTestUnit(data[list(data.keys())[0]]) - return tests_state \ No newline at end of file + return tests_state + +def list_cnn_models(): + cnn = CNNManager.get_instance() + logging.info("cnn_models_list") + return json.dumps(cnn.get_models()) + diff --git a/atmega328p.py b/atmega328p.py new file mode 100644 index 00000000..76c3965b --- /dev/null +++ b/atmega328p.py @@ -0,0 +1,84 @@ +# RPi PINOUTS +# MOSI -> GPIO10 +# MISO -> GPIO9 +# SCK -> GPIO11 +# CE1 -> GPIO7 +# CE1 -> GPIO8 + +# get the GPIO Library and SPI Library +import spidev +import time + +BAUDRATE_MAX = 250000 +BAUDRATE = 10000 + +START = 0xff +CMD_RESET = 0x00 +CMD_SET_DATA = 0x01 +CMD_GET_DATA = 0x02 +CMD_SET_MODE = 0x03 +CMD_SET_LED = 0x04 + +ADDR_AI_FIRST = 0x00 +ADDR_AI_LAST = 0x01 +ADDR_DI_FIRST = 0x02 +ADDR_DI_LAST = 0x05 +ADDR_DO_FIRST = 0x00 +ADDR_DO_LAST = 0x0a + +class ATMega328(): + + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = ATMega328() + return cls._instance + + def __init__(self): + # Initialze the SPI + self.spi = spidev.SpiDev() + self.spi.open(0,0) + self.spi.max_speed_hz = BAUDRATE_MAX + + def close(self): + self.spi.close() + + def digitalWrite(self, addr, value): + resp = self.spi.xfer([START, CMD_SET_DATA, addr, value, 0], BAUDRATE) + + def digitalRead(self, addr): + resp = self.spi.xfer([START, CMD_GET_DATA, addr, 0, 0], BAUDRATE) + return resp[3] + + def analogRead(self, addr): + resp = self.spi.xfer([START, CMD_GET_DATA, addr, 0, 0], BAUDRATE) + return resp[3] + + def setLed(self, begin_led, end_led, red, green, blue): + resp = self.spi.xfer([START, CMD_SET_LED, + min(max(begin_led, 0), 60), + min(max(end_led, 0), 60), + min(max(red, 0), 254), + min(max(green, 0), 254), + min(max(blue, 0), 254)], BAUDRATE) + return resp[3] + + def set_led(self, begin_led, end_led, red, green, blue): + begin = begin_led - 1 + end = end_led - 1 + red = int(red * 255 / 100) + green = int(green * 255 / 100) + blue = int(blue * 255 / 100) + return self.setLed(begin, end, red, green, blue) + + def get_input(self, addr): + if addr >= ADDR_AI_FIRST and addr <= ADDR_AI_LAST: + return self.analogRead(addr) + elif addr >= ADDR_DI_FIRST and addr <= ADDR_DI_LAST: + return self.digitalRead(addr) + + def set_output(self, addr, value): + if addr >= ADDR_DO_FIRST and addr <= ADDR_DO_LAST: + self.digitalWrite(addr, value) diff --git a/audio.py b/audio.py index 1d7798c1..6841d651 100644 --- a/audio.py +++ b/audio.py @@ -35,7 +35,7 @@ # [END import_libraries] # Audio recording parameters -RATE = 16000 +RATE = 44100 CHUNK = int(RATE / 10) # 100ms FORMAT = pyaudio.paInt16 diff --git a/audioControls.py b/audioControls.py new file mode 100644 index 00000000..be87d899 --- /dev/null +++ b/audioControls.py @@ -0,0 +1,52 @@ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# MUSICAL EXTENTION for CoderBot +# This extention is develop by: +# Michele Carbonera - miki_992@hotmail.it - m.carbonera@campus.unimib.it - michele.carbonera@unimib.it +# Antonino Tramontana - a.tramontana1@campus.unimib.it +# Copyright (C) 2020 +############################################################################ + +import os +import alsaaudio + +class AudioCtrl: + mixer = None + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = AudioCtrl() + return cls._instance + + def __init__(self): + self.mixer = alsaaudio.Mixer('Headphone') + + def getVolume(self): + print(self.mixer.getvolume()) + + def setVolume(self,valueVolume): + self.mixer.setvolume(valueVolume) + +# if __name__ == "__main__": +# a = AudioCtrl() +# a.setVolume(20) diff --git a/cnn_classifier.py b/cnn_classifier.py index 0b80453f..d8e61ce8 100644 --- a/cnn_classifier.py +++ b/cnn_classifier.py @@ -23,7 +23,15 @@ import logging import numpy as np -from tensorflow.lite.python.interpreter import Interpreter +try: + from tensorflow.lite.python.interpreter import Interpreter +except: + logging.warning("tensorflow not available (for inference)") +try: + from tflite_runtime.interpreter import Interpreter +except: + logging.warning("tflite not available") + import cv2 logger = logging.getLogger(__name__) @@ -31,8 +39,7 @@ class CNNClassifier(object): def __init__(self, model_file, label_file): logger.info(model_file) - self._interpreter = Interpreter(model_path=model_file) - self._interpreter.set_num_threads(4) + self._interpreter = Interpreter(model_path=model_file, num_threads=4) self._interpreter.allocate_tensors() self._labels = self.load_labels(label_file) self._input_details = self._interpreter.get_input_details() diff --git a/cnn_manager.py b/cnn_manager.py index f8bfc7dd..0294c9b1 100644 --- a/cnn_manager.py +++ b/cnn_manager.py @@ -26,7 +26,11 @@ import json import threading -from cnn_train import CNNTrainer +try: + from cnn_train import CNNTrainer +except: + logging.warning("tensorflow not available (for training)") + from cnn_classifier import CNNClassifier MODEL_PATH = "./cnn_models" @@ -117,6 +121,7 @@ def load_model(self, model_name): return CNNClassifier(model_file=MODEL_PATH + "/" + model_name + ".tflite", label_file=MODEL_PATH + "/" + model_name + ".txt") return None + class TrainThread(threading.Thread): def __init__(self, manager, model_name, architecture, image_tags, photos_metadata, training_steps, learning_rate): diff --git a/coderbot-copy.py b/coderbot-copy.py deleted file mode 100644 index b7295dfa..00000000 --- a/coderbot-copy.py +++ /dev/null @@ -1,431 +0,0 @@ -############################################################################ -# CoderBot, a didactical programmable robot. -# Copyright (C) 2014, 2015 Roberto Previtera -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -############################################################################ - -import os -import time -import threading -import logging -import pigpio -import sonar -import mpu - -PIN_MOTOR_ENABLE = 22 -PIN_LEFT_FORWARD = 25 -PIN_LEFT_BACKWARD = 24 -PIN_RIGHT_FORWARD = 4 -PIN_RIGHT_BACKWARD = 17 -PIN_PUSHBUTTON = 11 -PIN_SERVO_3 = 9 -PIN_SERVO_4 = 10 -PIN_SONAR_1_TRIGGER = 18 -PIN_SONAR_1_ECHO = 7 -PIN_SONAR_2_TRIGGER = 18 -PIN_SONAR_2_ECHO = 8 -PIN_SONAR_3_TRIGGER = 18 -PIN_SONAR_3_ECHO = 23 -PIN_ENCODER_LEFT = 15 -PIN_ENCODER_RIGHT = 14 - -PWM_FREQUENCY = 100 #Hz -PWM_RANGE = 100 #0-100 - -class CoderBot(object): - - # pylint: disable=too-many-instance-attributes - - _pin_out = [PIN_MOTOR_ENABLE, PIN_LEFT_FORWARD, PIN_RIGHT_FORWARD, PIN_LEFT_BACKWARD, PIN_RIGHT_BACKWARD, PIN_SERVO_3, PIN_SERVO_4] - - def __init__(self, servo=False, motor_trim_factor=1.0, encoder=False): - self.pi = pigpio.pi('localhost') - self.pi.set_mode(PIN_PUSHBUTTON, pigpio.INPUT) - self._cb = dict() - self._cb_last_tick = dict() - self._cb_elapse = dict() - self._servo = servo - self._encoder = encoder - self._motor_trim_factor = motor_trim_factor - if self._servo: - self.motor_control = self._servo_motor - elif self._encoder: - self._twin_motors_enc = self.TwinMotorsEncoder( - self.pi, - pin_enable=PIN_MOTOR_ENABLE, - pin_forward_left=PIN_LEFT_FORWARD, - pin_backward_left=PIN_LEFT_BACKWARD, - pin_encoder_left=PIN_ENCODER_LEFT, - pin_forward_right=PIN_RIGHT_FORWARD, - pin_backward_right=PIN_RIGHT_BACKWARD, - pin_encoder_right=PIN_ENCODER_RIGHT) - self.motor_control = self._dc_enc_motor - else: - self.motor_control = self._dc_motor - - self._cb1 = self.pi.callback(PIN_PUSHBUTTON, pigpio.EITHER_EDGE, self._cb_button) - - for pin in self._pin_out: - self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) - self.pi.set_PWM_range(pin, PWM_RANGE) - - self.sonar = [sonar.Sonar(self.pi, PIN_SONAR_1_TRIGGER, PIN_SONAR_1_ECHO), - sonar.Sonar(self.pi, PIN_SONAR_2_TRIGGER, PIN_SONAR_2_ECHO), - sonar.Sonar(self.pi, PIN_SONAR_3_TRIGGER, PIN_SONAR_3_ECHO)] - - try: - self._ag = mpu.AccelGyro() - except IOError: - logging.info("MPU not available") - - self.stop() - self._is_moving = False - - the_bot = None - - def exit(self): - self._cb1.cancel() - if self._encoder: - self._twin_motors_enc.exit() - for s in self.sonar: - s.cancel() - - @classmethod - def get_instance(cls, servo=False, motor_trim_factor=1.0): - if not cls.the_bot: - cls.the_bot = CoderBot(servo, motor_trim_factor) - return cls.the_bot - - def move(self, speed=100, elapse=-1, steps=-1): - speed_left = min(100, max(-100, speed * self._motor_trim_factor)) - speed_right = min(100, max(-100, speed / self._motor_trim_factor)) - self.motor_control(speed_left=speed_left, speed_right=speed_right, elapse=elapse, steps_left=steps, steps_right=steps) - - def turn(self, speed=100, elapse=-1, steps=-1): - speed_left = min(100, max(-100, speed * self._motor_trim_factor)) - speed_right = -min(100, max(-100, speed / self._motor_trim_factor)) - self.motor_control(speed_left=speed_left, speed_right=speed_right, elapse=elapse, steps_left=steps, steps_right=steps) - - def turn_angle(self, speed=100, angle=0): - z = self._ag.get_gyro_data()['z'] - self.turn(speed, elapse=-1) - while abs(z - self._ag.get_gyro_data()['z']) < angle: - time.sleep(0.05) - logging.info(self._ag.get_gyro_data()['z']) - self.stop() - - def forward(self, speed=100, elapse=-1): - self.move(speed=speed, elapse=elapse) - - def backward(self, speed=100, elapse=-1): - self.move(speed=-speed, elapse=elapse) - - def left(self, speed=100, elapse=-1): - self.turn(speed=-speed, elapse=elapse) - - def right(self, speed=100, elapse=-1): - self.turn(speed=speed, elapse=elapse) - - def servo3(self, angle): - self._servo_control(PIN_SERVO_3, angle) - - def servo4(self, angle): - self._servo_control(PIN_SERVO_4, angle) - - def get_sonar_distance(self, sonar_id=0): - return self.sonar[sonar_id].get_distance() - - def _dc_enc_motor(self, speed_left=100, speed_right=100, elapse=-1, steps_left=-1, steps_right=-1): - self._twin_motors_enc.control(power_left=speed_left, power_right=speed_right, - elapse=elapse, speed_left=speed_left, speed_right=speed_right, - steps_left=steps_left, steps_right=steps_right) - - def _dc_motor(self, speed_left=100, speed_right=100, elapse=-1, steps_left=-1, steps_right=-1): - - # pylint: disable=too-many-instance-attributes - - self._encoder_cur_left = 0 - self._encoder_cur_right = 0 - self._encoder_target_left = steps_left - self._encoder_target_right = steps_right - self._encoder_dir_left = (speed_left > 0) - (speed_left < 0) - self._encoder_dir_right = (speed_right > 0) - (speed_right < 0) - self._encoder_last_tick_time_left = 0 - self._encoder_last_tick_time_right = 0 - self._encoder_motor_stopping_left = False - self._encoder_motor_stopping_right = False - self._encoder_motor_stopped_left = False - self._encoder_motor_stopped_right = False - - self._is_moving = True - if speed_left < 0: - speed_left = abs(speed_left) - self.pi.write(PIN_LEFT_FORWARD, 0) - self.pi.set_PWM_dutycycle(PIN_LEFT_BACKWARD, speed_left) - else: - self.pi.write(PIN_LEFT_BACKWARD, 0) - self.pi.set_PWM_dutycycle(PIN_LEFT_FORWARD, speed_left) - - if speed_right < 0: - speed_right = abs(speed_right) - self.pi.write(PIN_RIGHT_FORWARD, 0) - self.pi.set_PWM_dutycycle(PIN_RIGHT_BACKWARD, speed_right) - else: - self.pi.write(PIN_RIGHT_BACKWARD, 0) - self.pi.set_PWM_dutycycle(PIN_RIGHT_FORWARD, speed_right) - - self.pi.write(PIN_MOTOR_ENABLE, 1) - if elapse > 0: - time.sleep(elapse) - self.stop() - - def _servo_motor(self, speed_left=100, speed_right=100, elapse=-1, steps_left=-1, steps_right=-1): - self._is_moving = True - speed_left = -speed_left - - steps_left - steps_right - - self.pi.write(PIN_MOTOR_ENABLE, 1) - self.pi.write(PIN_RIGHT_BACKWARD, 0) - self.pi.write(PIN_LEFT_BACKWARD, 0) - - self._servo_motor_control(PIN_LEFT_FORWARD, speed_left) - self._servo_motor_control(PIN_RIGHT_FORWARD, speed_right) - if elapse > 0: - time.sleep(elapse) - self.stop() - - - def _servo_motor_control(self, pin, speed): - self._is_moving = True - speed = ((speed + 100) * 50 / 200) + 52 - - self.pi.set_PWM_range(pin, 1000) - self.pi.set_PWM_frequency(pin, 50) - self.pi.set_PWM_dutycycle(pin, speed) - - def _servo_control(self, pin, angle): - duty = ((angle + 90) * 100 / 180) + 25 - - self.pi.set_PWM_range(pin, 1000) - self.pi.set_PWM_frequency(pin, 50) - self.pi.set_PWM_dutycycle(pin, duty) - - def stop(self): - if self._encoder: - self._twin_motors_enc.stop() - else: - for pin in self._pin_out: - self.pi.write(pin, 0) - self._is_moving = False - - def is_moving(self): - return self._is_moving - - def set_callback(self, gpio, callback, elapse): - self._cb_elapse[gpio] = elapse * 1000 - self._cb[gpio] = callback - self._cb_last_tick[gpio] = 0 - - def sleep(self, elapse): - logging.debug("sleep: %s", str(elapse)) - time.sleep(elapse) - - def _cb_button(self, gpio, level, tick): - cb = self._cb.get(gpio) - if cb: - elapse = self._cb_elapse.get(gpio) - if level == 0: - self._cb_last_tick[gpio] = tick - elif tick - self._cb_last_tick[gpio] > elapse: - self._cb_last_tick[gpio] = tick - logging.info("pushed: %d, %d", level, tick) - cb() - - class MotorEncoder(object): - def __init__(self, parent, _pigpio, pin_enable, pin_forward, pin_backward, pin_encoder): - self._parent = parent - self._pigpio = _pigpio - self._pin_enable = pin_enable - self._pin_forward = pin_forward - self._pin_backward = pin_backward - self._pin_encoder = pin_encoder - self._direction = False - self._pin_duty = 0 - self._pin_reverse = 0 - self._power = 0.0 - self._power_actual = 0.0 - self._encoder_dist = 0 - self._encoder_speed = 0.0 - self._encoder_last_tick = 0 - self._encoder_dist_target = 0 - self._encoder_speed_target = 0.0 - self._encoder_k_s_1 = 20 - self._encoder_k_v_1 = 80 - self._motor_stopping = False - self._motor_running = False - self._motor_stop_fast = True - self._pigpio.set_mode(self._pin_encoder, pigpio.INPUT) - self._cb = self._pigpio.callback(self._pin_encoder, pigpio.RISING_EDGE, self._cb_encoder) - self._motor_lock = threading.RLock() - - def exit(self): - self._cb.cancel() - - def _cb_encoder(self, gpio, level, tick): - self._motor_lock.acquire() - self._encoder_dist += 1 - delta_ticks = tick - self._encoder_last_tick if tick > self._encoder_last_tick else tick - self._encoder_last_tick + 4294967295 - self._encoder_last_tick = tick - self._encoder_speed = 1000000.0 / delta_ticks #convert speed in steps per second - #print "pin: " + str(self._pin_forward) + " dist: " + str(self._encoder_dist) + " target: " + str(self._encoder_dist_target) - if self._encoder_dist_target >= 0 and self._motor_stop_fast: - #delta_s is the delta (in steps)before the target to reverse the motor in order to arrive at target - delta_s = max(min(self._encoder_speed / self._encoder_k_s_1, 100), 0) - #print "pin: " + str(self._pin_forward) + " dist: " + str(self._encoder_dist) + " target: " + str(self._encoder_dist_target) + " delta_s: " + str(delta_s) - if (self._encoder_dist >= self._encoder_dist_target - delta_s and - not self._motor_stopping and self._motor_running): - self._motor_stopping = True - self._pigpio.write(self._pin_duty, 0) - self._pigpio.set_PWM_dutycycle(self._pin_reverse, self._power) - elif (self._motor_running and - ((self._motor_stopping and - self._encoder_speed < self._encoder_k_v_1) or - (self._motor_stopping and - self._encoder_dist >= self._encoder_dist_target))): - self.stop() - logging.info("dist: " + str(self._encoder_dist) + " speed: " + str(self._encoder_speed)) - if self._encoder_dist_target >= 0 and not self._motor_stop_fast: - if self._encoder_dist >= self._encoder_dist_target: - self.stop() - self._parent._cb_encoder(self, gpio, level, tick) - self._motor_lock.release() - if not self._motor_running: - self._parent._check_complete() - - def control(self, power=100.0, elapse=-1, speed=100.0, steps=-1): - self._motor_lock.acquire() - self._direction = speed > 0 - self._encoder_dist_target = steps - self._motor_stopping = False - self._motor_running = True - self._encoder_dist = 0 - self._encoder_speed_target = abs(speed) - self._power = abs(power) #TODO: initial power must be a function of desired speed - self._power_actual = abs(power) #TODO: initial power must be a function of desired speed - self._pin_duty = self._pin_forward if self._direction else self._pin_backward - self._pin_reverse = self._pin_backward if self._direction else self._pin_forward - self._pigpio.write(self._pin_reverse, 0) - self._pigpio.set_PWM_dutycycle(self._pin_duty, self._power) - self._pigpio.write(self._pin_enable, True) - self._motor_lock.release() - if elapse > 0: - time.sleep(elapse) - self.stop() - - def stop(self): - self._motor_lock.acquire() - self._motor_stopping = False - self._motor_running = False - self._pigpio.write(self._pin_forward, 0) - self._pigpio.write(self._pin_backward, 0) - self._motor_lock.release() - - def distance(self): - return self._encoder_dist - - def speed(self): - return self._encoder_speed - - def stopping(self): - return self._motor_stopping - - def running(self): - return self._motor_running - - def adjust_power(self, power_delta): - self._power_actual = min(max(self._power + power_delta, 0), 100) - self._pigpio.set_PWM_dutycycle(self._pin_duty, self._power_actual) - - class TwinMotorsEncoder(object): - def __init__(self, apigpio, pin_enable, pin_forward_left, pin_backward_left, pin_encoder_left, pin_forward_right, pin_backward_right, pin_encoder_right): - self._straight = False - self._running = False - self._encoder_sem = threading.Condition() - self._motor_left = CoderBot.MotorEncoder(self, apigpio, pin_enable, pin_forward_left, pin_backward_left, pin_encoder_left) - self._motor_right = CoderBot.MotorEncoder(self, apigpio, pin_enable, pin_forward_right, pin_backward_right, pin_encoder_right) - - def exit(self): - self._motor_left.exit() - self._motor_right.exit() - - def control(self, power_left=100.0, power_right=100.0, elapse=-1, speed_left=-1, speed_right=-1, steps_left=-1, steps_right=-1): - self._straight = power_left == power_right and speed_left == speed_right and steps_left == steps_right - - if steps_left >= 0 or steps_right >= 0: - self._encoder_sem.acquire() - - self._motor_left.control(power=power_left, elapse=-1, speed=speed_left, steps=steps_left) - self._motor_right.control(power=power_right, elapse=-1, speed=speed_right, steps=steps_right) - self._running = True - - if elapse > 0: - time.sleep(elapse) - self.stop() - - if steps_left >= 0 or steps_right >= 0: - self._encoder_sem.wait() - self._encoder_sem.release() - self.stop() - - def stop(self): - self._motor_left.stop() - self._motor_right.stop() - self._running = False - - def distance(self): - return (self._motor_left.distance() + self._motor_right.distance()) / 2 - - def speed(self): - return (self._motor_left.speed() + self._motor_right.speed()) / 2 - - def _cb_encoder(self, motor, gpio, level, tick): - if (self._straight and self._running and not self._motor_left.stopping() and not self._motor_right.stopping() and - abs(self._motor_left.distance() - self._motor_right.distance()) > 2): - distance_delta = self._motor_left.distance() - self._motor_right.distance() - speed_delta = self._motor_left.speed() - self._motor_right.speed() - power_delta = (distance_delta / 2.0) + (speed_delta / 10.0) - #print "power_delta: " + str(power_delta) + " distance_delta: " + str(distance_delta) + " speed_delta: " + str(speed_delta) - if self._motor_left == motor: - self._motor_left.adjust_power(-power_delta) - if self._motor_right == motor: - self._motor_right.adjust_power(power_delta) - - def _check_complete(self): - if self._motor_left.running() is False and self._motor_right.running() is False: - self._encoder_sem.acquire() - self._encoder_sem.notify() - self._encoder_sem.release() - - def halt(self): - os.system('sudo halt') - - def restart(self): - os.system('sudo /etc/init.d/coderbot restart') - - def reboot(self): - os.system('sudo reboot') diff --git a/coderbot.cfg b/coderbot.cfg index 5e8264f9..5f44f821 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"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", "prog_level": "adv", "move_motor_trim": "1", "move_motor_mode": "dc", "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.mp3", "ctrl_fw_elapse": "-1", "sound_stop": "$shutdown.mp3", "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.mp3", "encoder": "True", "wifi_mode": "ap", "wifi_ssid": "", "wifi_psk": ""} \ No newline at end of file +{"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", "prog_level": "adv", "move_motor_trim": "1", "move_motor_mode": "dc", "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.wav", "ctrl_fw_elapse": "-1", "sound_stop": "$shutdown.wav", "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.wav", "encoder": "True", "audio_volume_level": "100", "wifi_mode": "ap", "wifi_ssid": "coderbot", "wifi_psk": "coderbot", "packages_installed": ""} diff --git a/coderbot.py b/coderbot.py index 93f2e6a6..1c05db79 100644 --- a/coderbot.py +++ b/coderbot.py @@ -46,7 +46,7 @@ class GPIO_CODERBOT_V_4(): PIN_SONAR_3_TRIGGER = 18 PIN_SONAR_3_ECHO = 23 PIN_SONAR_4_TRIGGER = 18 - PIN_SONAR_4_ECHO = None + PIN_SONAR_4_ECHO = 13 # encoder PIN_ENCODER_LEFT_A = 14 @@ -93,12 +93,11 @@ class CoderBot(object): def __init__(self, motor_trim_factor=1.0, encoder=True): try: self._mpu = mpu.AccelGyroMag() - self.GPIOS = GPIO_CODERBOT_V_5() logging.info("MPU available") except: logging.info("MPU not available") - self.GPIOS = GPIO_CODERBOT_V_4() + self.GPIOS = GPIO_CODERBOT_V_5() self._pin_out = [self.GPIOS.PIN_LEFT_FORWARD, self.GPIOS.PIN_RIGHT_FORWARD, self.GPIOS.PIN_LEFT_BACKWARD, self.GPIOS.PIN_RIGHT_BACKWARD, self.GPIOS.PIN_SERVO_1, self.GPIOS.PIN_SERVO_2] self.pi = pigpio.pi('localhost') self.pi.set_mode(self.GPIOS.PIN_PUSHBUTTON, pigpio.INPUT) diff --git a/cv/camera.py b/cv/camera.py index 570d0a37..f2b1b68f 100644 --- a/cv/camera.py +++ b/cv/camera.py @@ -29,7 +29,7 @@ class Camera(object): - FFMPEG_CMD = 'MP4Box' + FFMPEG_CMD = 'ffmpeg' PHOTO_FILE_EXT = ".jpg" VIDEO_FILE_EXT = ".mp4" VIDEO_FILE_EXT_H264 = '.h264' @@ -90,7 +90,10 @@ def video_stop(self): self.camera.stop_recording(2) # pack in mp4 container - params = " -fps " + str(self.camera.framerate) + " -add " + self.video_filename + self.VIDEO_FILE_EXT_H264 + " " + self.video_filename + self.VIDEO_FILE_EXT + params = " -loglevel quiet -stats -framerate " + str(self.camera.framerate) + \ + " -i " + self.video_filename + self.VIDEO_FILE_EXT_H264 + \ + " -c copy " + self.video_filename + self.VIDEO_FILE_EXT + os.system(self.FFMPEG_CMD + params) # remove h264 file os.remove(self.video_filename + self.VIDEO_FILE_EXT_H264) diff --git a/data/activities.json b/data/activities.json index e9b18176..674b1c99 100644 --- a/data/activities.json +++ b/data/activities.json @@ -1 +1 @@ -{"_default": {}} \ No newline at end of file +{"_default": {"2": {"stock": true, "default": true, "uiLang": null, "defaultView": null, "exec": {"camera": true, "log": true}, "name": "default", "drawerEnabled": true, "showName": true, "buttons": [{"action": "clearProgramDlg", "icon": "clear", "label": "message.activity_program_clear", "type": "text"}, {"action": "saveProgram", "icon": "save", "label": "message.activity_program_save", "type": "text"}, {"action": "toggleSaveAs", "icon": "edit", "label": "message.activity_program_save_as", "type": "text"}, {"action": "loadProgramList", "icon": "folder_open", "label": "message.activity_program_load", "type": "text"}, {"action": "runProgram", "icon": "play_arrow", "label": "message.activity_program_run", "type": "text"}, {"action": "getProgramCode", "icon": "code", "label": "message.activity_program_show_code", "type": "text"}, {"action": "exportProgram", "icon": "fa-file-export", "label": "message.activity_program_export", "type": "text"}, {"action": "pickFile", "icon": "fa-file-import", "label": "message.activity_program_import", "type": "text"}], "description": "", "fontSize": "Medio", "capsSwitch": true, "bodyFont": "Roboto", "codeFont": "ubuntumono", "maxBlocks": 0, "availableViews": [], "viewSource": null, "autoRecVideo": null, "toolbox": {"contents": [{"colour": "210", "contents": [{"kind": "block", "type": "controls_if"}, {"kind": "block", "type": "logic_compare"}, {"kind": "block", "type": "logic_operation"}, {"kind": "block", "type": "logic_negate"}, {"kind": "block", "type": "logic_boolean"}, {"kind": "block", "type": "logic_null"}, {"kind": "block", "type": "logic_ternary"}], "kind": "category", "name": "Logica"}, {"colour": "120", "contents": [{"kind": "block", "type": "controls_repeat_ext", "value": {"contents": {"field": {"#text": "10", "name": "NUM"}, "kind": "block", "type": "math_number"}, "name": "TIMES"}}, {"kind": "block", "type": "controls_whileUntil"}, {"kind": "block", "type": "controls_for", "value": [{"contents": {"field": {"#text": "1", "name": "NUM"}, "kind": "block", "type": "math_number"}, "name": "FROM"}, {"contents": {"field": {"#text": "10", "name": "NUM"}, "kind": "block", "type": "math_number"}, "name": "TO"}, {"contents": {"field": {"#text": "1", "name": "NUM"}, "kind": "block", "type": "math_number"}, "name": "BY"}]}, {"kind": "block", "type": "controls_forEach"}, {"kind": "block", "type": "controls_flow_statements"}], "kind": "category", "name": "Cicli"}, {"colour": "230", "contents": [{"kind": "block", "type": "math_number"}, {"kind": "block", "type": "math_arithmetic"}, {"kind": "block", "type": "math_single"}, {"kind": "block", "type": "math_trig"}, {"kind": "block", "type": "math_constant"}, {"kind": "block", "type": "math_number_property"}, {"kind": "block", "type": "math_change"}, {"kind": "block", "type": "math_round"}, {"kind": "block", "type": "math_on_list"}, {"kind": "block", "type": "math_modulo"}, {"kind": "block", "type": "math_constrain"}, {"kind": "block", "type": "math_random_int"}, {"kind": "block", "type": "math_random_float"}], "kind": "category", "name": "Matematica"}, {"colour": "160", "contents": [{"kind": "block", "type": "text_print"}, {"kind": "block", "type": "text"}, {"kind": "block", "type": "text_join"}, {"kind": "block", "type": "text_append"}, {"kind": "block", "type": "text_length"}, {"kind": "block", "type": "text_isEmpty"}, {"kind": "block", "type": "text_indexOf"}, {"kind": "block", "type": "text_charAt"}, {"kind": "block", "type": "text_getSubstring"}, {"kind": "block", "type": "text_changeCase"}, {"kind": "block", "type": "text_trim"}], "kind": "category", "name": "Testo"}, {"colour": "260", "contents": [{"kind": "block", "type": "lists_create_empty"}, {"kind": "block", "type": "lists_create_with"}, {"kind": "block", "type": "lists_repeat"}, {"kind": "block", "type": "lists_length"}, {"kind": "block", "type": "lists_isEmpty"}, {"kind": "block", "type": "lists_indexOf"}, {"kind": "block", "type": "lists_getIndex"}, {"kind": "block", "type": "lists_setIndex"}, {"kind": "block", "type": "lists_getSublist"}, {"kind": "block", "type": "hashmap_get_value"}, {"kind": "block", "type": "hashmap_get_keys"}], "kind": "category", "name": "Liste"}, {"colour": "330", "custom": "VARIABLE", "kind": "category", "name": "Variabili"}, {"colour": "290", "custom": "PROCEDURE", "kind": "category", "name": "Funzioni"}, {"colour": "15", "contents": [{"kind": "block", "type": "coderbot_event_generator", "value": {"contents": {"kind": "block", "type": "controls_if", "value": [{"contents": {"type": "logic_compare"}, "name": "IF0"}, {"contents": {"field": {"#text": "a_topic", "name": "event_topic"}, "kind": "block", "type": "coderbot_event_publisher", "value": {"contents": {"#text": "\n\t\t\t\t\t\t\t\t", "kind": "block", "type": "text"}, "name": "event_data"}}, "name": "DO0"}]}, "name": "generator_statements"}}, {"field": {"#text": "a_topic", "name": "event_topic"}, "kind": "block", "type": "coderbot_event_publisher", "value": {"contents": {"#text": "\n\t\t\t\t", "kind": "block", "type": "text"}, "name": "event_data"}}, {"field": {"#text": "a_topic", "name": "event_topic"}, "kind": "block", "type": "coderbot_event_listener", "value": {"contents": {"kind": "block", "type": "text_print", "value": {"contents": {"field": {"#text": "event_data", "name": "VAR"}, "kind": "block", "type": "variables_get"}, "name": "TEXT"}}, "name": "event_statements"}}, {"field": {"#text": "event_data", "name": "VAR"}, "kind": "block", "type": "variables_get"}], "kind": "category", "name": "Eventi"}, {"colour": "40", "contents": [{"kind": "block", "type": "coderbot_moveForward"}, {"kind": "block", "type": "coderbot_moveBackward"}, {"kind": "block", "type": "coderbot_turnLeft"}, {"kind": "block", "type": "coderbot_turnRight"}, {"kind": "block", "type": "coderbot_adv_stop"}], "kind": "category", "name": "Movimento"}, {"colour": "120", "contents": [{"kind": "block", "type": "coderbot_camera_photoTake"}, {"kind": "block", "type": "coderbot_camera_videoRec"}, {"kind": "block", "type": "coderbot_camera_videoStop"}], "kind": "category", "name": "Camera"}, {"colour": "250", "contents": [{"kind": "block", "type": "coderbot_cam_average"}, {"kind": "block", "type": "coderbot_adv_pathAhead"}, {"kind": "block", "type": "coderbot_adv_findLine"}, {"kind": "block", "type": "coderbot_adv_findSignal"}, {"kind": "block", "type": "coderbot_adv_findFace"}, {"kind": "block", "type": "coderbot_adv_findColor", "value": {"contents": {"#text": "\n\t\t\t\t", "kind": "block", "type": "colour_picker"}, "name": "COLOR"}}, {"kind": "block", "type": "coderbot_adv_findText", "value": {"contents": {"#text": "\n\t\t\t\t", "kind": "block", "type": "colour_picker"}, "name": "COLOR"}}, {"kind": "block", "type": "coderbot_adv_findQRCode"}, {"kind": "block", "type": "coderbot_adv_findARCode"}, {"kind": "block", "type": "coderbot_adv_cnn_classify"}, {"kind": "block", "type": "coderbot_adv_cnn_detect_objects"}], "kind": "category", "name": "Visione"}, {"colour": "240", "contents": [{"kind": "block", "type": "coderbot_sonar_get_distance"}, {"kind": "block", "type": "coderbot_mpu_get_gyro"}, {"kind": "block", "type": "coderbot_mpu_get_accel"}, {"kind": "block", "type": "coderbot_mpu_get_heading"}, {"kind": "block", "type": "coderbot_mpu_get_temp"}], "kind": "category", "name": "Sensori"}, {"colour": "220", "contents": [{"kind": "block", "type": "coderbot_audio_say"}, {"kind": "block", "type": "coderbot_audio_record"}, {"kind": "block", "type": "coderbot_audio_play"}, {"kind": "block", "type": "coderbot_audio_hear", "value": [{"contents": {"field": {"#text": "100", "name": "NUM"}, "kind": "block", "type": "math_number"}, "name": "LEVEL"}, {"contents": {"field": {"#text": "1.0", "name": "NUM"}, "kind": "block", "type": "math_number"}, "name": "ELAPSE"}]}, {"kind": "block", "type": "coderbot_audio_listen"}], "kind": "category", "name": "Suoni"}, {"colour": "240", "contents": [{"kind": "block", "type": "coderbot_atmega_get_input"}, {"kind": "block", "type": "coderbot_atmega_set_output"}, {"kind": "block", "type": "coderbot_atmega_set_led"}], "kind": "category", "name": "Estensioni I/O"}, {"colour": "345", "contents": [{"kind": "block", "type": "coderbot_music_note_adv"}, {"kind": "block", "type": "coderbot_music_instrument_adv"}, {"kind": "block", "type": "coderbot_music_animal_adv"}, {"kind": "block", "type": "coderbot_music_pause_adv"}], "kind": "category", "name": "Music"}], "kind": "categoryToolbox"}}, "3": {"stock": null, "default": null, "uiLang": null, "defaultView": "blocks", "exec": {"camera": true, "log": true}, "name": "Base", "drawerEnabled": true, "showName": true, "buttons": [{"action": "clearProgramDlg", "icon": "clear", "label": "message.activity_program_clear", "type": "text"}, {"action": "saveProgram", "icon": "save", "label": "message.activity_program_save", "type": "text"}, {"action": "toggleSaveAs", "icon": "edit", "label": "message.activity_program_save_as", "type": "text"}, {"action": "loadProgramList", "icon": "folder_open", "label": "message.activity_program_load", "type": "text"}, {"action": "runProgram", "icon": "play_arrow", "label": "message.activity_program_run", "type": "text"}, {"action": "getProgramCode", "icon": "code", "label": "message.activity_program_show_code", "type": "text"}, {"action": "exportProgram", "icon": "fa-file-export", "label": "message.activity_program_export", "type": "text"}, {"action": "pickFile", "icon": "fa-file-import", "label": "message.activity_program_import", "type": "text"}], "description": "Base", "fontSize": "Medio", "capsSwitch": true, "bodyFont": "Roboto", "codeFont": "ubuntumono", "maxBlocks": 0, "availableViews": [], "viewSource": null, "autoRecVideo": null, "toolbox": {"kind": "flyoutToolbox", "contents": [{"kind": "block", "type": "coderbot_basic_moveForward", "enabled": true}, {"kind": "block", "type": "coderbot_basic_moveBackward", "enabled": true}, {"kind": "block", "type": "coderbot_basic_turnLeft", "enabled": true}, {"kind": "block", "type": "coderbot_basic_turnRight", "enabled": true}, {"kind": "block", "type": "coderbot_basic_repeat", "enabled": true}, {"kind": "block", "type": "coderbot_basic_audio_say", "enabled": true}, {"kind": "block", "type": "coderbot_basic_camera_photoTake", "enabled": true}]}}}} \ No newline at end of file diff --git a/data/defaults/programs/program_demo_cat_follower.json b/data/defaults/programs/program_demo_cat_follower.json new file mode 100644 index 00000000..376d1fb8 --- /dev/null +++ b/data/defaults/programs/program_demo_cat_follower.json @@ -0,0 +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", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_demo_io_ext.json b/data/defaults/programs/program_demo_io_ext.json new file mode 100644 index 00000000..3d2c518a --- /dev/null +++ b/data/defaults/programs/program_demo_io_ext.json @@ -0,0 +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", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_test_input.json b/data/defaults/programs/program_test_input.json new file mode 100644 index 00000000..309b3a4f --- /dev/null +++ b/data/defaults/programs/program_test_input.json @@ -0,0 +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", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_test_music.json b/data/defaults/programs/program_test_music.json new file mode 100644 index 00000000..9516746c --- /dev/null +++ b/data/defaults/programs/program_test_music.json @@ -0,0 +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", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_test_output.json b/data/defaults/programs/program_test_output.json new file mode 100644 index 00000000..954474ae --- /dev/null +++ b/data/defaults/programs/program_test_output.json @@ -0,0 +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", "default": false} \ No newline at end of file diff --git a/data/program_no_name.json b/data/program_no_name.json new file mode 100644 index 00000000..8d987595 --- /dev/null +++ b/data/program_no_name.json @@ -0,0 +1 @@ +{"name": "no_name", "dom_code": "", "code": "", "default": false} \ No newline at end of file diff --git a/data/program_test_led.json b/data/program_test_led.json new file mode 100644 index 00000000..c95f304b --- /dev/null +++ b/data/program_test_led.json @@ -0,0 +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", "default": false} \ No newline at end of file diff --git a/data/programs.json b/data/programs.json index 4e899519..e2968f2b 100644 --- a/data/programs.json +++ b/data/programs.json @@ -1 +1 @@ -{"_default": {"1": {"name": "test_find_code", "filename": "./data/defaults/programs/program_test_find_code.json", "default": "True"}, "2": {"name": "demo_color_seeker", "filename": "./data/defaults/programs/program_demo_color_seeker.json", "default": "True"}, "3": {"name": "demo_sound_clap_control", "filename": "./data/defaults/programs/program_demo_sound_clap_control.json", "default": "True"}, "4": {"name": "test_find_path_ahead", "filename": "./data/defaults/programs/program_test_find_path_ahead.json", "default": "True"}, "5": {"name": "test_sound_hear", "filename": "./data/defaults/programs/program_test_sound_hear.json", "default": "True"}, "6": {"name": "test_find_color", "filename": "./data/defaults/programs/program_test_find_color.json", "default": "True"}, "7": {"name": "test_cnn_classifier", "filename": "./data/defaults/programs/program_test_cnn_classifier.json", "default": "True"}, "8": {"name": "test_sound_rec", "filename": "./data/defaults/programs/program_test_sound_rec.json", "default": "True"}, "9": {"name": "test_find_face", "filename": "./data/defaults/programs/program_test_find_face.json", "default": "True"}, "10": {"name": "demo_obstacle_avoidance", "filename": "./data/defaults/programs/program_demo_obstacle_avoidance.json", "default": "True"}, "11": {"name": "test_sonars", "filename": "./data/defaults/programs/program_test_sonars.json", "default": "True"}, "12": {"name": "test_img_average", "filename": "./data/defaults/programs/program_test_img_average.json", "default": "True"}, "13": {"name": "demo_ar_tags", "filename": "./data/defaults/programs/program_demo_ar_tags.json", "default": "True"}, "14": {"name": "test_cnn_object_detect", "filename": "./data/defaults/programs/program_test_cnn_object_detect.json", "default": "True"}, "15": {"name": "demo_line_follower", "filename": "./data/defaults/programs/program_demo_line_follower.json", "default": "True"}}} \ No newline at end of file +{"_default": {"1": {"name": "test_find_code", "filename": "./data/defaults/programs/program_test_find_code.json", "default": "True"}, "2": {"name": "demo_color_seeker", "filename": "./data/defaults/programs/program_demo_color_seeker.json", "default": "True"}, "3": {"name": "demo_sound_clap_control", "filename": "./data/defaults/programs/program_demo_sound_clap_control.json", "default": "True"}, "4": {"name": "test_find_path_ahead", "filename": "./data/defaults/programs/program_test_find_path_ahead.json", "default": "True"}, "5": {"name": "test_sound_hear", "filename": "./data/defaults/programs/program_test_sound_hear.json", "default": "True"}, "6": {"name": "test_find_color", "filename": "./data/defaults/programs/program_test_find_color.json", "default": "True"}, "7": {"name": "test_cnn_classifier", "filename": "./data/defaults/programs/program_test_cnn_classifier.json", "default": "True"}, "8": {"name": "test_sound_rec", "filename": "./data/defaults/programs/program_test_sound_rec.json", "default": "True"}, "9": {"name": "test_find_face", "filename": "./data/defaults/programs/program_test_find_face.json", "default": "True"}, "10": {"name": "demo_obstacle_avoidance", "filename": "./data/defaults/programs/program_demo_obstacle_avoidance.json", "default": "True"}, "11": {"name": "test_img_average", "filename": "./data/defaults/programs/program_test_img_average.json", "default": "True"}, "12": {"name": "demo_ar_tags", "filename": "./data/defaults/programs/program_demo_ar_tags.json", "default": "True"}, "13": {"name": "test_cnn_object_detect", "filename": "./data/defaults/programs/program_test_cnn_object_detect.json", "default": "True"}, "14": {"name": "demo_line_follower", "filename": "./data/defaults/programs/program_demo_line_follower.json", "default": "True"}, "15": {"name": "cat_follower", "filename": "./data/defaults/programs/program_demo_cat_follower.json", "default": "True"}, "16": {"name": "test_music", "filename": "./data/defaults/programs/program_test_music.json", "default": "True"}, "17": {"name": "demo_cat_follower", "filename": "./data/defaults/programs/program_demo_cat_follower.json", "default": "True"}, "18": {"name": "test_input", "filename": "./data/defaults/programs/program_test_input.json", "default": "True"}, "19": {"name": "test_output", "filename": "./data/defaults/programs/program_test_output.json", "default": "True"}, "20": {"name": "demo_io_ext", "filename": "./data/defaults/programs/program_demo_io_ext.json", "default": "True"}, "21": {"name": "test_sonars", "filename": "./data/defaults/programs/program_test_sonars.json", "default": "True"}, "22": {"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", "default": false, "filename": "./data/program_test_led.json"}, "23": {"name": "no_name", "dom_code": "", "code": "", "default": false, "filename": "./data/program_no_name.json"}}} \ No newline at end of file diff --git a/main.py b/main.py index fa80e5bf..17659aa6 100644 --- a/main.py +++ b/main.py @@ -30,6 +30,7 @@ from config import Config from cnn_manager import CNNManager from event import EventManager +from audioControls import AudioCtrl # Logging configuration logger = logging.getLogger() @@ -42,12 +43,12 @@ #logger.addHandler(sh) logger.addHandler(fh) - ## (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 -connexionApp = connexion.App(__name__, swagger_ui=True, swagger_path='swagger-ui/') +options = {"swagger_ui": False} +connexionApp = connexion.App(__name__, options=options) # Connexion wraps FlaskApp, so app becomes connexionApp.app app = connexionApp.app @@ -56,7 +57,6 @@ babel = Babel(app) app.debug = False app.prog_engine = ProgramEngine.get_instance() -app.prog = None app.shutdown_requested = False ## New API and web application @@ -153,6 +153,8 @@ def handle_config(): """ Overwrite configuration file on disk and reload it """ + audioCtrl = AudioCtrl.get_instance() + audioCtrl.setVolume(int(request.form['audio_volume_level'])) Config.write(updateDict(app.bot_config, request.form)) app.bot_config = Config.get() return "ok" @@ -176,9 +178,9 @@ def handle_wifi(): psk = request.form.get("wifi_psk") logging.info("mode " + mode +" ssid: " + ssid + " psk: " + psk) - client_params = " \"" + ssid + "\" \"" + psk + "\"" if ssid != "" and psk != "" else "" + client_params = " --ssid \"" + ssid + "\" --pwd \"" + psk + "\"" if ssid != "" and psk != "" else "" logging.info(client_params) - os.system("sudo ./wifi.py updatecfg " + mode + client_params) + os.system("sudo ./wifi.py updatecfg --mode " + mode + client_params) os.system("sudo reboot") if mode == "ap": return "http://coder.bot" @@ -333,8 +335,8 @@ def handle_program_load(): """ logging.debug("program_load") name = request.args.get('name') - app.prog = app.prog_engine.load(name) - return jsonify(app.prog.as_dict()) + prog = app.prog_engine.load(name) + return jsonify(prog.as_dict()) @app.route("/program/save", methods=["POST"]) def handle_program_save(): @@ -367,8 +369,8 @@ def handle_program_exec(): logging.debug("program_exec") name = request.form.get('name') code = request.form.get('code') - app.prog = app.prog_engine.create(name, code) - return json.dumps(app.prog.execute()) + prog = app.prog_engine.create(name, code) + return json.dumps(prog.execute()) @app.route("/program/end", methods=["POST"]) def handle_program_end(): @@ -376,9 +378,9 @@ def handle_program_end(): Stop the program execution """ logging.debug("program_end") - if app.prog: - app.prog.end() - app.prog = None + prog = app.prog_engine.get_current_program() + if prog: + prog.end() return "ok" @app.route("/program/status", methods=["GET"]) @@ -387,9 +389,9 @@ def handle_program_status(): Expose the program status """ logging.debug("program_status") - prog = Program("") - if app.prog: - prog = app.prog + prog = app.prog_engine.get_current_program() + if prog is None: + prog = Program("") return json.dumps({'name': prog.name, "running": prog.is_running(), "log": app.prog_engine.get_log()}) @app.route("/cnnmodels", methods=["GET"]) @@ -446,10 +448,11 @@ def execute(command): def button_pushed(): if app.bot_config.get('button_func') == "startstop": - if app.prog and app.prog.is_running(): - app.prog.end() - elif app.prog and not app.prog.is_running(): - app.prog.execute() + prog = app.prog_engine.get_current_prog() + if prog and prog.is_running(): + prog.end() + elif prog and not prog.is_running(): + prog.execute() def remove_doreset_file(): try: @@ -468,6 +471,10 @@ def run_server(): encoder=bool(app.bot_config.get('encoder'))) audio = Audio.get_instance() audio.say(app.bot_config.get("sound_start")) + + audioCtrl = AudioCtrl.get_instance() + audioCtrl.setVolume(int(app.bot_config.get('audio_volume_level'))) + try: cam = Camera.get_instance() Motion.get_instance() @@ -478,8 +485,8 @@ def run_server(): EventManager.get_instance("coderbot") if app.bot_config.get('load_at_start') and app.bot_config.get('load_at_start'): - app.prog = app.prog_engine.load(app.bot_config.get('load_at_start')) - app.prog.execute() + prog = app.prog_engine.load(app.bot_config.get('load_at_start')) + prog.execute() except ValueError as e: app.bot_config = {} logging.error(e) diff --git a/music.py b/music.py new file mode 100644 index 00000000..0544b468 --- /dev/null +++ b/music.py @@ -0,0 +1,152 @@ +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# MUSICAL EXTENTION for CoderBot +# This extention is develop by: +# Michele Carbonera - miki_992@hotmail.it - m.carbonera@campus.unimib.it - michele.carbonera@unimib.it +# Antonino Tramontana - a.tramontana1@campus.unimib.it +# Copyright (C) 2020 +############################################################################ + +import os +import sox +import time + +class Music: + _instance = None + managerPackage = None + + noteDict = { + 'C2': -7.0, 'D2' : -5.0, 'E2' : -3.0, 'F2' : -2.0, 'F#2' : -1.0, 'G2' : 0.0, + 'A2' : 2.0, 'Bb2' : 3.0, 'B2' : 4.0, 'C3' : 5.0, 'D3' : 7.0, 'E3' : 9.0, + 'F3' : 10.0, 'G3' : 12.0 + } + + + @classmethod + def get_instance(cls,managerPackage): + if cls._instance is None: + cls._instance = Music(managerPackage) + return cls._instance + + def __init__(self,managerPackage): + + #os.putenv('AUDIODRIVER', 'alsa') + #os.putenv('AUDIODEV', 'hw:1,0') + self.managerPackage = managerPackage + print("We have create a class: MUSICAL") + + def test(self): + tfm = sox.Transformer() + tfm.preview('cat.wav') + tfm.build('cat.wav', 'outMusicDemo.wav') + + #play a pause + # @param duration: duration of the pause in seconds + def play_pause(self, duration): + duration = float(duration) + time.sleep(duration) + + #play a given note for a given instrument + # @param instrument: name of the instrument to be used + # @param note: name of the note in the following format "A2" + # @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) + tfm = sox.Transformer() + + duration = float(duration) + + alt = 0.0 + if alteration == 'bmolle': + alt = -1.0 + elif alteration == 'diesis': + alt = 1.0 + + if note in self.noteDict : + shift = self.noteDict[note]+ alt + else: + print('note not exist') + return + + tfm.pitch(shift, quick=False) + tfm.trim(0.0, end_time=0.5*duration) + if self.managerPackage.isPackageAvailable(instrument): + tfm.preview('./sounds/notes/' + instrument + '/audio.wav') + else: + print("no instrument:"+str(instrument)+" present in this coderbot!") + + def play_animal(self, instrument, note='G2', alteration='none', duration=1.0): + tfm = sox.Transformer() + + duration = float(duration) + + alt = 0.0 + if alteration == 'bmolle': + alt = -1.0 + elif alteration == 'diesis': + alt = 1.0 + + if note == 'C2': + shift = -7.0 + alt + elif note == 'D2': + shift = -5.0 + alt + elif note == 'E2': + shift = -3.0 + alt + elif note == 'F2': + shift = -2.0 + alt + elif note == 'F#2': + shift = -1.0 + alt + elif note == 'G2': + shift = 0.0 + alt + elif note == 'A2': + shift = 2.0 + alt + elif note == 'Bb2': + shift = 3.0 + alt + elif note == 'B2': + shift = 4.0 + alt + elif note == 'C3': + shift = 5.0 + alt + elif note == 'D3': + shift = 7.0 + alt + elif note == 'E3': + shift = 9.0 + alt + elif note == 'F3': + shift = 10.0 + alt + elif note == 'G3': + shift = 12.0 + alt + + if note in self.noteDict : + shift = self.noteDict[note]+ alt + else: + print('note 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!") + return + tfm.pitch(shift, quick=False) + tfm.trim(0.0, end_time=0.5*duration) + #tfm.stretch(time, window=20) + tfm.preview('./sounds/notes/' + instrument + '/audio.wav') diff --git a/musicPackages.py b/musicPackages.py new file mode 100644 index 00000000..051b8738 --- /dev/null +++ b/musicPackages.py @@ -0,0 +1,221 @@ +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# MUSICAL EXTENTION for CoderBot +# This extention is develop by: +# Michele Carbonera - miki_992@hotmail.it - m.carbonera@campus.unimib.it - michele.carbonera@unimib.it +# Antonino Tramontana - a.tramontana1@campus.unimib.it +# Copyright (C) 2020 +############################################################################ + +import json +import os +import logging +import copy + +class MusicPackage: + + def __init__(self, nameID, category, name_IT, name_EN, version, date): + self.nameID = nameID + self.category = category + self.name_IT = name_IT + self.name_EN = name_EN + self.version = version + self.date = date + self.interfaces = list() + + def getNameID(self): + return self.nameID + + def getCategory(self): + return self.category + + def getNameIT(self): + return self.name_IT + + def getNameEN(self): + return self.name_EN + + def getVersion(self): + return self.version + + def getDate(self): + return self.date + + def getInterfaces(self): + return self.interfaces + + def addInterface(self, musicPackageInterface): + self.interfaces.append(musicPackageInterface) + +class MusicPackageInterface: + + def __init__(self,interfaceName,available,icon): + self.interfaceName = interfaceName + self.available = available + self.icon = icon + + def getInterfaceName(self): + return self.interfaceName + + def getAvailable(self): + return self.available + + def getIcon(self): + return self.icon + +class MusicPackageManager: + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = MusicPackageManager() + return cls._instance + + def __init__(self): + self.packages = dict() + with open('./sounds/notes/music_package.json') as json_file: + data = json.load(json_file) + for p in data['packages']: + + package = data['packages'][p] + mp = MusicPackage(p,package['category'],package['name_IT'],package['name_EN'],package['version'],package['date']) + for i in package['interface']: + interfaceItem = package['interface'][i] + mpi = MusicPackageInterface(i,interfaceItem['available'],interfaceItem['icon']) + mp.addInterface(mpi) + + if p not in self.packages: + self.packages[p] = mp + + def listPackages(self): + packages_serializable = dict() + for name, package in self.packages.items(): + package_copy = copy.deepcopy(package) + packages_serializable[name] = package_copy.__dict__ + packages_serializable[name]['interfaces'] = [] + for i in package.interfaces: + packages_serializable[name]['interfaces'].append(i.__dict__) + return packages_serializable + + def updatePackages(self): + newdict = { 'packages': {} } + for element in self.packages: + nameID = self.packages[element].getNameID() + newdict['packages'][nameID] = { } + newdict['packages'][nameID]['category']= self.packages[element].getCategory() + newdict['packages'][nameID]['name_IT']= self.packages[element].getNameIT() + newdict['packages'][nameID]['name_EN']= self.packages[element].getNameEN() + newdict['packages'][nameID]['version']= self.packages[element].getVersion() + newdict['packages'][nameID]['date']= self.packages[element].getDate() + newdict['packages'][nameID]['interface']= {'base':{}, 'intermediate':{}, 'advanced': {}} + newdict['packages'][nameID]['interface']['base']['available'] = self.packages[element].getInterfaces()[0].getAvailable() + newdict['packages'][nameID]['interface']['base']['icon'] = self.packages[element].getInterfaces()[0].getIcon() + newdict['packages'][nameID]['interface']['intermediate']['available'] = self.packages[element].getInterfaces()[1].getAvailable() + newdict['packages'][nameID]['interface']['intermediate']['icon'] = self.packages[element].getInterfaces()[1].getIcon() + newdict['packages'][nameID]['interface']['advanced']['available'] = self.packages[element].getInterfaces()[2].getAvailable() + newdict['packages'][nameID]['interface']['advanced']['icon'] = self.packages[element].getInterfaces()[2].getIcon() + + #json_packages = json.dumps(newdict) + with open('sounds/notes/music_package.json', 'w', encoding='utf-8') as json_file: + json.dump(newdict, json_file, ensure_ascii=False, indent=4) + + + def deletePackage(self, packageName): + logging.info("packageName: " + packageName) + if packageName in self.packages: + del self.packages[packageName] + self.updatePackages() + else: + logging.error("errore, il pacchetto " + packageName + " non è stato trovato") + return 2 + + if os.path.exists('./sounds/notes/' + packageName): + os.system('rm -rf ./sounds/notes/' + packageName) + return 1 + + + def verifyVersion(self, packageName, version): + logging.info("verifica pacchetto") + #newversionList = version.split('.') + if packageName not in self.packages: + return True + + newVersionList = [int(x) for x in version.split('.')] + #for i in ragen(0,len(newversionList) -1): + #newversionList[i] = int(newLversionList[i]) + + oldVersion = self.packages[packageName].getVersion() + oldVersionList = [int(x) for x in oldVersion.split('.')] + + for i in range(0,len(newVersionList) -1): + if(newVersionList[i] > oldVersionList[i] ): + return True + elif(newVersionList[i] < oldVersionList[i] ): + return False + + return False + + def addPackage(self, filename): + pkgnames = filename.split('_') + version = pkgnames[1].replace('.zip', '') + logging.info("Music Package version: " + version) + pkgname = pkgnames[0] + pkgpath = './sounds/notes/' + pkgname + if not self.verifyVersion(pkgname, version): + if (version == self.packages[pkgname].getVersion()): + logging.error("errore, il pacchetto " + pkgname + " ha versione identica a quello attualmente installato") + return 3 + else: + logging.info("errore, il pacchetto " + pkgname + " ha versione precendente a quello attualmente installato") + return 2 + else: + + os.system('unzip -o ' + './updatePackages/' + filename + " -d ./updatePackages") + + os.system('mkdir ' + pkgpath) + os.system('mv ./updatePackages/' + pkgname + "/" + 'audio.wav ' + pkgpath + '/') + + with open('./updatePackages/' + pkgname + '/' + pkgname + '.json') as json_file: + logging.info("adding " + pkgname + " package") + data = json.load(json_file) + for p in data['packages']: + package = data['packages'][p] + mp = MusicPackage(p,package['category'],package['name_IT'],package['name_EN'],package['version'],package['date']) + for i in package['interface']: + interfaceItem = package['interface'][i] + mpi = MusicPackageInterface(i,interfaceItem['available'],interfaceItem['icon']) + mp.addInterface(mpi) + + self.packages[p] = mp + + self.updatePackages() + + os.system('rm -rf ./updatePackages/' + pkgname) + return 1 + + + def isPackageAvailable(self,namePackage): + if namePackage in self.packages: + return True + else: + return False diff --git a/photos/0.txt b/photos/0.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/photos/metadata.json b/photos/metadata.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/photos/metadata.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/program.py b/program.py index b66efde9..15629115 100644 --- a/program.py +++ b/program.py @@ -31,13 +31,16 @@ import config import audio import event - - +import music +import musicPackages +import atmega328p PROGRAM_PATH = "./data/" PROGRAM_PREFIX = "program_" PROGRAM_SUFFIX = ".json" +musicPackageManager = musicPackages.MusicPackageManager.get_instance() + def get_cam(): return camera.Camera.get_instance() @@ -56,6 +59,12 @@ def get_prog_eng(): def get_event(): return event.EventManager.get_instance() +def get_music(): + return music.Music.get_instance(musicPackageManager) + +def get_atmega(): + return atmega328p.ATMega328.get_instance() + class ProgramEngine: # pylint: disable=exec-used @@ -105,7 +114,8 @@ def load(self, name): logging.info(program_db_entries[0]) f = open(program_db_entries[0]["filename"], 'r') self._program = Program.from_dict(json.load(f)) - return self._program + return self._program + return None def delete(self, name): query = Query() @@ -131,6 +141,12 @@ def log(self, text): def get_log(self): return self._log + def set_log(self, log): + self._log = "" + + def get_current_program(self): + return self._program + class Program: _running = False @@ -139,21 +155,20 @@ def dom_code(self): return self._dom_code def __init__(self, name, code=None, dom_code=None, default=False): - #super(Program, self).__init__() self._thread = None self.name = name self._dom_code = dom_code self._code = code self._default = default - def execute(self): + def execute(self, options={}): if self._running: raise RuntimeError('already running') + ProgramEngine.get_instance().set_log("") self._running = True - try: - self._thread = threading.Thread(target=self.run) + self._thread = threading.Thread(target=self.run, args=(options,)) self._thread.start() except RuntimeError as re: logging.error("RuntimeError: %s", str(re)) @@ -178,15 +193,16 @@ def is_running(self): def is_default(self): return self._default - def run(self): + def run(self, *args): + options = args[0] try: program = self try: - if config.Config.get().get("prog_video_rec") == "true": - get_cam().video_rec(program.name) + if options.get("autoRecVideo") == True: + get_cam().video_rec(program.name.replace(" ", "_")) logging.debug("starting video") - except Exception: - logging.error("Camera not available") + except Exception as e: + logging.error("Camera not available: " + str(e)) self._log = "" #clear log imports = "import json\n" diff --git a/requirements_stub.txt b/requirements_stub.txt index e8301ef3..6fd067b3 100644 --- a/requirements_stub.txt +++ b/requirements_stub.txt @@ -1,47 +1,49 @@ # Basic set of required packages. If you need to run on a real system (NOT in STUB mode) # install the packages in `requirements.txt` too. -absl-py==0.9.0 +absl-py==1.0.0 astor==0.8.1 -Babel==2.8.0 -certifi==2018.4.16 +Babel==2.10.1 +certifi==2022.5.18.1 chardet==3.0.4 -click==7.0 -clickclick==1.2.2 -connexion==1.4.2 -Flask==1.1.1 -Flask-Babel==0.12.2 -Flask-Cors==3.0.8 -gast==0.2.2 -grpcio==1.26.0 -idna==2.8 -pybind11==2.4.3 -inflection==0.3.1 -itsdangerous==0.24 -Jinja2==2.11.1 -jsonschema==2.6.0 -Markdown==3.1.1 -MarkupSafe==1.1.1 -numpy==1.17.4 -opencv-contrib-python==4.1.1.26 -pigpio==1.45 -Pillow==7.0.0 -protobuf==3.11.3 -Pypubsub==4.0.0 -pytz==2018.4 -pyyaml>=4.2b1 -pyzbar==0.1.7 -requests==2.22.0 -six==1.14.0 -swagger-spec-validator==2.3.1 +click==8.1.3 +clickclick==20.10.2 +connexion==2.13.1 +Flask==2.1.2 +Flask-Babel==2.0.0 +Flask-Cors==3.0.10 +gast==0.5.3 +grpcio==1.46.3 +idna==3.3 +pybind11==2.9.2 +inflection==0.5.1 +itsdangerous==2.1.2 +Jinja2==3.1.2 +jsonschema==4.5.1 +Markdown==3.3.7 +MarkupSafe==2.1.1 +numpy==1.22.4 +opencv-contrib-python==4.5.3.56 +pigpio==1.78 +Pillow==9.1.1 +protobuf==4.21.1 +Pypubsub==4.0.3 +pytz==2022.1 +pyyaml==6.0 +pyzbar==0.1.9 +requests==2.27.1 +six==1.16.0 +swagger-spec-validator==2.7.4 termcolor==1.1.0 -tinydb==3.12.1 -tensorflow==2.1.0 -tensorflow_hub>=0.7.0 -urllib3==1.24.2 -Werkzeug==0.15.3 -setuptools==42.0.1 -smbus2==0.3.0 -spidev==3.4 -cachetools==3.0.0 -pytesseract==0.3.4 +tinydb==4.7.0 +tflite_runtime==2.8.0 +urllib3==1.26.9 +Werkzeug==2.1.2 +setuptools==62.3.2 +smbus2==0.4.1 +spidev==3.5 +cachetools==5.1.0 +sox==1.4.1 +pyalsaaudio==0.9.2 +pytesseract==0.3.9 +sox==1.4.1 diff --git a/sounds/buzz.mp3 b/sounds/buzz.mp3 deleted file mode 100644 index e28d0f69..00000000 Binary files a/sounds/buzz.mp3 and /dev/null differ diff --git a/sounds/buzz.wav b/sounds/buzz.wav new file mode 100644 index 00000000..dae05b95 Binary files /dev/null and b/sounds/buzz.wav differ diff --git a/sounds/i-see-you.mp3 b/sounds/i-see-you.mp3 deleted file mode 100644 index 9965ad52..00000000 Binary files a/sounds/i-see-you.mp3 and /dev/null differ diff --git a/sounds/i-see-you.wav b/sounds/i-see-you.wav new file mode 100644 index 00000000..a3ab3021 Binary files /dev/null and b/sounds/i-see-you.wav differ diff --git a/sounds/notes/cat/audio.wav b/sounds/notes/cat/audio.wav new file mode 100644 index 00000000..eb20db75 Binary files /dev/null and b/sounds/notes/cat/audio.wav differ diff --git a/sounds/notes/dinosaur.wav b/sounds/notes/dinosaur.wav new file mode 100644 index 00000000..4fe28f48 Binary files /dev/null and b/sounds/notes/dinosaur.wav differ diff --git a/sounds/notes/dog/audio.wav b/sounds/notes/dog/audio.wav new file mode 100644 index 00000000..fa3e4e67 Binary files /dev/null and b/sounds/notes/dog/audio.wav differ diff --git a/sounds/notes/duck/audio.wav b/sounds/notes/duck/audio.wav new file mode 100644 index 00000000..ee416bc0 Binary files /dev/null and b/sounds/notes/duck/audio.wav differ diff --git a/sounds/notes/elephant/audio.wav b/sounds/notes/elephant/audio.wav new file mode 100644 index 00000000..7d313846 Binary files /dev/null and b/sounds/notes/elephant/audio.wav differ diff --git a/sounds/notes/flute/audio.wav b/sounds/notes/flute/audio.wav new file mode 100644 index 00000000..1e14fa57 Binary files /dev/null and b/sounds/notes/flute/audio.wav differ diff --git a/sounds/notes/guitar/audio.wav b/sounds/notes/guitar/audio.wav new file mode 100644 index 00000000..673b5127 Binary files /dev/null and b/sounds/notes/guitar/audio.wav differ diff --git a/sounds/notes/music_package.json b/sounds/notes/music_package.json new file mode 100644 index 00000000..d876c5c3 --- /dev/null +++ b/sounds/notes/music_package.json @@ -0,0 +1,193 @@ +{ + "packages": { + "piano": { + "category": "instrument", + "name_IT": "pianoforte", + "name_EN": "piano", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "piano.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "piano.png" + }, + "advanced": { + "available": "TRUE", + "icon": "piano.png" + } + } + }, + "guitar": { + "category": "instrument", + "name_IT": "chitarra", + "name_EN": "guitar", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "guitar.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "guitar.png" + }, + "advanced": { + "available": "TRUE", + "icon": "guitar.png" + } + } + }, + "flute": { + "category": "instrument", + "name_IT": "flauto", + "name_EN": "flute", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "flute.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "flute.png" + }, + "advanced": { + "available": "TRUE", + "icon": "flute.png" + } + } + }, + "cat": { + "category": "animal", + "name_IT": "gatto", + "name_EN": "cat", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "cat.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "cat.png" + }, + "advanced": { + "available": "TRUE", + "icon": "cat.png" + } + } + }, + "dog": { + "category": "animal", + "name_IT": "cane", + "name_EN": "dog", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "dog.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "dog.png" + }, + "advanced": { + "available": "TRUE", + "icon": "dog.png" + } + } + }, + "pig": { + "category": "animal", + "name_IT": "maiale", + "name_EN": "pig", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "pig.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "pig.png" + }, + "advanced": { + "available": "TRUE", + "icon": "pig.png" + } + } + }, + "elephant": { + "category": "animal", + "name_IT": "elefante", + "name_EN": "elephant", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "elephant.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "elephant.png" + }, + "advanced": { + "available": "TRUE", + "icon": "elephant.png" + } + } + }, + "snake": { + "category": "animal", + "name_IT": "serpente", + "name_EN": "snake", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "snake.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "snake.png" + }, + "advanced": { + "available": "TRUE", + "icon": "snake.png" + } + } + }, + "duck": { + "category": "animal", + "name_IT": "anatra", + "name_EN": "duck", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "duck.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "duck.png" + }, + "advanced": { + "available": "TRUE", + "icon": "duck.png" + } + } + } + } +} diff --git a/sounds/notes/piano/audio.wav b/sounds/notes/piano/audio.wav new file mode 100644 index 00000000..5eaabb67 Binary files /dev/null and b/sounds/notes/piano/audio.wav differ diff --git a/sounds/notes/pig/audio.wav b/sounds/notes/pig/audio.wav new file mode 100644 index 00000000..63ddcf4c Binary files /dev/null and b/sounds/notes/pig/audio.wav differ diff --git a/sounds/notes/snake/audio.wav b/sounds/notes/snake/audio.wav new file mode 100644 index 00000000..39775e14 Binary files /dev/null and b/sounds/notes/snake/audio.wav differ diff --git a/sounds/phaser.mp3 b/sounds/phaser.mp3 deleted file mode 100644 index 632a307b..00000000 Binary files a/sounds/phaser.mp3 and /dev/null differ diff --git a/sounds/phaser.wav b/sounds/phaser.wav new file mode 100644 index 00000000..bc3a59ff Binary files /dev/null and b/sounds/phaser.wav differ diff --git a/sounds/scanner.mp3 b/sounds/scanner.mp3 deleted file mode 100644 index 534647f3..00000000 Binary files a/sounds/scanner.mp3 and /dev/null differ diff --git a/sounds/scanner.wav b/sounds/scanner.wav new file mode 100644 index 00000000..9212f29a Binary files /dev/null and b/sounds/scanner.wav differ diff --git a/sounds/shutdown.mp3 b/sounds/shutdown.mp3 deleted file mode 100644 index 0039e395..00000000 Binary files a/sounds/shutdown.mp3 and /dev/null differ diff --git a/sounds/shutdown.wav b/sounds/shutdown.wav new file mode 100644 index 00000000..73627427 Binary files /dev/null and b/sounds/shutdown.wav differ diff --git a/sounds/shutter.mp3 b/sounds/shutter.mp3 deleted file mode 100644 index b376d7c3..00000000 Binary files a/sounds/shutter.mp3 and /dev/null differ diff --git a/sounds/shutter.wav b/sounds/shutter.wav new file mode 100644 index 00000000..96bc89c6 Binary files /dev/null and b/sounds/shutter.wav differ diff --git a/sounds/startup.mp3 b/sounds/startup.mp3 deleted file mode 100644 index a33852fe..00000000 Binary files a/sounds/startup.mp3 and /dev/null differ diff --git a/sounds/startup.wav b/sounds/startup.wav new file mode 100644 index 00000000..707ace6b Binary files /dev/null and b/sounds/startup.wav differ diff --git a/sounds/still-there.mp3 b/sounds/still-there.mp3 deleted file mode 100644 index 9bd92881..00000000 Binary files a/sounds/still-there.mp3 and /dev/null differ diff --git a/sounds/still-there.wav b/sounds/still-there.wav new file mode 100644 index 00000000..35c2f01b Binary files /dev/null and b/sounds/still-there.wav differ diff --git a/sounds/there-you-are.mp3 b/sounds/there-you-are.mp3 deleted file mode 100644 index 92165bc8..00000000 Binary files a/sounds/there-you-are.mp3 and /dev/null differ diff --git a/sounds/there-you-are.wav b/sounds/there-you-are.wav new file mode 100644 index 00000000..bf18c808 Binary files /dev/null and b/sounds/there-you-are.wav differ diff --git a/start.sh b/start.sh index a8ab92dd..b628acf2 100755 --- a/start.sh +++ b/start.sh @@ -1,2 +1,3 @@ #!/bin/bash -LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1.2.0 python3 init.py +[[ -d "firmware" ]] && [[ ! -f "firmware/initialised" ]] && source firmware/upload.sh +AUDIODEV=hw:1 LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1.2.0 python3 init.py diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 5dd1c305..3602c871 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -1206,3 +1206,115 @@ Blockly.Python['coderbot_mpu_get_temp'] = function(block) { var code = 'get_bot().get_mpu_temp()'; return [code, Blockly.Python.ORDER_ATOMIC]; }; + +Blockly.Blocks['coderbot_atmega_get_input'] = { + /** + * Block for get_input function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); + this.setColour(240); + this.appendDummyInput() + .appendField(Blockly.Msg.CODERBOT_ATMEGA_READ) + .appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_ATMEGA_AI_1, "0"], + [Blockly.Msg.CODERBOT_ATMEGA_AI_2, "1"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_3, "2"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_4, "3"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_5, "4"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_6, "5"],]), 'INPUT'); + this.setOutput(true, 'Number'); + this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); + } +}; + +Blockly.Python['coderbot_atmega_get_input'] = function(block) { + // input index: 0, 1 are Analogs, 2..5 are Digital + var input = block.getFieldValue('INPUT'); + var code = 'get_atmega().get_input(' + input + ')'; + return [code, Blockly.Python.ORDER_ATOMIC]; +}; + +Blockly.Blocks['coderbot_atmega_set_output'] = { + /** + * Block for set_output function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); + this.setColour(240); + this.appendDummyInput() + .appendField(Blockly.Msg.CODERBOT_ATMEGA_WRITE) + .appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_ATMEGA_DO_1, "0"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_2, "1"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_3, "2"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_4, "3"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_5, "4"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_6, "5"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_7, "6"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_8, "7"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_9, "8"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_10, "9"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_11, "10"],]), 'OUTPUT'); + this.appendValueInput('VALUE') + .setCheck('Boolean') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_VALUE); + this.setInputsInline(true); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); + } +}; + +Blockly.Python['coderbot_atmega_set_output'] = function(block) { + // input index: 0, 10 are Digital + var output = block.getFieldValue('OUTPUT'); + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '\'\''; + var code = 'get_atmega().set_output(' + output + ', ' + value + ')\n'; + return code; +}; + +Blockly.Blocks['coderbot_atmega_set_led'] = { + /** + * Block for set_output function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); + this.setColour(240); + this.appendDummyInput() + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_SET) + this.appendValueInput('BEGIN') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_BEGIN); + this.appendValueInput('END') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_END); + this.appendValueInput('RED') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_RED); + this.appendValueInput('GREEN') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_GREEN); + this.appendValueInput('BLUE') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_BLUE); + this.setInputsInline(true); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); + } +}; + +Blockly.Python['coderbot_atmega_set_led'] = function(block) { + // input index: 0, 10 are Digital + var begin = Blockly.Python.valueToCode(block, 'BEGIN', Blockly.Python.ORDER_NONE); + var end = Blockly.Python.valueToCode(block, 'END', Blockly.Python.ORDER_NONE); + var red = Blockly.Python.valueToCode(block, 'RED', Blockly.Python.ORDER_NONE); + var green = Blockly.Python.valueToCode(block, 'GREEN', Blockly.Python.ORDER_NONE); + var blue = Blockly.Python.valueToCode(block, 'BLUE', Blockly.Python.ORDER_NONE); + var code = 'get_atmega().set_led(' + begin + ', ' + end + ', ' + red + ', ' + green + ', ' + blue + ')\n'; + + return code; +}; diff --git a/static/js/blockly/bot_en.js b/static/js/blockly/bot_en.js index bc436856..31d77c4c 100644 --- a/static/js/blockly/bot_en.js +++ b/static/js/blockly/bot_en.js @@ -97,4 +97,24 @@ Blockly.Msg.CODERBOT_EVENT_PUBLISH = "publish"; Blockly.Msg.CODERBOT_EVENT_ON_TOPIC = "on topic"; Blockly.Msg.CODERBOT_EVENT_GENERATOR = "event generator"; Blockly.Msg.CODERBOT_CONVERSATION_PARSE = "parse"; +Blockly.Msg.CODERBOT_ATMEGA_READ = "Read"; +Blockly.Msg.CODERBOT_ATMEGA_VALUE = "Value"; +Blockly.Msg.CODERBOT_ATMEGA_AI_1 = "Analog Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_AI_2 = "Analog Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_3 = "Digital Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_DI_4 = "Digital Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_5 = "Digital Input3"; +Blockly.Msg.CODERBOT_ATMEGA_DI_6 = "Digital Input 4"; +Blockly.Msg.CODERBOT_ATMEGA_WRITE = "Write"; +Blockly.Msg.CODERBOT_ATMEGA_DO_1 = "Digital Output 1"; +Blockly.Msg.CODERBOT_ATMEGA_DO_2 = "Digital Output 2"; +Blockly.Msg.CODERBOT_ATMEGA_DO_3 = "Digital Output 3"; +Blockly.Msg.CODERBOT_ATMEGA_DO_4 = "Digital Output 4"; +Blockly.Msg.CODERBOT_ATMEGA_DO_5 = "Digital Output 5"; +Blockly.Msg.CODERBOT_ATMEGA_DO_6 = "Digital Output 6"; +Blockly.Msg.CODERBOT_ATMEGA_DO_7 = "Digital Output 7"; +Blockly.Msg.CODERBOT_ATMEGA_DO_8 = "Digital Output 8"; +Blockly.Msg.CODERBOT_ATMEGA_DO_9 = "Digital Output 9"; +Blockly.Msg.CODERBOT_ATMEGA_DO_10 = "Digital Output 10"; +Blockly.Msg.CODERBOT_ATMEGA_DO_11 = "Digital Output 11"; diff --git a/static/js/blockly/bot_it.js b/static/js/blockly/bot_it.js index 0d7c70e2..d8492b0a 100644 --- a/static/js/blockly/bot_it.js +++ b/static/js/blockly/bot_it.js @@ -97,3 +97,29 @@ Blockly.Msg.CODERBOT_EVENT_PUBLISH = "pubblica"; Blockly.Msg.CODERBOT_EVENT_ON_TOPIC = "sul topic"; Blockly.Msg.CODERBOT_EVENT_GENERATOR = "genera eventi"; Blockly.Msg.CODERBOT_CONVERSATION_PARSE = "interpreta"; +Blockly.Msg.CODERBOT_ATMEGA_READ = "Leggi"; +Blockly.Msg.CODERBOT_ATMEGA_VALUE = "Valore"; +Blockly.Msg.CODERBOT_ATMEGA_AI_1 = "Analog Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_AI_2 = "Analog Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_3 = "Digital Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_DI_4 = "Digital Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_5 = "Digital Input3"; +Blockly.Msg.CODERBOT_ATMEGA_DI_6 = "Digital Input 4"; +Blockly.Msg.CODERBOT_ATMEGA_WRITE = "Scrivi"; +Blockly.Msg.CODERBOT_ATMEGA_DO_1 = "Digital Output 1"; +Blockly.Msg.CODERBOT_ATMEGA_DO_2 = "Digital Output 2"; +Blockly.Msg.CODERBOT_ATMEGA_DO_3 = "Digital Output 3"; +Blockly.Msg.CODERBOT_ATMEGA_DO_4 = "Digital Output 4"; +Blockly.Msg.CODERBOT_ATMEGA_DO_5 = "Digital Output 5"; +Blockly.Msg.CODERBOT_ATMEGA_DO_6 = "Digital Output 6"; +Blockly.Msg.CODERBOT_ATMEGA_DO_7 = "Digital Output 7"; +Blockly.Msg.CODERBOT_ATMEGA_DO_8 = "Digital Output 8"; +Blockly.Msg.CODERBOT_ATMEGA_DO_9 = "Digital Output 9"; +Blockly.Msg.CODERBOT_ATMEGA_DO_10 = "Digital Output 10"; +Blockly.Msg.CODERBOT_ATMEGA_DO_11 = "Digital Output 11"; +Blockly.Msg.CODERBOT_ATMEGA_LED_SET = "Controlla Led"; +Blockly.Msg.CODERBOT_ATMEGA_LED_BEGIN = "Led inizio"; +Blockly.Msg.CODERBOT_ATMEGA_LED_END = "Led fine"; +Blockly.Msg.CODERBOT_ATMEGA_LED_RED = "Intensità Rosso"; +Blockly.Msg.CODERBOT_ATMEGA_LED_GREEN = "Intensità Verde"; +Blockly.Msg.CODERBOT_ATMEGA_LED_BLUE = "Intensità Blu"; diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index 2f2a2030..4a3f3c70 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -331,6 +331,11 @@ + + + + + diff --git a/test/musicPackage_test.py b/test/musicPackage_test.py new file mode 100644 index 00000000..65f4823f --- /dev/null +++ b/test/musicPackage_test.py @@ -0,0 +1,41 @@ +#__import__("../musicPackages") +import json +import sys +sys.path.insert(0, './') +import musicPackages +class MusicPackage_test: + + def test_musicPackage(self): + print("sample Music Package: ") + print(" name_IT = name_it, name_EN = name_en , category = sample_category, version = sample_version, date = sample_date, interfaces = sample_interfaces, nameID = sample_id") + + mpkg = musicPackages.MusicPackage(name_IT = "name_it", name_EN = "name_en", category= "sample_category", version= "sample_version", date="sample_date", nameID="sample_id") + print("name_IT : ", mpkg.getNameIT()) + print("name_EN : ", mpkg.getNameEN()) + print("nameID : ", mpkg.getNameID()) + print("version : ", mpkg.getVersion()) + print("date : ", mpkg.getDate()) + print("category : ", mpkg.getCategory()) + print("interfaces : ", mpkg.getInterfaces()) + + def test_isPackageAvaible(self): + pkg_manager = musicPackages.MusicPackageManager() + for package_name in pkg_manager.packages: + print("Test if " + package_name + " package is available") + result = pkg_manager.isPackageAvailable(package_name) + if(result): + print(package_name + " package is available") + else: + print(package_name + " package is not available") + + print("Test if NONE package is available" ) + result = pkg_manager.isPackageAvailable("NONE") + if(result): + print("NONE package is available") + else: + print("NONE package is not available") + +test = MusicPackage_test() +test.test_musicPackage() +test.test_isPackageAvaible() + diff --git a/test/music_test.py b/test/music_test.py new file mode 100644 index 00000000..b73a6b1b --- /dev/null +++ b/test/music_test.py @@ -0,0 +1,116 @@ +import sys +import sox +import time +import os +sys.path.insert(0, './') +from musicPackages import MusicPackageManager +from music import Music + +class Music_test: + + def test_library(self): + print("testing sound playback:...") + tfm = sox.Transformer() + tfm.preview('cat.wav') + tfm.build('cat.wav', 'outMusicDemo.wav') + +# test each parametr of the function play_note + def test_play_note(self): + musicPkg = MusicPackageManager() + m = Music(musicPkg) + print('test Music.play_note') + print("m.play_note(note='C2')") + m.play_note(note='C2') + print("m.play_note(note='C2',duration=2.0)") + m.play_note(note='C2',duration=2.0) + print("m.play_note(note='C2',instrument='guitar')") + m.play_note(note='C2',instrument='guitar') + print("m.play_note(note='C2',alteration='bmolle')") + m.play_note(note='C2',alteration='bmolle') + print("m.play_note(note='C2',alteration='diesis')") + m.play_note(note='C2',alteration='diesis') + print("m.play_note(note='C2',instrument='guitar')") + m.play_note(note='C2',instrument='guitar') + print("m.play_note(note='C2',alteration='bmolle')") + m.play_note(note='C2',alteration='bmolle') + print("m.play_note(note='C2',instrument='guitar',alteration='diesis')") + m.play_note(note='C2',instrument='guitar',alteration='diesis') + print("m.play_note(note='C2',instrument='guitar',alteration='diesis',duration=2.0)") + m.play_note(note='C2',instrument='guitar',alteration='diesis',duration=2.0) + print("m.play_note(note='G3',duration=2.0)") + m.play_note(note='G3',duration=2.0) + print("m.play_note(note='G3',instrument='guitar')") + m.play_note(note='G3',instrument='guitar') + print("m.play_note(note='G3',alteration='bmolle')") + m.play_note(note='G3',alteration='bmolle') + print("m.play_note(note='G3',alteration='diesis')") + m.play_note(note='G3',alteration='diesis') + print("m.play_note(note='G3',instrument='guitar')") + m.play_note(note='G3',instrument='guitar') + print("m.play_note(note='G3',alteration='bmolle')") + m.play_note(note='G3',alteration='bmolle') + print("m.play_note(note='G3',instrument='guitar',alteration='diesis')") + m.play_note(note='G3',instrument='guitar',alteration='diesis') + print("m.play_note(note='G3',instrument='guitar',alteration='diesis',duration=2.0)") + m.play_note(note='G3',instrument='guitar',alteration='diesis',duration=2.0) + print("it's ok if print: no instrument: coderInstrument present in this coderbot!") + m.play_note(note='C2',instrument='coderInstrument',alteration='diesis',duration=2.0) + print("it's ok if print: note: coderNote not exist") + m.play_note(note='coderNote',instrument='piano',alteration='diesis',duration=2.0) + + +# test each parametr of the function play_note + def test_play_animal(self): + print('test Music.play_animal') + musicPkg = MusicPackageManager() + m = Music(musicPkg) + print("(note='C2',instrument='cat', duration=2.0)") + m.play_animal(note='C2',instrument='cat', duration=2.0) + print("m.play_animal(note='C2',instrument='dog')") + m.play_animal(note='C2',instrument='dog') + print("m.play_animal(note='C2',instrument='dog', alteration='bmolle')") + m.play_animal(note='C2',instrument='dog', alteration='bmolle') + print("m.play_animal(note='C2',instrument='cat', alteration='diesis')") + m.play_animal(note='C2',instrument='cat', alteration='diesis') + print("m.play_animal(note='C2',instrument='dinosaur')") + m.play_animal(note='C2',instrument='dinosaur') + print("m.play_animal(note='C2',alteration='bmolle')") + m.play_animal(note='C2',instrument="dinosaur", alteration='bmolle') + print("m.play_animal(note='C2',instrument='cat',alteration='diesis')") + m.play_animal(note='C2',instrument='cat',alteration='diesis') + print("m.play_animal(note='C2',instrument='cat',alteration='diesis',duration=2.0)") + m.play_animal(note='C2',instrument='cat',alteration='diesis',duration=2.0) + print("m.play_note(note='G3',duration=2.0)") + m.play_note(note='G3',duration=2.0) + print("m.play_note(note='G3',instrument='dinosaur',alteration='bmolle')") + m.play_note(note='G3',instrument='dinosaur',alteration='bmolle') + print("m.play_note(note='G3',instrument='dinosaur',alteration='diesis')") + m.play_note(note='G3',instrument='dinosaur',alteration='diesis') + print("m.play_note(note='G3',alteration='bmolle', instrument= 'cat')") + m.play_note(note='G3',alteration='bmolle', instrument= 'cat') + print("m.play_note(note='G3',instrument='cat',alteration='diesis')") + m.play_note(note='G3',instrument='cat',alteration='diesis') + print("m.play_note(note='G3',instrument='cat',alteration='diesis',duration=2.0)") + m.play_note(note='G3',instrument='cat',alteration='diesis',duration=2.0) + print("it's ok if print: no instrument: coderInstrument present in this coderbot!") + m.play_animal(note='C2',instrument='coderInstrument',alteration='diesis',duration=2.0) + print("it's ok if print: note: coderNote not exist") + m.play_animal(note='coderNote',instrument='cat',alteration='diesis',duration=2.0) + print('test Music.play_note: ENDED') + + + def test_play_pause(self): + print('test Music.play_pause') + musicPkg = MusicPackageManager() + m = Music() + prrint("play pause") + m.play_pause(1.0) + prrint("play pause and note") + m.play_note(note='C2',instrument='guitar') + m.play_pause(2.0) + m.play_note(note='C2',instrument='guitar') + +test = Music_test() +test.test_play_note() +test.test_play_animal() + diff --git a/v2.yml b/v2.yml index 803cac67..b09d8871 100644 --- a/v2.yml +++ b/v2.yml @@ -1,6 +1,6 @@ swagger: "2.0" info: - version: "0.2" + version: "0.3" title: OpenAPI 2.0 definition of Coderbot API v2 consumes: @@ -74,9 +74,35 @@ paths: description: "ok" 400: description: "Failed to save the activity" + /listMusicPackages: + get: + operationId: "api.listMusicPackages" + summary: "List Music Packages" + responses: + 200: + description: "ok" + /deleteMusicPackage: + post: + operationId: "api.deleteMusicPackage" + summary: "Delete Music Package" + parameters: + - name: package_data + in: body + schema: + type: object + properties: + package_name: + type: string + responses: + 200: + description: "ok" + 400: + description: "not found" + /updateFromPackage: post: operationId: "api.updateFromPackage" + summary: "Update CoderBot from package" consumes: - multipart/form-data parameters: @@ -120,7 +146,11 @@ paths: - name: name in: query type: string - required: true + required: false + - name: default + in: query + type: string + required: false tags: - Activity management responses: @@ -133,10 +163,6 @@ paths: tags: - Program management parameters: - - name: overwrite - in: query - required: false - type: string - in: body name: data schema: @@ -191,7 +217,7 @@ paths: description: Components names to be tested schema: type: object - default: {'varargin': ['motors', 'sonar', 'speaker', 'ocr']} + # default: {'varargin': ['motors', 'sonar', 'speaker', 'ocr']} required: - varargin properties: @@ -232,7 +258,7 @@ paths: description: Movement speed and duration schema: type: object - default: {'speed': 100, 'elapse':0, 'distance':0} + # default: {'speed': 100, 'elapse':0, 'distance':0} required: - speed - elapse @@ -276,3 +302,13 @@ paths: responses: 200: description: Sent command to the bot GPIO. + /listCNNModels: + get: + operationId: "api.list_cnn_models" + summary: "list of CNN Models" + tags: + - CNN Models + responses: + 200: + description: "CNN Models as JSON Object" + diff --git a/wifi.py b/wifi.py index 735db240..3bc5101e 100755 --- a/wifi.py +++ b/wifi.py @@ -91,7 +91,7 @@ def get_ipaddr(cls, ifname): return socket.inet_ntoa(fcntl.ioctl( s.fileno(), 0x8915, # SIOCGIFADDR - struct.pack('256s', ifname[:15]) + struct.pack('256s', ifname.encode('utf-8')[:15]) )[20:24]) @classmethod @@ -153,7 +153,11 @@ def start_as_client(cls): time.sleep(1.0) out = os.system("wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf > /dev/null 2>&1") out += os.system("dhclient -1 wlan0") - print(out) + print("start_as_client: " + str(out)) + ipaddr = cls.get_ipaddr("wlan0") + if ipaddr is None or "169.254" in ipaddr: + os.system("sudo pkill wpa_supplicant") + raise Exception() try: cls.register_ipaddr(cls.get_macaddr("wlan0"), cls.get_config().get('bot_name', 'CoderBot'), cls.get_ipaddr("wlan0"), "roberto.previtera@gmail.com") print("registered bot, ip: " + str(cls.get_ipaddr("wlan0") + " name: " + cls.get_config().get('bot_name', 'CoderBot'))) @@ -175,7 +179,7 @@ def start_as_ap(cls): out += str(subprocess.check_output(["ip", "a", "add", "10.0.0.1/24", "dev", "wlan0"])) out += str(subprocess.check_output(["ip", "link", "set", "dev", "wlan0", "up"])) out += str(subprocess.check_output(["ifconfig"])) - print(out) + print("start_as_ap: " + str(out)) cls.start_hostapd() cls.start_dnsmasq()