From 54ad8fc623747a6dd3e68d55431e4271705ad371 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Thu, 25 Jun 2015 16:04:00 +0100 Subject: [PATCH 01/53] Added error handling for when raspicamera is not available during init. --- camera.py | 32 +++++++++++++++++++--- init.py | 3 ++ main.py | 2 +- scripts/coderbot | 71 ++++++++++++++++++++++++------------------------ 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/camera.py b/camera.py index 3dffd619..81a826b1 100644 --- a/camera.py +++ b/camera.py @@ -14,6 +14,7 @@ CAMERA_REFRESH_INTERVAL=0.1 MAX_IMAGE_AGE = 0.0 PHOTO_PATH = "./photos" +DEFAULT_IMAGE = "./photos/broken.jpg" PHOTO_PREFIX = "DSC" VIDEO_PREFIX = "VID" PHOTO_THUMB_SUFFIX = "_thumb" @@ -39,7 +40,14 @@ def get_instance(cls): def __init__(self): logging.info("starting camera") cam_props = {"width":640, "height":480, "exposure_mode": config.Config.get().get("camera_exposure_mode")} - self._camera = camera.Camera(props=cam_props) + #try initialising the camera + try: + self._camera = camera.Camera(props=cam_props) + except: + self._camera = None + logging.error("Unexpected error:" + str(sys.exc_info()[0])) + pass + self._streamer = streamer.JpegStreamer("0.0.0.0:"+str(self.stream_port), st=0.1) #self._cam_off_img.save(self._streamer) self.recording = False @@ -58,6 +66,8 @@ def __init__(self): super(Camera, self).__init__() def run(self): + if self._camera is None: + return try: self._camera.grab_start() while self._run: @@ -84,13 +94,18 @@ def run(self): raise def get_image(self, maxage = MAX_IMAGE_AGE): - return image.Image(self._camera.get_image_bgr()) + if self._camera is None: + return Image(cv2.imread(DEFAULT_IMAGE)) + else: + return image.Image(self._camera.get_image_bgr()) def save_image(self, image_jpeg): self._streamer.set_image(image_jpeg) self._image_time=time.time() def set_text(self, text): + if self._camera is None: + return self._camera.set_overlay_text(str(text)) def get_next_photo_index(self): @@ -105,6 +120,8 @@ def get_next_photo_index(self): return last_photo_index + 1 def photo_take(self): + if self._camera is None: + return photo_index = self.get_next_photo_index() filename = PHOTO_PREFIX + str(photo_index) + self._camera.PHOTO_FILE_EXT; filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; @@ -124,7 +141,9 @@ def video_rec(self, video_name=None): if self.is_recording(): return self.recording = True - + if self._camera is None: + return + if video_name is None: video_index = self.get_next_photo_index() filename = VIDEO_PREFIX + str(video_index) + self._camera.VIDEO_FILE_EXT; @@ -146,6 +165,8 @@ def video_rec(self, video_name=None): self.video_start_time = time.time() def video_stop(self): + if self._camera is None: + return if self.recording: self._camera.video_stop() self.recording = False @@ -162,7 +183,8 @@ def get_photo_thumb_file(self, filename): def delete_photo(self, filename): logging.info("delete photo: " + filename) os.remove(PHOTO_PATH + "/" + filename) - os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT) + if self._camera is not None: + os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT) self._photos.remove(filename) def exit(self): @@ -170,6 +192,8 @@ def exit(self): self.join() def calibrate(self): + if self._camera is None: + return img = self._camera.getImage() self._background = img.hueHistogram()[-1] diff --git a/init.py b/init.py index cc459059..ef291d7c 100755 --- a/init.py +++ b/init.py @@ -2,6 +2,9 @@ import coderbot import main +import os + +os.chdir("/home/pi/coderbot/") if __name__=="__main__": main.run_server() diff --git a/main.py b/main.py index 8858e5b4..b8b0c94b 100644 --- a/main.py +++ b/main.py @@ -39,7 +39,7 @@ def get_locale(): # header the browser transmits. loc = request.accept_languages.best_match(['it', 'en']) if loc is None: - loc = 'it' + loc = 'en' return loc @app.route("/") diff --git a/scripts/coderbot b/scripts/coderbot index 2d0f3d1a..0c481379 100755 --- a/scripts/coderbot +++ b/scripts/coderbot @@ -1,58 +1,57 @@ #!/bin/sh - + ### BEGIN INIT INFO -# Provides: myservice -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: CoderBot service -# Description: Provide CoderBot Service web application +# Provides: coderbot +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Web-based Blockly Interface for Pi +# Description: Web-based Blockly Interface for programming the Pi, goto 10.0.0.10:8080 ### END INIT INFO - -# Change the next 3 lines to suit where you install your script and what you want to call it + +# Location of daemon python script DIR=/home/pi/coderbot DAEMON=$DIR/init.py DAEMON_NAME=coderbot - -# This next line determines what user the script runs as. -# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python. -DAEMON_USER=pi - -# The process ID of the script when it runs is stored here: + +# Put your command-line options here +DAEMON_OPTS="" + +# Must start as root to use GPIO +DAEMON_USER=root + +# The process ID of the script when it runs is stored here PIDFILE=/var/run/$DAEMON_NAME.pid - + . /lib/lsb/init-functions - -do_start () { + +do_start() { log_daemon_msg "Starting system $DAEMON_NAME daemon" - start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chdir $DIR --chuid $DAEMON_USER --exec /usr/bin/python -- ./init.py + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS log_end_msg $? } -do_stop () { + +do_stop() { log_daemon_msg "Stopping system $DAEMON_NAME daemon" start-stop-daemon --stop --pidfile $PIDFILE --retry 10 log_end_msg $? } - + case "$1" in - start|stop) - do_${1} - ;; - + do_${1} + ;; restart|reload|force-reload) - do_stop - do_start - ;; - + do_stop + do_start + ;; status) - status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? - ;; + status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? + ;; *) - echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" - exit 1 - ;; - + echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" + exit 1 + ;; esac exit 0 From 956a1188668b30b1431fbe9b73e45293c98e3423 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Fri, 26 Jun 2015 13:38:36 +0100 Subject: [PATCH 02/53] updated readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 94ba1704..55eef486 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -CoderBot +CoderBot_lboro ======== A RaspberryPI-based bot controller -The module provide a simple web interface to a raspberry py "robot". +The module provide a simple web interface to a raspberry pi "robot". -See the [wiki](https://github.com/CoderBotOrg/coderbot/wiki) for the documentation +See the [wiki](https://github.com/explosivose/coderbot_lboro/wiki) for the documentation From 21ad4b2f1d0b6cc466f8e39eb88b7d0f60543aa7 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Tue, 7 Jul 2015 14:10:19 +0000 Subject: [PATCH 03/53] changes and fixes for using servos with coderbot --- coderbot.cfg | 2 +- init.py | 2 +- motion.py | 2 +- motor_test.py | 17 +++++++++++++++++ pigpio_test.py | 25 +++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 motor_test.py create mode 100644 pigpio_test.py diff --git a/coderbot.cfg b/coderbot.cfg index 910d219b..06cfdbee 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file +{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "no", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} diff --git a/init.py b/init.py index ef291d7c..5b6c67f4 100755 --- a/init.py +++ b/init.py @@ -4,7 +4,7 @@ import main import os -os.chdir("/home/pi/coderbot/") +os.chdir("/home/pi/coderbot_lboro/") if __name__=="__main__": main.run_server() diff --git a/motion.py b/motion.py index bf41ae9c..258e8e81 100644 --- a/motion.py +++ b/motion.py @@ -7,7 +7,7 @@ from coderbot import CoderBot from camera import Camera -from program import get_prog_eng +#from program import get_prog_eng from config import Config lk_params = dict( winSize = (15, 15), diff --git a/motor_test.py b/motor_test.py new file mode 100644 index 00000000..6eea2f39 --- /dev/null +++ b/motor_test.py @@ -0,0 +1,17 @@ +#!/usr/bin/python + +import os + +os.chdir('/home/pi/coderbot_lboro/') + +from program import ProgramEngine, Program + +prog_name = 'motor_test' +prog_code = 'get_bot().motor_control(speed_left=100,speed_right=100,elapse=5)' + + +prog_engine = ProgramEngine.get_instance() +prog = None + +prog = prog_engine.create(prog_name, prog_code) +prog.execute diff --git a/pigpio_test.py b/pigpio_test.py new file mode 100644 index 00000000..8d1f8c76 --- /dev/null +++ b/pigpio_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/python + +import time +import pigpio + + +PIN_SERVO = 4 + +PWM_FREQ = 50 +PWM_RANGE = 100 + +pi = pigpio.pi('localhost') + +pi.set_PWM_frequency(PIN_SERVO,PWM_FREQ) +pi.set_PWM_range(PIN_SERVO,PWM_RANGE) + +pi.set_PWM_dutycycle(PIN_SERVO,0) +time.sleep(1) +pi.set_PWM_dutycycle(PIN_SERVO,25) +time.sleep(1) +pi.set_PWM_dutycycle(PIN_SERVO,60) +time.sleep(1) +pi.set_PWM_dutycycle(PIN_SERVO,100) + + From 73855a9463fff8d60788b198c68fc9f325826505 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Wed, 8 Jul 2015 10:28:29 +0000 Subject: [PATCH 04/53] modified init.d scripts --- README.md | 6 ++-- coderbot.cfg | 2 +- init.py | 2 +- motion.py | 2 +- motor_test.py | 17 ++++++++++ pigpio_test.py | 25 +++++++++++++++ scripts/coderbot | 5 ++- scripts/pigpiod | 83 ++++++++++++++++++++++++------------------------ 8 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 motor_test.py create mode 100644 pigpio_test.py diff --git a/README.md b/README.md index 94ba1704..55eef486 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -CoderBot +CoderBot_lboro ======== A RaspberryPI-based bot controller -The module provide a simple web interface to a raspberry py "robot". +The module provide a simple web interface to a raspberry pi "robot". -See the [wiki](https://github.com/CoderBotOrg/coderbot/wiki) for the documentation +See the [wiki](https://github.com/explosivose/coderbot_lboro/wiki) for the documentation diff --git a/coderbot.cfg b/coderbot.cfg index 910d219b..06cfdbee 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file +{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "no", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} diff --git a/init.py b/init.py index ef291d7c..5b6c67f4 100755 --- a/init.py +++ b/init.py @@ -4,7 +4,7 @@ import main import os -os.chdir("/home/pi/coderbot/") +os.chdir("/home/pi/coderbot_lboro/") if __name__=="__main__": main.run_server() diff --git a/motion.py b/motion.py index bf41ae9c..258e8e81 100644 --- a/motion.py +++ b/motion.py @@ -7,7 +7,7 @@ from coderbot import CoderBot from camera import Camera -from program import get_prog_eng +#from program import get_prog_eng from config import Config lk_params = dict( winSize = (15, 15), diff --git a/motor_test.py b/motor_test.py new file mode 100644 index 00000000..6eea2f39 --- /dev/null +++ b/motor_test.py @@ -0,0 +1,17 @@ +#!/usr/bin/python + +import os + +os.chdir('/home/pi/coderbot_lboro/') + +from program import ProgramEngine, Program + +prog_name = 'motor_test' +prog_code = 'get_bot().motor_control(speed_left=100,speed_right=100,elapse=5)' + + +prog_engine = ProgramEngine.get_instance() +prog = None + +prog = prog_engine.create(prog_name, prog_code) +prog.execute diff --git a/pigpio_test.py b/pigpio_test.py new file mode 100644 index 00000000..8d1f8c76 --- /dev/null +++ b/pigpio_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/python + +import time +import pigpio + + +PIN_SERVO = 4 + +PWM_FREQ = 50 +PWM_RANGE = 100 + +pi = pigpio.pi('localhost') + +pi.set_PWM_frequency(PIN_SERVO,PWM_FREQ) +pi.set_PWM_range(PIN_SERVO,PWM_RANGE) + +pi.set_PWM_dutycycle(PIN_SERVO,0) +time.sleep(1) +pi.set_PWM_dutycycle(PIN_SERVO,25) +time.sleep(1) +pi.set_PWM_dutycycle(PIN_SERVO,60) +time.sleep(1) +pi.set_PWM_dutycycle(PIN_SERVO,100) + + diff --git a/scripts/coderbot b/scripts/coderbot index 0c481379..40082480 100755 --- a/scripts/coderbot +++ b/scripts/coderbot @@ -11,15 +11,14 @@ ### END INIT INFO # Location of daemon python script -DIR=/home/pi/coderbot +DIR=/home/pi/coderbot_lboro DAEMON=$DIR/init.py DAEMON_NAME=coderbot # Put your command-line options here DAEMON_OPTS="" -# Must start as root to use GPIO -DAEMON_USER=root +DAEMON_USER=pi # The process ID of the script when it runs is stored here PIDFILE=/var/run/$DAEMON_NAME.pid diff --git a/scripts/pigpiod b/scripts/pigpiod index 408a9cc9..c4ae23fe 100755 --- a/scripts/pigpiod +++ b/scripts/pigpiod @@ -1,58 +1,57 @@ #!/bin/sh ### BEGIN INIT INFO -# Provides: pigpiod -# Required-Start: udev -# Required-Stop: -# Default-Start: S -# Default-Stop: -# X-Interactive: -# Description: PI GPIO Server -# Short-Description: Provides access to GPIO to non-root users +# Provides: pigpiod +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: PI GPIO Server +# Description: Provides access to GPIO non-root users! ### END INIT INFO -PATH='/sbin:/bin:/usr/bin' +# Location of daemon python script +DIR=/usr/local/bin +DAEMON=$DIR/pigpiod +DAEMON_NAME=pigpiod -NAME=pigpiod -PIGPIOD=/usr/local/bin/pigpiod -FIND_PID="ps -eo pid,args | grep /usr/local/bin/pigpiod | awk '{print $1}'" +# Put your command-line options here +DAEMON_OPTS="-s 10" -RET=0 +# Must start as root to use GPIO +DAEMON_USER=root + +# The process ID of the script when it runs is stored here +PIDFILE=/var/run/$DAEMON_NAME.pid . /lib/lsb/init-functions -kill_pid () { - PID=$(eval $FIND_PID) - if [ ! -z $PID ]; then - kill $PID - PID=$(eval $FIND_PID) - if [ ! -z $PID ]; then - sleep 3 - kill -9 $PID - fi - fi +do_start() { + log_daemon_msg "Starting system $DAEMON_NAME daemon" + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- ${DAEMON_OPTS} + log_end_msg $? +} + +do_stop() { + log_daemon_msg "Stopping system $DAEMON_NAME daemon" + start-stop-daemon --stop --pidfile $PIDFILE --retry 10 + log_end_msg $? } case "$1" in - start|reload|restart|force-reload) - kill_pid - log_daemon_msg "Starting PIGPIO Daemon" "pigpiod" - echo - $PIGPIOD - RET=$? - ;; - stop) - kill_pid - RET=$? - ;; + start|stop) + do_${1} + ;; + restart|reload|force-reload) + do_stop + do_start + ;; status) - ;; + status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? + ;; *) - log_failure_msg "Usage: /etc/init.d/$NAME {start|stop|restart}" - RET=1 - ;; + echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" + exit 1 + ;; esac - -exit $RET - -: +exit 0 From 869b9aa2cf5ff66005bb5d62b12fbb9df931615f Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Wed, 8 Jul 2015 14:07:11 +0000 Subject: [PATCH 05/53] adding custom blocks for microservo (work in progress) --- coderbot.cfg | 2 +- coderbot.py | 14 ++++----- static/js/blockly/blocks.js | 60 ++++++++++++++++++++++++++++++++++++ templates/blocks_std.xml | 4 +++ templates/config_params.html | 3 +- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/coderbot.cfg b/coderbot.cfg index 06cfdbee..63140bd7 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "no", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} +{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "no", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15","arm_angle_raised": "60","arm_angle_lowered": "120"} diff --git a/coderbot.py b/coderbot.py index 439a1c86..ef21be02 100644 --- a/coderbot.py +++ b/coderbot.py @@ -4,14 +4,14 @@ PIN_MOTOR_ENABLE = 22 PIN_LEFT_FORWARD = 25 -PIN_LEFT_BACKWARD = 24 +PIN_LEFT_BACKWARD = 24 # NOT USED FOR SERVOS PIN_RIGHT_FORWARD = 4 -PIN_RIGHT_BACKWARD = 17 +PIN_RIGHT_BACKWARD = 17 # NOT USED FOR SERVOS PIN_PUSHBUTTON = 18 PIN_SERVO_3 = 9 PIN_SERVO_4 = 10 -PWM_FREQUENCY = 100 #Hz +PWM_FREQUENCY = 50 #Hz PWM_RANGE = 100 #0-100 def coderbot_callback(gpio, level, tick): @@ -114,15 +114,15 @@ 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_range(pin, PWM_RANGE) + self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) 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_range(pin, PWM_RANGE) + self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, duty) def stop(self): diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index dadc0a59..5d0970a8 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -760,3 +760,63 @@ Blockly.Python['coderbot_adv_findLogo'] = function(block) { var code = 'get_cam().find_logo()'; return [code, Blockly.Python.ORDER_ATOMIC]; }; + +Blockly.Blocks['coderbot_armRaise'] = { + /** + * Block for armRaise function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.CODERBOT_ARM_RAISE_HELPURL); + this.setColour(40); + var di = this.appendDummyInput() + if (CODERBOT_PROG_LEVEL.indexOf("basic")>=0) { + di.appendField(new Blockly.FieldImage('/images/blocks/arm_raise.png', 32, 32, '*')); + } else { + di.appendField(Blockly.Msg.CODERBOT_ARM_RAISE) + } + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Coderbot_armRaiseTooltip'); + } +}; + +Blockly.JavaScript['coderbot_armRaise'] = function(block) { + // Generate JavaScript for raising the arm + return 'get_bot()..servo3(' + CODERBOT_ARM_ANGLE_RAISED + ');\n'; +}; + +Blockly.Python['coderbot_armRaise'] = function(block) { + // Generate Python code for raising the arm + return 'get_bot()..servo3(' + CODERBOT_ARM_ANGLE_RAISED + ')\n'; +}; + +Blockly.Blocks['coderbot_armLower'] = { + /** + * Block for armLower function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.CODERBOT_ARM_RAISE_HELPURL); + this.setColour(40); + var di = this.appendDummyInput() + if (CODERBOT_PROG_LEVEL.indexOf("basic")>=0) { + di.appendField(new Blockly.FieldImage('/image/blocks/arm_lower.png', 32, 32, '*')); + } else { + di.appendField(Blockly.Msg.CODERBOT_ARM_LOWER) + } + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Coderbot_armLowerTooltip'); + } +}; + +Blockly.JavaScript['coderbot_armLower'] = function(block) { + // Generate JavaScript code for lowering the arm + return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_LOWERED + ');\n'; +}; + +Blockly.Python['coderbot_armLower'] = function(block) { + // Generate Python code for lowering the arm + return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_LOWERED + ')\n'; +}; diff --git a/templates/blocks_std.xml b/templates/blocks_std.xml index 2f9b07a2..a930ab3a 100644 --- a/templates/blocks_std.xml +++ b/templates/blocks_std.xml @@ -58,6 +58,10 @@ + + + + diff --git a/templates/config_params.html b/templates/config_params.html index b9356ca0..57a96e94 100644 --- a/templates/config_params.html +++ b/templates/config_params.html @@ -15,7 +15,8 @@ var CODERBOT_CTRL_TR_ELAPSE="{{config.ctrl_tr_elapse}}"; var CODERBOT_CTRL_COUNTER="{{config.ctrl_counter}}"=="yes"; var CODERBOT_CTRL_MOVE_MOTION="{{config.ctrl_move_motion}}"=="yes"; - +var CODERBOT_ARM_ANGLE_LOWERED="{{config.arm_angle_lowered}}"; +var CODERBOT_ARM_ANGLE_RAISED="{{config.arm_angle_raised}}"; var BotMessages = Object(); BotMessages.Input = "{%trans%}Say what:{%endtrans%}"; BotMessages.Saved = "{%trans%}Saved{%endtrans%}"; From 7fcfadccc569c62250c26e0ac66d48bdbab8aafa Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Wed, 8 Jul 2015 17:11:41 +0000 Subject: [PATCH 06/53] working on servo testing and values --- coderbot.py | 9 +++++---- pigpio_test.py | 24 +++++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/coderbot.py b/coderbot.py index ef21be02..4fc63713 100644 --- a/coderbot.py +++ b/coderbot.py @@ -11,7 +11,7 @@ PIN_SERVO_3 = 9 PIN_SERVO_4 = 10 -PWM_FREQUENCY = 50 #Hz +PWM_FREQUENCY = 500 #Hz PWM_RANGE = 100 #0-100 def coderbot_callback(gpio, level, tick): @@ -112,15 +112,15 @@ def _servo_motor(self, speed_left=100, speed_right=100, elapse=-1): def _servo_motor_control(self, pin, speed): self._is_moving = True - speed = ((speed + 100) * 50 / 200) + 52 - + #speed = ((speed + 100) * 50 / 200) + 52 + speed = 50 + speed/2 + print "duty cycle: ", speed self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) 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, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, duty) @@ -130,6 +130,7 @@ def stop(self): self.pi.write(pin, 0) self._is_moving = False + def is_moving(self): return self._is_moving diff --git a/pigpio_test.py b/pigpio_test.py index 8d1f8c76..c1a4cab9 100644 --- a/pigpio_test.py +++ b/pigpio_test.py @@ -4,22 +4,24 @@ import pigpio -PIN_SERVO = 4 +PIN = 4 # RIGHT=4 LEFT=25 ARM=9 -PWM_FREQ = 50 +PWM_FREQ = 500 PWM_RANGE = 100 pi = pigpio.pi('localhost') -pi.set_PWM_frequency(PIN_SERVO,PWM_FREQ) -pi.set_PWM_range(PIN_SERVO,PWM_RANGE) +pi.set_PWM_frequency(PIN,PWM_FREQ) +pi.set_PWM_range(PIN,PWM_RANGE) -pi.set_PWM_dutycycle(PIN_SERVO,0) -time.sleep(1) -pi.set_PWM_dutycycle(PIN_SERVO,25) -time.sleep(1) -pi.set_PWM_dutycycle(PIN_SERVO,60) -time.sleep(1) -pi.set_PWM_dutycycle(PIN_SERVO,100) +# duty cycle values for range 100, frequency 400 +# 0- + +for x in range (50,100): + pi.set_PWM_dutycycle(PIN,x) + time.sleep(0.2) + print x + +pi.set_PWM_dutycycle(PIN,0) From 33cb5243557390376abac8663222091d80146002 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Thu, 9 Jul 2015 10:24:55 +0000 Subject: [PATCH 07/53] finished adding arm custom blocks, needs testing --- .gitignore | 4 ++++ static/images/blocks/arm_lower.png | Bin 0 -> 2307 bytes static/images/blocks/arm_raise.png | Bin 0 -> 1584 bytes static/js/blockly/blocks.js | 4 ++-- static/js/blockly/bot_en.js | 2 ++ templates/blocks_adv.xml | 4 ++++ templates/blocks_basic.xml | 2 ++ templates/blocks_basic_move.xml | 2 ++ 8 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 static/images/blocks/arm_lower.png create mode 100644 static/images/blocks/arm_raise.png diff --git a/.gitignore b/.gitignore index b17f9758..8746ab78 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,7 @@ static/blockly-tutorial .Trashes ehthumbs.db Thumbs.db + +# autosave 'tilde' files # +########################## +*~ diff --git a/static/images/blocks/arm_lower.png b/static/images/blocks/arm_lower.png new file mode 100644 index 0000000000000000000000000000000000000000..e1f85d4c61f7b08ec574d2f41687c33bb0535ea3 GIT binary patch literal 2307 zcmb7G`9Bm~7awbkHOg2TQMN}JG?+Ytv9FOmVT3_;V~K3pp2%B0vdh@!Au;xvVh|%C z>k!$O!4oD+lO;nF@6hM{1KuC*xu0{-z31G|Ip_PmH`UU@hzBSN1ONa$CdT^Kj2X!o z_8jaCS@SlZApuOG)<(L3+Mx>+hQfMR$6N;hXh`FtyRk8JPPFmuPym3p^JFmf1XXx2 z7DdAh9KvjZyuu>fLOcPMZa#rw5DNoaiEt=H6{4)9v>Sp2060QS^mT0SyKK%zqiw&4 zuv0U|m|zkhD{cY)ic!N4P5?c32>(oVFQfs51%sc0T1)@7ct@AaWriMo)^)0bPCwRm za;&j6grhk6UT^{pJ!GT`aoWN>QxYb&msFUF_XpIUOKWrowHLg;3mSs&U?hXWNs;Y6 zL2dr8QoDOIWn+U4FAY5()}9oHf7%3&dwr?_3A`be5VpzDO4}WWvY>dP8}W zbrkMf@t~RN2}_A;%VZzwtD-Y%x15K|F;P)b$%WU0mr(SX{;%==CD7)lYIex%Tu|n$ z&*&4Us;ZYugr1I3$5&xK4}wint;D%~JEZX=d50LoEujxh?RUaHoqPh#YK%xoNS3|YcTFvEe>o!Kv1aE4YOlS$? zD^WjxY<4$o_=j4)<&ezg#k@j5+C->(ll=_7T1pJA` zVi)mX#q3Ae8Kb_87iJX40z-^NI=DD969tj8(raw@KI&V{eaM0}m+ciPj;6d=d0!?V zz5DEZv#kaT2v5O}kK3bGs&B6_a{+;Y`lKBodlHwYIOE(kygG-E)it*YY5r9GdhOw&(BqBf#fr(C68#{FS=ju5;w*DZFg9WF@&?yQGiYupKU!pLM457jN(f;FS?XNut{Tusd;t=g|Pod`n}uDkM< zE!n6Dp2t@LVW7*}N9*kg9cqgeQ%}j!-w(26aE#_e44@_>KSB0~pjhAArli zIE9eUPRg;^C+oJPg-6KVqn_ctu-3NJcV(e7%D165E}m73cR+Pna@yM_TQlDCk_M}o z6m9*Ayw=9X1|1=u8@5Mji=88kmTx`+($$2yV%NJm8R)~UNnRDyPK2f9Z@W@Ex1?si zpWzdyuNV;sd-mkI#0~1sV%D1_EW@kavCh?yF%51nYW5;^%Y*a5@zJljT=U|r0PDi+EPam;LMVgCQ=P~fuIr`0p^_&jKxc7fzY{)p zkB3oK&d6?H@Kh*qE+L?DG%ZY4|7pFVVO~Q}vakf=7T@UP- z`26|a6A`GFpDh^{Pv3Yns4n4baY|uweaX?HGwtmVjiMOIZZToh<12*I8;GA=7zl|5Lc`d`%iO_Cye9}XpJ1p z%F1~_-sS6k>pvIeF}611gpNIkq!mkWI~tj)I6I!k^?pYFmK|Az-^ zyxq0uq7>0u@V%j}AZDkgahfH82p{K-6Olsqgg5atemZR;-e-*;Ho$k_E%la>J(z^M zp1r)eM9-Amq*C~7@{G^v?NW9T%MoB-KQSfR1kC?Yfgs0ZLOVw-8w7238f#esPMeVW z*!mp0U_{-8<&W1oP2}ekwM`kDq!Wu%2xLajHh%scE|dI4)0DH#e%{p2$A{AW?x*kI z^qU%rh=@Q_%t#jT%f9us1SCnh>CI_Pf)ir9`>kx?o?pkByUcQ8Uit2Q&0#!oVo)e_ zLD|~IGo>AXNOmLEF3aVl$e&a(IJH$hMz%FSBfnW>al*mZW^~ju1;O${>D=TFxM%x> zq$ZUh8RRc>k&%2m$Vui`opF3!O01JR&K>`=W=5>W0dIB&Ee7YY)fO-QYS6Gf1;~Yu zXD@wgv0yr=sZw!G3q9X|!WizIA5vKhnYOE@H+E`KC$KAS@g7};Oz#pXW#1K84W@tt T=}R(z8KhAqR)rHf10iE7SR(C_#W*05Lq;2!+IG$C?{i7@3-wtWa10AXVUOkHB2JH1|2e2^XX; zvlzGV92Rnu@PVe@g?v(bhX^xI(|fgtw0u^R)J=HP3e;`mW3a6b`mK7uLO2PlOCutvEj|}n*Pcq^cB~) zxUVbbbWafb(qiqnn-T|-H5QI>h30W*EQ&H@NE+jXNUiI;0=s)Mz@*cDg~$T8=T8ij zKm5GK8y=!V6Z}>kRMbcIb86R&=-`rUlc#(r88a-lF*n6#XVlmslgnXRP^6!Oo8H- z)I=6cc_tJ`R@h|TWI?gNK}Z^CRoWoeHyP$Xl4-)df5y0l(zVZ8e)h)R6sXcv43?9r z@%vEK%-sWqM|hM*@rCPy$??CEaDKi7Q`Gq?ceC9~dXlmE^u9oK`PXOPFY`DfiayaL zs$N&{9lEn#b~O2G>vP`unG9;x3qy%~Smo)_ih)21g(4rj_&$4j+H%0$ zdy5**c86K zlwsMMw}Oh4I0MHTIwcXw>C;W<7(Q%dOhZnszqxZ4(htC~nk+IB{ z_}>-ZgV(bynF8$9wFs|M@#+h(>;AOW5(yVe2U;1{T$W;+6C+7osHEFBg%QIvh*t5} zFq)e&U7Noc!^4Ro-ot0au5!>?r~SGMdYGoaGaF6TndQ-K^r&fV5K&{PmLS`4-x?zI zGed{8V{+wC2ejr(kXcfw=2A?iHRPjo+k`05Xw(|BvsgOYq;o6|!lhaZH<&d1mi(w3 zQk!8~)AUF6^%6GHv;4vSPSgv~e3Ck(^Llvd)1H)eYPX%TVOd~ztCMtrM5$)Ra%+r> h_w60=zd$6OSk$TpvbZP7ZsN2D0B0o1o@48u@(%=0) { di.appendField(new Blockly.FieldImage('/images/blocks/arm_raise.png', 32, 32, '*')); @@ -798,7 +798,7 @@ Blockly.Blocks['coderbot_armLower'] = { */ init: function() { this.setHelpUrl(Blockly.Msg.CODERBOT_ARM_RAISE_HELPURL); - this.setColour(40); + this.setColour(290); var di = this.appendDummyInput() if (CODERBOT_PROG_LEVEL.indexOf("basic")>=0) { di.appendField(new Blockly.FieldImage('/image/blocks/arm_lower.png', 32, 32, '*')); diff --git a/static/js/blockly/bot_en.js b/static/js/blockly/bot_en.js index 665dc0fc..1fb0f1a7 100644 --- a/static/js/blockly/bot_en.js +++ b/static/js/blockly/bot_en.js @@ -8,6 +8,8 @@ Blockly.Msg.CODERBOT_MOVE_FORWARD = "move forward"; Blockly.Msg.CODERBOT_MOVE_BACKWARD = "move backward"; Blockly.Msg.CODERBOT_MOVE_LEFT = "turn left"; Blockly.Msg.CODERBOT_MOVE_RIGHT = "turn right"; +Blockly.Msg.CODERBOT_ARM_LOWER = "drop arm"; +Blockly.Msg.CODERBOT_ARM_RAISE = "raise arm"; Blockly.Msg.CODERBOT_MOVE_ADV_MOVE = "move bot"; Blockly.Msg.CODERBOT_MOVE_MOTION_MOVE = "move bot (motion control)"; Blockly.Msg.CODERBOT_MOVE_MOTION_TURN = "turn bot (motion control)"; diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index eb4ccdfd..e0e0b53e 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -226,6 +226,10 @@ + + + + diff --git a/templates/blocks_basic.xml b/templates/blocks_basic.xml index 36a57710..cd6a601d 100644 --- a/templates/blocks_basic.xml +++ b/templates/blocks_basic.xml @@ -3,6 +3,8 @@ + + diff --git a/templates/blocks_basic_move.xml b/templates/blocks_basic_move.xml index 6b3ffc8d..18e73883 100644 --- a/templates/blocks_basic_move.xml +++ b/templates/blocks_basic_move.xml @@ -3,4 +3,6 @@ + + From f48c2f1ee52b066fa1d8e1d85d75bc1bdc7cb90c Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Thu, 9 Jul 2015 13:05:29 +0000 Subject: [PATCH 08/53] updated servo test scripts --- arm_test.py | 35 +++++++++++++++++++++++++++++++++++ motor_test.py | 17 ----------------- pigpio_test.py | 34 ++++++++++++++++++++++++++-------- 3 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 arm_test.py delete mode 100644 motor_test.py diff --git a/arm_test.py b/arm_test.py new file mode 100644 index 00000000..4d43582f --- /dev/null +++ b/arm_test.py @@ -0,0 +1,35 @@ +#!/usr/python/bin + +import time +import pigpio + +PIN = 9 + +PWM_FREQ = 50 +PWM_RANGE = 2000 + +pi = pigpio.pi('localhost') + +pi.set_PWM_frequency(PIN,PWM_FREQ) +pi.set_PWM_range(PIN,PWM_RANGE) + +# entire dutycycle range seems to be 50-220 + +print '1ms-2ms range' +for x in range (100,200): + pi.set_PWM_dutycycle(PIN,x) + time.sleep(0.02) + print x + +print 'three waves hello:' +for x in range (0,3): + pi.set_PWM_dutycycle(PIN,100) + time.sleep(0.5) + pi.set_PWM_dutycycle(PIN,200) + time.sleep(1) + + + + +pi.set_PWM_dutycycle(PIN,0) +print 'test complete.' diff --git a/motor_test.py b/motor_test.py deleted file mode 100644 index 6eea2f39..00000000 --- a/motor_test.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python - -import os - -os.chdir('/home/pi/coderbot_lboro/') - -from program import ProgramEngine, Program - -prog_name = 'motor_test' -prog_code = 'get_bot().motor_control(speed_left=100,speed_right=100,elapse=5)' - - -prog_engine = ProgramEngine.get_instance() -prog = None - -prog = prog_engine.create(prog_name, prog_code) -prog.execute diff --git a/pigpio_test.py b/pigpio_test.py index c1a4cab9..d0cba354 100644 --- a/pigpio_test.py +++ b/pigpio_test.py @@ -4,24 +4,42 @@ import pigpio -PIN = 4 # RIGHT=4 LEFT=25 ARM=9 +PIN = 25 # RIGHT=4 LEFT=25 ARM=9 -PWM_FREQ = 500 -PWM_RANGE = 100 +PWM_FREQ = 50 +PWM_RANGE = 2000 pi = pigpio.pi('localhost') pi.set_PWM_frequency(PIN,PWM_FREQ) pi.set_PWM_range(PIN,PWM_RANGE) -# duty cycle values for range 100, frequency 400 -# 0- +# duty cycle values for range 2000, frequency 50 +# -for x in range (50,100): +print 'full counter-clockwise (1ms):' +pi.set_PWM_dutycycle(PIN,100) +time.sleep(2) + +print 'rest (1.5ms):' +pi.set_PWM_dutycycle(PIN,150) +time.sleep(2) + +print 'full clockwise (2ms):' +pi.set_PWM_dutycycle(PIN,200) +time.sleep(2) + +print 'stop:' +pi.set_PWM_dutycycle(PIN,0) +time.sleep(2) + +print 'whole range:' +for x in range (100,200): pi.set_PWM_dutycycle(PIN,x) - time.sleep(0.2) + time.sleep(0.02) print x -pi.set_PWM_dutycycle(PIN,0) +pi.set_PWM_dutycycle(PIN,0) +print 'test complete.' From eee87aacf884f09968c4beefc4288324e636390c Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Thu, 9 Jul 2015 13:34:14 +0000 Subject: [PATCH 09/53] fixed and tested arm blocks --- camera.py | 2 +- coderbot.py | 17 +++++++++++------ data/program_arm_test.data | 1 + static/js/blockly/blocks.js | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 data/program_arm_test.data diff --git a/camera.py b/camera.py index 81a826b1..a4f0265a 100644 --- a/camera.py +++ b/camera.py @@ -332,6 +332,6 @@ def find_color(self, s_color): return [dist, angle] def sleep(self, elapse): - logging.debug("sleep: " + elapse) + logging.debug("sleep: " + str(elapse)) time.sleep(elapse) diff --git a/coderbot.py b/coderbot.py index 4fc63713..4ab64027 100644 --- a/coderbot.py +++ b/coderbot.py @@ -8,11 +8,11 @@ PIN_RIGHT_FORWARD = 4 PIN_RIGHT_BACKWARD = 17 # NOT USED FOR SERVOS PIN_PUSHBUTTON = 18 -PIN_SERVO_3 = 9 +PIN_SERVO_3 = 9 # ARM PIN PIN_SERVO_4 = 10 -PWM_FREQUENCY = 500 #Hz -PWM_RANGE = 100 #0-100 +PWM_FREQUENCY = 50 #Hz +PWM_RANGE = 2000 #100-200 duty cycle operating range for servos def coderbot_callback(gpio, level, tick): return CoderBot.get_instance().callback(gpio, level, tick) @@ -112,15 +112,20 @@ def _servo_motor(self, speed_left=100, speed_right=100, elapse=-1): def _servo_motor_control(self, pin, speed): self._is_moving = True - #speed = ((speed + 100) * 50 / 200) + 52 - speed = 50 + speed/2 + + # transform speed value from -100 to +100 range + # to servo duty cycle range: 100 to 200 + speed = 150 + speed/2 print "duty cycle: ", speed self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, speed) def _servo_control(self, pin, angle): - duty = ((angle + 90) * 100 / 180) + 25 + + # assuming angle range is 0 to 120 + # transform from angle range to servo duty cycle range (100 to 200) + duty = angle + 90 # (90-210) self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, duty) diff --git a/data/program_arm_test.data b/data/program_arm_test.data new file mode 100644 index 00000000..cd09fa2a --- /dev/null +++ b/data/program_arm_test.data @@ -0,0 +1 @@ +{"dom_code": "41.01.0", "code": "for count in range(4):\n get_prog_eng().check_end()\n get_bot().servo3(60)\n get_cam().sleep(1)\n get_bot().servo3(120)\n get_cam().sleep(1)\n", "name": "arm_test"} \ No newline at end of file diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 458070df..6d2b284f 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -783,12 +783,12 @@ Blockly.Blocks['coderbot_armRaise'] = { Blockly.JavaScript['coderbot_armRaise'] = function(block) { // Generate JavaScript for raising the arm - return 'get_bot()..servo3(' + CODERBOT_ARM_ANGLE_RAISED + ');\n'; + return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_RAISED + ');\n'; }; Blockly.Python['coderbot_armRaise'] = function(block) { // Generate Python code for raising the arm - return 'get_bot()..servo3(' + CODERBOT_ARM_ANGLE_RAISED + ')\n'; + return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_RAISED + ')\n'; }; Blockly.Blocks['coderbot_armLower'] = { From 6635ed86e398b712747f1126b73e1f11606371e2 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Fri, 10 Jul 2015 12:36:19 +0000 Subject: [PATCH 10/53] added photos/ dir to repository to fix problem where photos would not save when dir not exist --- .gitignore | 5 ++++- photos/README.md | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 photos/README.md diff --git a/.gitignore b/.gitignore index 8746ab78..a6994bd3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,10 @@ *.sqlite # Pictures taken -photos/ +*.jpg +*.jpeg +*.png + # Logs logs/ diff --git a/photos/README.md b/photos/README.md new file mode 100644 index 00000000..ced4d772 --- /dev/null +++ b/photos/README.md @@ -0,0 +1,3 @@ +# Photos + +This directory will be used for storing jpg images from the camera! From 41c82d07e76c3540e74b7a6bec24c09c30282f5b Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Mon, 13 Jul 2015 10:59:39 +0000 Subject: [PATCH 11/53] fixed start on boot for coderbot --- init.py | 5 +++-- scripts/coderbot | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/init.py b/init.py index 5b6c67f4..1f050b0f 100755 --- a/init.py +++ b/init.py @@ -1,10 +1,11 @@ #!/usr/bin/python -import coderbot -import main import os os.chdir("/home/pi/coderbot_lboro/") +import coderbot +import main + if __name__=="__main__": main.run_server() diff --git a/scripts/coderbot b/scripts/coderbot index 40082480..a8e7f0a0 100755 --- a/scripts/coderbot +++ b/scripts/coderbot @@ -7,7 +7,7 @@ # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Web-based Blockly Interface for Pi -# Description: Web-based Blockly Interface for programming the Pi, goto 10.0.0.10:8080 +# Description: Web-based Blockly Interface for programming the Pi, goto 10.0.0.1:8080 ### END INIT INFO # Location of daemon python script From 0ebd0e5e08375ae708b8ed33d409a2eca7b23db0 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Mon, 13 Jul 2015 11:01:38 +0000 Subject: [PATCH 12/53] changed espeak from italian to english --- coderbot.cfg | 2 +- coderbot.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/coderbot.cfg b/coderbot.cfg index 63140bd7..f4a0e3cf 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "no", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15","arm_angle_raised": "60","arm_angle_lowered": "120"} +{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_counter": "yes", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file diff --git a/coderbot.py b/coderbot.py index 4ab64027..a12debb0 100644 --- a/coderbot.py +++ b/coderbot.py @@ -116,7 +116,6 @@ def _servo_motor_control(self, pin, speed): # transform speed value from -100 to +100 range # to servo duty cycle range: 100 to 200 speed = 150 + speed/2 - print "duty cycle: ", speed self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, speed) @@ -143,7 +142,7 @@ def say(self, what): if what and "$" in what: os.system ('omxplayer sounds/' + what[1:]) elif what and len(what): - os.system ('espeak -vit -p 90 -a 200 -s 150 -g 10 "' + what + '" 2>>/dev/null') + os.system ('espeak -ven -p 90 -a 200 -s 150 -g 10 "' + what + '" 2>>/dev/null') def set_callback(self, gpio, callback, elapse): self._cb_elapse[gpio] = elapse * 1000 From 4236ba65695c79a64331316c9f8b84cc09de6162 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Mon, 13 Jul 2015 13:31:46 +0000 Subject: [PATCH 13/53] added a camera rotate button --- camera.py | 23 +++- coderbot.cfg | 2 +- main.py | 2 + messages.pot | 174 +++++++++++++----------- static/js/coderbot.js | 4 + static/js/control.js | 3 + templates/control.html | 1 + translations/it/LC_MESSAGES/messages.po | 174 +++++++++++++----------- viz/camera.py | 1 + 9 files changed, 227 insertions(+), 157 deletions(-) diff --git a/camera.py b/camera.py index a4f0265a..6b0df8a8 100644 --- a/camera.py +++ b/camera.py @@ -39,7 +39,12 @@ def get_instance(cls): def __init__(self): logging.info("starting camera") - cam_props = {"width":640, "height":480, "exposure_mode": config.Config.get().get("camera_exposure_mode")} + cam_props = { + "width":640, + "height":480, + "exposure_mode": config.Config.get().get("camera_exposure_mode"), + "rotation": config.Config.get().get("camera_rotation") + } #try initialising the camera try: self._camera = camera.Camera(props=cam_props) @@ -119,6 +124,15 @@ def get_next_photo_index(self): pass return last_photo_index + 1 + def rotate(self): + if self._camera is None: + return + rot = self._camera.camera.rotation + rot = rot + 90 + if rot > 271: + rot = 0 + self._camera.camera.rotation = rot + def photo_take(self): if self._camera is None: return @@ -196,7 +210,12 @@ def calibrate(self): return img = self._camera.getImage() self._background = img.hueHistogram()[-1] - + + def set_rotation(self, rotation): + if self._camera is None: + return + self._camera.rotation = rotation + def find_line(self): self._image_lock.acquire() img = self.get_image(0).binarize() diff --git a/coderbot.cfg b/coderbot.cfg index f4a0e3cf..acbc0c6e 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_counter": "yes", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file +{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "camera_rotation": "180", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_counter": "yes", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file diff --git a/main.py b/main.py index b8b0c94b..4dfeefec 100644 --- a/main.py +++ b/main.py @@ -83,6 +83,8 @@ def handle_bot(): elif cmd == "stop": bot.stop() motion.stop() + elif cmd == "rotate_camera": + cam.rotate() elif cmd == "take_photo": cam.photo_take() bot.say(app.bot_config.get("sound_shutter")) diff --git a/messages.pot b/messages.pot index 1234ecd0..9c90c1dc 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-04 00:32+0200\n" +"POT-Creation-Date: 2015-07-13 13:16+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -64,16 +64,20 @@ msgstr "" msgid "Movement" msgstr "" -#: templates/blocks_adv.xml:229 +#: templates/blocks_adv.xml:229 templates/blocks_std.xml:61 +msgid "Arm" +msgstr "" + +#: templates/blocks_adv.xml:233 msgid "Camera" msgstr "" -#: templates/blocks_adv.xml:234 templates/blocks_std.xml:61 +#: templates/blocks_adv.xml:238 templates/blocks_std.xml:65 #: templates/config.html:14 msgid "Sensor" msgstr "" -#: templates/blocks_adv.xml:246 templates/blocks_std.xml:73 +#: templates/blocks_adv.xml:250 templates/blocks_std.xml:77 msgid "Sound" msgstr "" @@ -82,7 +86,7 @@ msgstr "" msgid "Control" msgstr "" -#: templates/config.html:3 templates/control.html:63 +#: templates/config.html:3 templates/control.html:61 msgid "Back" msgstr "" @@ -102,7 +106,7 @@ msgstr "" msgid "Button" msgstr "" -#: templates/config.html:18 templates/config.html:127 +#: templates/config.html:18 templates/config.html:128 msgid "User Interface" msgstr "" @@ -110,241 +114,257 @@ msgstr "" msgid "WiFi" msgstr "" -#: templates/config.html:24 templates/config.html:63 +#: templates/config.html:20 +msgid "Restart, Halt" +msgstr "" + +#: templates/config.html:25 templates/config.html:64 msgid "Movement assisted by vision" msgstr "" -#: templates/config.html:27 +#: templates/config.html:28 msgid "Step timing | distance / angle" msgstr "" -#: templates/config.html:28 templates/config.html:66 +#: templates/config.html:29 templates/config.html:67 msgid "Forward speed" msgstr "" -#: templates/config.html:30 templates/config.html:68 +#: templates/config.html:31 templates/config.html:69 msgid "Forward elapse / distance" msgstr "" -#: templates/config.html:32 templates/config.html:70 +#: templates/config.html:33 templates/config.html:71 msgid "Turn speed" msgstr "" -#: templates/config.html:34 templates/config.html:72 +#: templates/config.html:35 templates/config.html:73 msgid "Turn elapse / angle" msgstr "" -#: templates/config.html:37 +#: templates/config.html:38 msgid "Show control counter" msgstr "" -#: templates/config.html:40 +#: templates/config.html:41 msgid "HUD image name" msgstr "" -#: templates/config.html:45 +#: templates/config.html:46 msgid "Editor level" msgstr "" -#: templates/config.html:47 +#: templates/config.html:48 msgid "Move" msgstr "" -#: templates/config.html:49 +#: templates/config.html:50 msgid "Basic" msgstr "" -#: templates/config.html:51 +#: templates/config.html:52 msgid "Standard" msgstr "" -#: templates/config.html:53 +#: templates/config.html:54 msgid "Advanced" msgstr "" -#: templates/config.html:56 +#: templates/config.html:57 msgid "Program editor details" msgstr "" -#: templates/config.html:57 +#: templates/config.html:58 msgid "Shows Editor scrollbars" msgstr "" -#: templates/config.html:59 +#: templates/config.html:60 msgid "Save program before every run" msgstr "" -#: templates/config.html:75 +#: templates/config.html:76 msgid "Other program parameters" msgstr "" -#: templates/config.html:76 +#: templates/config.html:77 msgid "Auto record video on program run" msgstr "" -#: templates/config.html:79 +#: templates/config.html:80 msgid "Max number of blocks allowed" msgstr "" -#: templates/config.html:83 +#: templates/config.html:84 msgid "Start sound" msgstr "" -#: templates/config.html:85 +#: templates/config.html:86 msgid "Stop sound" msgstr "" -#: templates/config.html:87 +#: templates/config.html:88 msgid "Shutter sound" msgstr "" -#: templates/config.html:91 +#: templates/config.html:92 msgid "Camera parameters" msgstr "" -#: templates/config.html:92 +#: templates/config.html:93 msgid "Exposure mode" msgstr "" -#: templates/config.html:103 +#: templates/config.html:104 msgid "Motor control mode" msgstr "" -#: templates/config.html:105 +#: templates/config.html:106 msgid "Power (target angle -15)" msgstr "" -#: templates/config.html:107 +#: templates/config.html:108 msgid "Power (target angle -4)" msgstr "" -#: templates/config.html:109 +#: templates/config.html:110 msgid "Power (target angle -1)" msgstr "" -#: templates/config.html:113 +#: templates/config.html:114 msgid "Load at start" msgstr "" -#: templates/config.html:118 +#: templates/config.html:119 msgid "Button function" msgstr "" -#: templates/config.html:119 +#: templates/config.html:120 msgid "None" msgstr "" -#: templates/config.html:121 +#: templates/config.html:122 msgid "Start/Stop current program" msgstr "" -#: templates/config.html:128 +#: templates/config.html:129 msgid "Show Control page" msgstr "" -#: templates/config.html:130 +#: templates/config.html:131 msgid "Show Control movement commands" msgstr "" -#: templates/config.html:132 +#: templates/config.html:133 msgid "Show Program page" msgstr "" -#: templates/config.html:134 +#: templates/config.html:135 msgid "Show Preferences button" msgstr "" -#: templates/config.html:139 +#: templates/config.html:140 msgid "WiFi Client access mode" msgstr "" -#: templates/config.html:142 templates/config.html:164 +#: templates/config.html:143 +msgid "Halt" +msgstr "" + +#: templates/config.html:144 +msgid "Restart" +msgstr "" + +#: templates/config.html:145 +msgid "Reboot" +msgstr "" + +#: templates/config.html:148 templates/config.html:169 msgid "Cancel" msgstr "" -#: templates/config.html:143 templates/program.html:18 +#: templates/config.html:149 templates/program.html:18 #: templates/program.html:26 msgid "Save" msgstr "" -#: templates/config.html:151 +#: templates/config.html:156 msgid "WiFi Client mode access data" msgstr "" -#: templates/config.html:153 +#: templates/config.html:158 msgid "WiFi mode" msgstr "" -#: templates/config.html:155 +#: templates/config.html:160 msgid "Access Point" msgstr "" -#: templates/config.html:157 +#: templates/config.html:162 msgid "Client" msgstr "" -#: templates/config.html:159 +#: templates/config.html:164 msgid "WiFi name" msgstr "" -#: templates/config.html:161 +#: templates/config.html:166 msgid "WiFi password" msgstr "" -#: templates/config.html:165 +#: templates/config.html:170 msgid "Apply Wifi config" msgstr "" -#: templates/config.html:172 +#: templates/config.html:177 msgid "WiFi AP mode access" msgstr "" -#: templates/config.html:184 +#: templates/config.html:189 msgid "WiFi Client mode access" msgstr "" -#: templates/config_params.html:20 +#: templates/config_params.html:21 msgid "Say what:" msgstr "" -#: templates/config_params.html:21 +#: templates/config_params.html:22 msgid "Saved" msgstr "" -#: templates/config_params.html:22 +#: templates/config_params.html:23 msgid "Program saved" msgstr "" -#: templates/config_params.html:23 +#: templates/config_params.html:24 msgid "Program name already in use, overwrite?" msgstr "" -#: templates/config_params.html:24 +#: templates/config_params.html:25 msgid "Delete" msgstr "" -#: templates/config_params.html:25 +#: templates/config_params.html:26 msgid "Delete photo" msgstr "" -#: templates/config_params.html:26 +#: templates/config_params.html:27 msgid "Reset move counter?" msgstr "" -#: templates/config_params.html:27 templates/program.html:40 +#: templates/config_params.html:28 templates/program.html:40 msgid "is running" msgstr "" -#: templates/config_params.html:28 +#: templates/config_params.html:29 msgid "stopped" msgstr "" -#: templates/config_params.html:29 templates/control.html:51 +#: templates/config_params.html:30 templates/control.html:49 #: templates/program.html:32 msgid "Stop" msgstr "" -#: templates/config_params.html:30 +#: templates/config_params.html:31 msgid "Close" msgstr "" @@ -356,39 +376,39 @@ msgstr "" msgid "Program" msgstr "" -#: templates/control.html:19 -msgid "Halt" -msgstr "" - -#: templates/control.html:24 +#: templates/control.html:21 msgid "Forward" msgstr "" -#: templates/control.html:27 +#: templates/control.html:24 msgid "Left" msgstr "" -#: templates/control.html:28 +#: templates/control.html:25 msgid "Right" msgstr "" -#: templates/control.html:31 +#: templates/control.html:28 msgid "Backward" msgstr "" -#: templates/control.html:40 +#: templates/control.html:37 msgid "Say" msgstr "" -#: templates/control.html:45 +#: templates/control.html:42 msgid "Photo" msgstr "" -#: templates/control.html:50 +#: templates/control.html:43 +msgid "Rotate" +msgstr "" + +#: templates/control.html:48 msgid "Rec" msgstr "" -#: templates/control.html:54 +#: templates/control.html:52 msgid "Photos" msgstr "" diff --git a/static/js/coderbot.js b/static/js/coderbot.js index a28fa626..a40eba9c 100644 --- a/static/js/coderbot.js +++ b/static/js/coderbot.js @@ -51,6 +51,10 @@ CoderBot.prototype.stop = function() { } } +CoderBot.prototype.rotateCamera = function() { + this.command('rotate_camera',0); +} + CoderBot.prototype.takePhoto = function() { this.command('take_photo', 0); } diff --git a/static/js/control.js b/static/js/control.js index 46bdc447..b5488534 100644 --- a/static/js/control.js +++ b/static/js/control.js @@ -78,6 +78,9 @@ $(document).on( "pagecreate", '#page-control', function( event ) { $('#b_camera').on("click", function (){ bot.takePhoto(); }); + $('#b_camera_rot').on("click",function(){ + bot.rotateCamera(); + }); $('#b_video_rec').on("click", function (){ bot.videoRec(); }); diff --git a/templates/control.html b/templates/control.html index 58d985f4..35870816 100644 --- a/templates/control.html +++ b/templates/control.html @@ -40,6 +40,7 @@

CoderBot

+
diff --git a/translations/it/LC_MESSAGES/messages.po b/translations/it/LC_MESSAGES/messages.po index bd70ca66..f4375c2c 100644 --- a/translations/it/LC_MESSAGES/messages.po +++ b/translations/it/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-04 00:32+0200\n" +"POT-Creation-Date: 2015-07-13 13:16+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: it \n" @@ -64,16 +64,20 @@ msgstr "Funzioni" msgid "Movement" msgstr "Movimento" -#: templates/blocks_adv.xml:229 +#: templates/blocks_adv.xml:229 templates/blocks_std.xml:61 +msgid "Arm" +msgstr "" + +#: templates/blocks_adv.xml:233 msgid "Camera" msgstr "Camera" -#: templates/blocks_adv.xml:234 templates/blocks_std.xml:61 +#: templates/blocks_adv.xml:238 templates/blocks_std.xml:65 #: templates/config.html:14 msgid "Sensor" msgstr "Sensore" -#: templates/blocks_adv.xml:246 templates/blocks_std.xml:73 +#: templates/blocks_adv.xml:250 templates/blocks_std.xml:77 msgid "Sound" msgstr "Suono" @@ -82,7 +86,7 @@ msgstr "Suono" msgid "Control" msgstr "Controllo" -#: templates/config.html:3 templates/control.html:63 +#: templates/config.html:3 templates/control.html:61 msgid "Back" msgstr "Indietro" @@ -102,7 +106,7 @@ msgstr "Avvio" msgid "Button" msgstr "Bottone" -#: templates/config.html:18 templates/config.html:127 +#: templates/config.html:18 templates/config.html:128 msgid "User Interface" msgstr "Interfaccia utente" @@ -110,241 +114,257 @@ msgstr "Interfaccia utente" msgid "WiFi" msgstr "" -#: templates/config.html:24 templates/config.html:63 +#: templates/config.html:20 +msgid "Restart, Halt" +msgstr "" + +#: templates/config.html:25 templates/config.html:64 msgid "Movement assisted by vision" msgstr "Movimento assistito da visione" -#: templates/config.html:27 +#: templates/config.html:28 msgid "Step timing | distance / angle" msgstr "Durata o lunghezza step (distanza / angolo)" -#: templates/config.html:28 templates/config.html:66 +#: templates/config.html:29 templates/config.html:67 msgid "Forward speed" msgstr "Avanti" -#: templates/config.html:30 templates/config.html:68 +#: templates/config.html:31 templates/config.html:69 msgid "Forward elapse / distance" msgstr "Durata / distanza avanti" -#: templates/config.html:32 templates/config.html:70 +#: templates/config.html:33 templates/config.html:71 msgid "Turn speed" msgstr "Velocità di rotazione" -#: templates/config.html:34 templates/config.html:72 +#: templates/config.html:35 templates/config.html:73 msgid "Turn elapse / angle" msgstr "Tempo / angolo di rotazione" -#: templates/config.html:37 +#: templates/config.html:38 msgid "Show control counter" msgstr "Mostra contatore mosse" -#: templates/config.html:40 +#: templates/config.html:41 msgid "HUD image name" msgstr "Nome immagine HUD" -#: templates/config.html:45 +#: templates/config.html:46 msgid "Editor level" msgstr "Livello editor" -#: templates/config.html:47 +#: templates/config.html:48 msgid "Move" msgstr "Movimento" -#: templates/config.html:49 +#: templates/config.html:50 msgid "Basic" msgstr "Base" -#: templates/config.html:51 +#: templates/config.html:52 msgid "Standard" msgstr "Normale" -#: templates/config.html:53 +#: templates/config.html:54 msgid "Advanced" msgstr "Avanzato" -#: templates/config.html:56 +#: templates/config.html:57 msgid "Program editor details" msgstr "Dettagli editor del programma" -#: templates/config.html:57 +#: templates/config.html:58 msgid "Shows Editor scrollbars" msgstr "Mostra le scrollbar dell'area di lavoro" -#: templates/config.html:59 +#: templates/config.html:60 msgid "Save program before every run" msgstr "Salva il programma prima di ogni esecuzione" -#: templates/config.html:75 +#: templates/config.html:76 msgid "Other program parameters" msgstr "Altri parametri del programma" -#: templates/config.html:76 +#: templates/config.html:77 msgid "Auto record video on program run" msgstr "Avvia automaticamente registrazione video all'avvio del programma" -#: templates/config.html:79 +#: templates/config.html:80 msgid "Max number of blocks allowed" msgstr "Numero massimo consentito di blocchi di programma" -#: templates/config.html:83 +#: templates/config.html:84 msgid "Start sound" msgstr "Suono avvio" -#: templates/config.html:85 +#: templates/config.html:86 msgid "Stop sound" msgstr "Suono stop" -#: templates/config.html:87 +#: templates/config.html:88 msgid "Shutter sound" msgstr "Suono avvio" -#: templates/config.html:91 +#: templates/config.html:92 msgid "Camera parameters" msgstr "Parametri videocamera" -#: templates/config.html:92 +#: templates/config.html:93 msgid "Exposure mode" msgstr "Esposizione" -#: templates/config.html:103 +#: templates/config.html:104 msgid "Motor control mode" msgstr "Tipo di motori (dc | servo)" -#: templates/config.html:105 +#: templates/config.html:106 msgid "Power (target angle -15)" msgstr "Potenza (angolo target -15)" -#: templates/config.html:107 +#: templates/config.html:108 msgid "Power (target angle -4)" msgstr "Potenza (angolo target -4)" -#: templates/config.html:109 +#: templates/config.html:110 msgid "Power (target angle -1)" msgstr "Potenza (angolo target -1)" -#: templates/config.html:113 +#: templates/config.html:114 msgid "Load at start" msgstr "Carica all'avvio" -#: templates/config.html:118 +#: templates/config.html:119 msgid "Button function" msgstr "Funzione del bottone" -#: templates/config.html:119 +#: templates/config.html:120 msgid "None" msgstr "Nessuno" -#: templates/config.html:121 +#: templates/config.html:122 msgid "Start/Stop current program" msgstr "Avvia/Ferma il programma caricato" -#: templates/config.html:128 +#: templates/config.html:129 msgid "Show Control page" msgstr "Mostra pagina Controllo" -#: templates/config.html:130 +#: templates/config.html:131 msgid "Show Control movement commands" msgstr "Mostra comandi movimento in pagina Controllo" -#: templates/config.html:132 +#: templates/config.html:133 msgid "Show Program page" msgstr "Nome programma" -#: templates/config.html:134 +#: templates/config.html:135 msgid "Show Preferences button" msgstr "Mostra bottone Preferenze" -#: templates/config.html:139 +#: templates/config.html:140 msgid "WiFi Client access mode" msgstr "WiFi in modalità Client" -#: templates/config.html:142 templates/config.html:164 +#: templates/config.html:143 +msgid "Halt" +msgstr "Stop" + +#: templates/config.html:144 +msgid "Restart" +msgstr "Ricomincia" + +#: templates/config.html:145 +msgid "Reboot" +msgstr "" + +#: templates/config.html:148 templates/config.html:169 msgid "Cancel" msgstr "Annulla" -#: templates/config.html:143 templates/program.html:18 +#: templates/config.html:149 templates/program.html:18 #: templates/program.html:26 msgid "Save" msgstr "Salva" -#: templates/config.html:151 +#: templates/config.html:156 msgid "WiFi Client mode access data" msgstr "Dati di accesso alla rete WiFi" -#: templates/config.html:153 +#: templates/config.html:158 msgid "WiFi mode" msgstr "Modalità WiFi" -#: templates/config.html:155 +#: templates/config.html:160 msgid "Access Point" msgstr "" -#: templates/config.html:157 +#: templates/config.html:162 msgid "Client" msgstr "" -#: templates/config.html:159 +#: templates/config.html:164 msgid "WiFi name" msgstr "Nome rete WiFi" -#: templates/config.html:161 +#: templates/config.html:166 msgid "WiFi password" msgstr "Password WiFi" -#: templates/config.html:165 +#: templates/config.html:170 msgid "Apply Wifi config" msgstr "Applica le modifiche alla configurazione WiFi" -#: templates/config.html:172 +#: templates/config.html:177 msgid "WiFi AP mode access" msgstr "WiFi in modalità AP" -#: templates/config.html:184 +#: templates/config.html:189 msgid "WiFi Client mode access" msgstr "WiFi in modalità Client" -#: templates/config_params.html:20 +#: templates/config_params.html:21 msgid "Say what:" msgstr "Frase:" -#: templates/config_params.html:21 +#: templates/config_params.html:22 msgid "Saved" msgstr "Salvato" -#: templates/config_params.html:22 +#: templates/config_params.html:23 msgid "Program saved" msgstr "Programma salvato" -#: templates/config_params.html:23 +#: templates/config_params.html:24 msgid "Program name already in use, overwrite?" msgstr "Esiste già un programma con questo nome, sovrascriverlo?" -#: templates/config_params.html:24 +#: templates/config_params.html:25 msgid "Delete" msgstr "Cancella" -#: templates/config_params.html:25 +#: templates/config_params.html:26 msgid "Delete photo" msgstr "Cancellare la foto" -#: templates/config_params.html:26 +#: templates/config_params.html:27 msgid "Reset move counter?" msgstr "Azzera contatore mosse?" -#: templates/config_params.html:27 templates/program.html:40 +#: templates/config_params.html:28 templates/program.html:40 msgid "is running" msgstr "in esecuzione" -#: templates/config_params.html:28 +#: templates/config_params.html:29 msgid "stopped" msgstr "fermo" -#: templates/config_params.html:29 templates/control.html:51 +#: templates/config_params.html:30 templates/control.html:49 #: templates/program.html:32 msgid "Stop" msgstr "Ferma" -#: templates/config_params.html:30 +#: templates/config_params.html:31 msgid "Close" msgstr "Chiudi" @@ -356,39 +376,39 @@ msgstr "Preferenze" msgid "Program" msgstr "Programma" -#: templates/control.html:19 -msgid "Halt" -msgstr "Stop" - -#: templates/control.html:24 +#: templates/control.html:21 msgid "Forward" msgstr "Avanti" -#: templates/control.html:27 +#: templates/control.html:24 msgid "Left" msgstr "Sinistra" -#: templates/control.html:28 +#: templates/control.html:25 msgid "Right" msgstr "Destra" -#: templates/control.html:31 +#: templates/control.html:28 msgid "Backward" msgstr "Indietro" -#: templates/control.html:40 +#: templates/control.html:37 msgid "Say" msgstr "Parla" -#: templates/control.html:45 +#: templates/control.html:42 msgid "Photo" msgstr "Foto" -#: templates/control.html:50 +#: templates/control.html:43 +msgid "Rotate" +msgstr "Ruotare" + +#: templates/control.html:48 msgid "Rec" msgstr "Registra" -#: templates/control.html:54 +#: templates/control.html:52 msgid "Photos" msgstr "Album" diff --git a/viz/camera.py b/viz/camera.py index 54fc8fda..ffc479f5 100644 --- a/viz/camera.py +++ b/viz/camera.py @@ -19,6 +19,7 @@ def __init__(self, props): self.camera.resolution = (props.get('width', 640), props.get('height', 240)) self.camera.framerate = 30 self.camera.exposure_mode = props.get('exposure_mode') + self.camera.rotation = props.get('rotation') self.out_jpeg = io.BytesIO() self.out_rgb = picamera.array.PiRGBArray(self.camera, size=(160,120)) self.h264_encoder = None From a2a5e121eadd74b8a8c0944e5391704fcf88ec59 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Mon, 13 Jul 2015 14:51:57 +0000 Subject: [PATCH 14/53] Added config controls for camera rotation and arm servo angles --- coderbot.cfg | 2 +- static/js/control.js | 2 +- templates/config.html | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/coderbot.cfg b/coderbot.cfg index acbc0c6e..4b69cda8 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"move_tr_speed": "75", "move_fw_elapse": "1.5", "show_page_program": "true", "load_at_start": "", "move_tr_elapse": "90", "sound_start": "$startup.mp3", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "camera_rotation": "180", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_counter": "yes", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file +{"move_tr_speed": "75", "move_fw_elapse": "1.5", "arm_angle_raised": "120", "load_at_start": "", "move_tr_elapse": "90", "show_page_program": "true", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_counter": "yes", "sound_start": "$startup.mp3", "camera_rotation": "180", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "arm_angle_lowered": "60", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file diff --git a/static/js/control.js b/static/js/control.js index b5488534..ec72259d 100644 --- a/static/js/control.js +++ b/static/js/control.js @@ -79,7 +79,7 @@ $(document).on( "pagecreate", '#page-control', function( event ) { bot.takePhoto(); }); $('#b_camera_rot').on("click",function(){ - bot.rotateCamera(); + bot.rotateCamera(); }); $('#b_video_rec').on("click", function (){ bot.videoRec(); diff --git a/templates/config.html b/templates/config.html index 923dd7df..6d9967f0 100644 --- a/templates/config.html +++ b/templates/config.html @@ -99,6 +99,13 @@

CoderBot

          + +
@@ -109,6 +116,10 @@

CoderBot

+ + + +
From e22fcf08a7ba9af6101ffee807c01c673f0b59a0 Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Wed, 15 Jul 2015 13:13:51 +0000 Subject: [PATCH 15/53] added a local_client wifi mode2 --- coderbot.cfg | 2 +- main.py | 4 ++-- static/js/control.js | 4 +++- templates/config.html | 13 +++++++++++ wifi.py | 50 +++++++++++++++++++++++++++++++++++++------ 5 files changed, 63 insertions(+), 10 deletions(-) diff --git a/coderbot.cfg b/coderbot.cfg index 4b69cda8..1e8b5218 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1 @@ -{"move_tr_speed": "75", "move_fw_elapse": "1.5", "arm_angle_raised": "120", "load_at_start": "", "move_tr_elapse": "90", "show_page_program": "true", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "auto", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_counter": "yes", "sound_start": "$startup.mp3", "camera_rotation": "180", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "arm_angle_lowered": "60", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file +{"move_tr_speed": "75", "move_fw_elapse": "1.5", "arm_angle_raised": "120", "load_at_start": "", "move_tr_elapse": "90", "show_page_program": "true", "sound_stop": "$shutdown.mp3", "camera_exposure_mode": "fixedfps", "prog_move_motion": "yes", "show_control_move_commands": "true", "prog_level": "adv", "prog_scrollbars": "true", "ctrl_counter": "yes", "sound_start": "$startup.mp3", "camera_rotation": "180", "ctrl_fw_speed": "100", "move_fw_speed": "100", "show_page_control": "true", "sound_shutter": "$shutter.mp3", "show_page_prefs": "true", "arm_angle_lowered": "60", "prog_maxblocks": "-1", "ctrl_hud_image": "", "button_func": "none", "move_motor_mode": "servo", "ctrl_fw_elapse": "-1", "ctrl_tr_elapse": "-1", "move_power_angle_2": "20", "move_power_angle_3": "20", "ctrl_tr_speed": "80", "move_power_angle_1": "15"} \ No newline at end of file diff --git a/main.py b/main.py index 4dfeefec..7442b010 100644 --- a/main.py +++ b/main.py @@ -62,8 +62,8 @@ def handle_wifi(): logging.info(client_params) os.system("sudo python wifi.py updatecfg " + mode + client_params) if mode == "ap": - return "http://coder.bot:8080/"; - else: + return "http://coder.bot:8080/" + elif mode == "client": return "http://coderbotsrv.appspot.com/" @app.route("/bot", methods=["GET"]) diff --git a/static/js/control.js b/static/js/control.js index ec72259d..6f394292 100644 --- a/static/js/control.js +++ b/static/js/control.js @@ -114,8 +114,10 @@ $(document).on( "pagecreate", '#page-preferences', function( event ) { $('#popup-wifi').popup('close'); if($("[name='wifi_mode']:checked").val()=="ap"){ $('#popup-wifi-ap').popup('open'); - } else { + } else if ($("[name='wifi_mode']:checked").val()=="client") { $('#popup-wifi-client').popup('open'); + } else if ($("[name=wifi_mode']:checked").val()=="local_client") { + $('#popup-wifi-local-client').popup('open'); } return false; }); diff --git a/templates/config.html b/templates/config.html index 6d9967f0..2831381e 100644 --- a/templates/config.html +++ b/templates/config.html @@ -171,6 +171,8 @@

{% trans %}WiFi Client mode access data{% endtrans %}

+ + @@ -208,4 +210,15 @@

{% trans %}WiFi Client mode access{% endtrans %}

+ diff --git a/wifi.py b/wifi.py index aa9c2610..5b7a6b19 100755 --- a/wifi.py +++ b/wifi.py @@ -13,7 +13,7 @@ class WiFi(): CONFIG_FILE = "/etc/coderbot_wifi.conf" - adapters = ["RT5370", "RTL8188CUS"] + adapters = ["RT5370", "RTL8188CUS", "RT3572"] hostapds = {"RT5370": "hostapd.RT5370", "RTL8188CUS": "hostapd.RTL8188"} web_url = "http://coderbotsrv.appspot.com/register_ip" wifi_client_conf_file = "/etc/wpa_supplicant/wpa_supplicant.conf" @@ -45,13 +45,13 @@ def get_adapter_type(cls): @classmethod def start_hostapd(cls): - adapter = cls.get_adapter_type() - hostapd_type = cls.hostapds.get(adapter) + #adapter = cls.get_adapter_type() + #hostapd_type = cls.hostapds.get(adapter) try: print "starting hostapd..." #os.system("start-stop-daemon --start --oknodo --quiet --exec /usr/sbin/" + hostapd_type + " -- /etc/hostapd/" + hostapd_type + " &") - os.system("/usr/sbin/" + hostapd_type + " /etc/hostapd/" + hostapd_type + " -B") - + #os.system("/usr/sbin/" + hostapd_type + " /etc/hostapd/" + hostapd_type + " -B") + out = subprocess.check_output(["service", "hostapd", "start"]) except subprocess.CalledProcessError as e: print e.output @@ -100,12 +100,14 @@ def set_client_params(cls, wssid, wpsk): f.write(" psk=\""+wpsk+"\"\n") f.write("}") + # set WIFI client mode @classmethod def set_start_as_client(cls): shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") cls._config["wifi_mode"] = "client" cls.save_config() + # start as client to coderbot server @classmethod def start_as_client(cls): cls.stop_hostapd() @@ -118,6 +120,25 @@ def start_as_client(cls): print e.output raise + # set WIFI client mode and record as "local_client" + @classmethod + def set_start_as_local_client(cls): + shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") + cls._config["wifi_mode"] = "local_client" + cls.save_config() + + # start as client without registering to coderbot server + @classmethod + def start_as_local_client(cls): + cls.stop_hostapd() + try: + out = subprocess.check_output(["ifdown", "wlan0"]) + out = subprocess.check_output(["ifup", "wlan0"]) + except: + print e.output + raise + + @classmethod def set_start_as_ap(cls): shutil.copy("/etc/network/interfaces_ap", "/etc/network/interfaces") @@ -139,9 +160,16 @@ def start_service(cls): elif config["wifi_mode"] == "client": print "starting as client..." try: - cls.start_as_client() + cls.start_as_start_as_local_clientclient() except: print "Unable to register ip, revert to ap mode" + cls.start_as_astart_as_local_clientp() + elif config["wifi_mode"] == "local_client": + print "starting as local client..." + try: + cls.start_as_local_client() + except: + print "Unable to connect to WLAN, rever to ap mode" cls.start_as_ap() def main(): @@ -160,6 +188,16 @@ def main(): except: print "Unable to register ip, revert to ap mode" w.start_as_ap() + elif len(sys.argv) > 2 and sys.argv[2] == "local_client": + if len(sys.argv) > 3: + w.set_client_params(sys.argv[3], sys.argv[4]) + w.set_start_as_client() + try: + w.start_as_local_client() + except: + print "Unable to connect to WLAN, revert to ap mode" + w.start_as_ap() + else: w.start_service() From e75b8abd41e73a0e0717c430fd492d4809193d5d Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Wed, 15 Jul 2015 21:36:46 +0000 Subject: [PATCH 16/53] fixed error where wifi popup would call handle_photo() --- static/js/control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/control.js b/static/js/control.js index 6f394292..e3db6eb6 100644 --- a/static/js/control.js +++ b/static/js/control.js @@ -159,7 +159,7 @@ $('video').on('loadeddata', function( event, ui ) { }, dataType="json"); }); -$(document).on( "click", 'a[data-rel="popup"]', function( event ) { +$(document).on( "click", 'li.ui-li-has-thumb', function( event ) { var src = "/photos/" + $(this).find('img').attr('data-src'); $('#popup-photo').find('img').attr('src', src); $('#popup-video').find('video').attr('src', src); From 2c519f755b36276e3b13be785dab8821d4f9750a Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Thu, 16 Jul 2015 15:25:05 +0000 Subject: [PATCH 17/53] added swapping between AP and Local Client (Wi-Fi) --- config_test.conf | 5 +++ config_test.py | 50 ++++++++++++++++++++++++++++ main.py | 5 +-- scripts/etc/hostapd/hostapd.conf | 4 +-- wifi.py | 57 ++++++++++++++++++++++++++++---- 5 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 config_test.conf create mode 100644 config_test.py diff --git a/config_test.conf b/config_test.conf new file mode 100644 index 00000000..c1d613b7 --- /dev/null +++ b/config_test.conf @@ -0,0 +1,5 @@ +ssid = ssidvalue +psk = poo +diff = diffvalue +chimp = ooh-ooh-ah-ah + diff --git a/config_test.py b/config_test.py new file mode 100644 index 00000000..9503f3e4 --- /dev/null +++ b/config_test.py @@ -0,0 +1,50 @@ +#!/usr/bin/python + +import sys +import ConfigParser +import StringIO + +class ConfigTest(): + + filename="./config_test.conf" + + @classmethod + def set_key(cls, key, val): + config = ConfigParser.ConfigParser() + try: + with open(cls.filename) as f: + conf_str = '[config]\n' + f.read() + conf_fp = StringIO.StringIO(conf_str) + config.readfp(conf_fp) + except IOError as e: + print cls.filename, 'does not exist!' + return + + config.set('config', str(key), str(val)) + + try: + with open(cls.filename, 'wb') as f: + conf_items = config.items('config') + for (key, value) in conf_items: + f.write("{0} = {1}\n".format(key, value)) + f.write("\n") + except IOError as e: + print e + + + @classmethod + def get_key(cls, key): + config = ConfigParser.ConfigParser() + config.read(cls.filename) + return config.get('config_test',str(key)) + + +def main(): + if len(sys.argv) > 3 and sys.argv[1] == "set": + ConfigTest.set_key(sys.argv[2],sys.argv[3]) + elif len(sys.argv) > 2 and sys.argv[1] == "get": + print ConfigTest.get_key(sys.argv[2]) + +if __name__ == "__main__": + main() + diff --git a/main.py b/main.py index 7442b010..e878371a 100644 --- a/main.py +++ b/main.py @@ -57,8 +57,8 @@ def handle_wifi(): mode = request.form.get("wifi_mode") ssid = request.form.get("wifi_ssid") psk = request.form.get("wifi_psk") - logging.info( "mode ", mode, " ssid: ", ssid, " psk: ", psk) - client_params = " \"" + ssid + "\" \"" + psk + "\"" if ssid != "" and psk != "" else "" + logging.info( "mode ", str(mode), " ssid: ", str(ssid), " psk: ", str(psk)) + client_params = " \"" + str(ssid) + "\" \"" + str(psk) + "\"" if ssid != "" and psk != "" else "" logging.info(client_params) os.system("sudo python wifi.py updatecfg " + mode + client_params) if mode == "ap": @@ -124,6 +124,7 @@ def handle_photos(): @app.route("/photos/", methods=["GET"]) def handle_photo(filename): + logging.info("photo") mimetype = {'jpeg': 'image/jpeg', 'h264': 'video/mp4'} video = None diff --git a/scripts/etc/hostapd/hostapd.conf b/scripts/etc/hostapd/hostapd.conf index a1fcb119..922e53bf 100644 --- a/scripts/etc/hostapd/hostapd.conf +++ b/scripts/etc/hostapd/hostapd.conf @@ -1,6 +1,6 @@ interface=wlan0 -#driver=nl80211 -driver=rtl871xdrv +driver=nl80211 +#driver=rtl871xdrv ctrl_interface=/var/run/hostapd ctrl_interface_group=0 ssid=coderbot diff --git a/wifi.py b/wifi.py index 5b7a6b19..ba7a8d7d 100755 --- a/wifi.py +++ b/wifi.py @@ -9,12 +9,15 @@ import fcntl import struct import json +import ConfigParser +import StringIO class WiFi(): CONFIG_FILE = "/etc/coderbot_wifi.conf" + HOSTAPD_CONF_FILE = "/etc/hostapd/hostapd.conf" adapters = ["RT5370", "RTL8188CUS", "RT3572"] - hostapds = {"RT5370": "hostapd.RT5370", "RTL8188CUS": "hostapd.RTL8188"} + hostapds = {"RT5370": "hostapd.RT5370", "RTL8188CUS": "hostapd.RTL8188"} web_url = "http://coderbotsrv.appspot.com/register_ip" wifi_client_conf_file = "/etc/wpa_supplicant/wpa_supplicant.conf" _config = {} @@ -51,18 +54,51 @@ def start_hostapd(cls): print "starting hostapd..." #os.system("start-stop-daemon --start --oknodo --quiet --exec /usr/sbin/" + hostapd_type + " -- /etc/hostapd/" + hostapd_type + " &") #os.system("/usr/sbin/" + hostapd_type + " /etc/hostapd/" + hostapd_type + " -B") - out = subprocess.check_output(["service", "hostapd", "start"]) + #out = subprocess.check_output(["service", "hostapd", "restart"]) + os.system("/etc/init.d/hostapd restart") + #something seems to mess with the static IP when hostapd restarts, quickfix: + os.system("ifconfig wlan0 10.0.0.1") except subprocess.CalledProcessError as e: print e.output @classmethod def stop_hostapd(cls): try: - out = subprocess.check_output(["pkill", "-9", "hostapd"]) - print out + #out = subprocess.check_output(["service", "hostapd", "stop"]) + #print out + os.system("/etc/init.d/hostapd stop") except subprocess.CalledProcessError as e: print e.output + @classmethod + def set_hostapd_params(cls, wssid, wpsk): + config = ConfigParser.ConfigParser() + # configparser requires sections like '[section]' + # open hostapd.conf with dummy section '[hostapd]' + try: + with open(cls.HOSTAPD_CONF_FILE) as f: + conf_str = '[hostapd]\n' + f.read() + conf_fp = StringIO.StringIO(conf_str) + config.readfp(conf_fp) + except IOError as e: + print e + return + + if len(str(wpsk)) < 8: + wpsk='coderbot' + + config.set('hostapd','ssid',str(wssid)) + config.set('hostapd','wpa_passphrase',str(wpsk)) + + try: + with open(cls.HOSTAPD_CONF_FILE, 'wb') as f: + conf_items = config.items('hostapd') + for (key,value) in conf_items: + f.write("{0}={1}\n".format(key, value)) + f.write("\n") + except IOError as e: + print e + @classmethod def get_ipaddr(cls, ifname): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -104,6 +140,8 @@ def set_client_params(cls, wssid, wpsk): @classmethod def set_start_as_client(cls): shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") + # disable hostapd start-on-boot + os.system('sudo update-rc.d hostapd remove') cls._config["wifi_mode"] = "client" cls.save_config() @@ -124,6 +162,8 @@ def start_as_client(cls): @classmethod def set_start_as_local_client(cls): shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") + # disable hostapd start-on-boot + os.system('sudo update-rc.d hostapd remove') cls._config["wifi_mode"] = "local_client" cls.save_config() @@ -142,6 +182,9 @@ def start_as_local_client(cls): @classmethod def set_start_as_ap(cls): shutil.copy("/etc/network/interfaces_ap", "/etc/network/interfaces") + # assuming /etc/init.d/hostapd start script exists + # enable hostapd start-on-boot + os.system('sudo update-rc.d hostapd defaults') cls._config["wifi_mode"] = "ap" cls.save_config() @@ -160,10 +203,10 @@ def start_service(cls): elif config["wifi_mode"] == "client": print "starting as client..." try: - cls.start_as_start_as_local_clientclient() + cls.start_as_client() except: print "Unable to register ip, revert to ap mode" - cls.start_as_astart_as_local_clientp() + cls.start_as_ap() elif config["wifi_mode"] == "local_client": print "starting as local client..." try: @@ -176,6 +219,8 @@ def main(): w = WiFi() if len(sys.argv) > 2 and sys.argv[1] == "updatecfg": if len(sys.argv) > 2 and sys.argv[2] == "ap": + if len(sys.argv) > 3: + w.set_hostapd_params(sys.argv[3], sys.argv[4]) w.set_start_as_ap() w.start_as_ap() elif len(sys.argv) > 2 and sys.argv[2] == "client": From 81603be92e29a343c2ff85d6a8bfa9632843302b Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Thu, 16 Jul 2015 15:32:51 +0000 Subject: [PATCH 18/53] updated hostapd start script, added readme for scripts folder --- scripts/README.md | 14 ++++++++++++ scripts/hostapd | 54 +++++++++++++++++++++++------------------------ 2 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 scripts/README.md diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..f52d2c82 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,14 @@ +|Script|Live Dir| +|coderbot|/etc/init.d/| +|hostapd|/etc/init.d/| +|interfaces_ap|/etc/network/| +|interfaces_cli|/etc/network/| +|pigpiod|/etc/init.d| +|hostapd.conf|/etc/hostapd/| +|dnsmasq.conf|/etc/| + + +Note that scripts in the init.d folder need to be enabled/disabled as +described in [Starting Coderbot](https://github.com/explosivose/coderbot_lboro/wiki/Starting-Coderbot). + + diff --git a/scripts/hostapd b/scripts/hostapd index b19a99f7..0e17e57f 100755 --- a/scripts/hostapd +++ b/scripts/hostapd @@ -14,52 +14,50 @@ ### END INIT INFO PATH=/sbin:/bin:/usr/sbin:/usr/bin -DAEMON_SBIN_1=/usr/sbin/hostapd.RT5370 -DAEMON_SBIN_2=/usr/sbin/hostapd.RTL8188 -DAEMON_DEFS_1=/etc/hostapd/hostapd.RT5370 -DAEMON_DEFS_2=/etc/hostapd/hostapd.RTL8188 -DAEMON_CONF= +DAEMON_SBIN=/usr/sbin/hostapd +DAEMON_DEFS=/etc/default/hostapd +DAEMON_CONF=/etc/hostapd/hostapd.conf NAME=hostapd DESC="advanced IEEE 802.11 management" PIDFILE=/var/run/hostapd.pid -[ -x "$DAEMON_SBIN_1" ] || exit 0 -[ -s "$DAEMON_DEFS_1" ] && . /etc/default/hostapd +[ -x "$DAEMON_SBIN" ] || exit 0 +[ -s "$DAEMON_DEFS" ] && . /etc/default/hostapd [ -n "$DAEMON_CONF" ] || exit 0 -DAEMON_OPTS="-B -P $PIDFILE $DAEMON_OPTS $DAEMON_CONF" . /lib/lsb/init-functions -case "$1" in - start) +do_start() { log_daemon_msg "Starting $DESC" "$NAME" - echo "starting 1" - start-stop-daemon --start --oknodo --quiet --exec "$DAEMON_SBIN_1" --pidfile "$PIDFILE" -- $DAEMON_DEFS_1 >/dev/null - echo "starting 2" - start-stop-daemon --start --oknodo --quiet --exec "$DAEMON_SBIN_2" --pidfile "$PIDFILE" -- $DAEMON_DEFS_2 >/dev/null - echo "$?" - log_end_msg "$?" - ;; - stop) - log_daemon_msg "Stopping $DESC" "$NAME" - start-stop-daemon --stop --oknodo --quiet --exec "$DAEMON_SBIN_1" --pidfile "$PIDFILE" - start-stop-daemon --stop --oknodo --quiet --exec "$DAEMON_SBIN_2" --pidfile "$PIDFILE" - log_end_msg "$?" + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile \ + --startas $DAEMON_SBIN -- $DAEMON_CONF + log_end_msg $? +} + +do_stop() { + log_daemon_msg "Stopping $NAME" + start-stop-daemon --stop --pidfile $PIDFILE --retry 10 + log_end_msg $? +} + +case "$1" in + start|stop) + do_${1} ;; reload) log_daemon_msg "Reloading $DESC" "$NAME" - start-stop-daemon --stop --signal HUP --exec "$DAEMON_SBIN_1" --pidfile "$PIDFILE" - start-stop-daemon --stop --signal HUP --exec "$DAEMON_SBIN_2" --pidfile "$PIDFILE" + start-stop-daemon --stop --signal HUP --exec "$DAEMON_SBIN" \ + --pidfile "$PIDFILE" log_end_msg "$?" ;; restart|force-reload) - $0 stop - sleep 8 - $0 start + do_stop + sleep 8 + do_start ;; status) - status_of_proc "$DAEMON_SBIN_1" "$NAME" + status_of_proc "$DAEMON_SBIN" "$NAME" exit $? ;; *) From 3d7ae4f45a98707e30082455ccc847c92d6740da Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 21 Jul 2015 15:11:33 +0000 Subject: [PATCH 19/53] Changed wifi.py to check if it is connected to a router and a client, and if it isnt, then make a hotspot --- scripts/etc/coderbot_wifi.conf | 2 + scripts/etc/init.d/coderbot | 56 ++++++++++++++++++++++++ scripts/etc/init.d/hostapd | 70 ++++++++++++++++++++++++++++++ scripts/etc/init.d/pigpiod | 57 ++++++++++++++++++++++++ scripts/etc/network/interfaces_ap | 12 +++++ scripts/etc/network/interfaces_cli | 9 ++++ wifi.py | 43 +++++++++--------- 7 files changed, 229 insertions(+), 20 deletions(-) create mode 100644 scripts/etc/coderbot_wifi.conf create mode 100755 scripts/etc/init.d/coderbot create mode 100755 scripts/etc/init.d/hostapd create mode 100755 scripts/etc/init.d/pigpiod create mode 100644 scripts/etc/network/interfaces_ap create mode 100644 scripts/etc/network/interfaces_cli diff --git a/scripts/etc/coderbot_wifi.conf b/scripts/etc/coderbot_wifi.conf new file mode 100644 index 00000000..16afbe86 --- /dev/null +++ b/scripts/etc/coderbot_wifi.conf @@ -0,0 +1,2 @@ +#This is located at /etc/coderbot_wifi.conf +{"wifi_mode": "ap"} diff --git a/scripts/etc/init.d/coderbot b/scripts/etc/init.d/coderbot new file mode 100755 index 00000000..a8e7f0a0 --- /dev/null +++ b/scripts/etc/init.d/coderbot @@ -0,0 +1,56 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: coderbot +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Web-based Blockly Interface for Pi +# Description: Web-based Blockly Interface for programming the Pi, goto 10.0.0.1:8080 +### END INIT INFO + +# Location of daemon python script +DIR=/home/pi/coderbot_lboro +DAEMON=$DIR/init.py +DAEMON_NAME=coderbot + +# Put your command-line options here +DAEMON_OPTS="" + +DAEMON_USER=pi + +# The process ID of the script when it runs is stored here +PIDFILE=/var/run/$DAEMON_NAME.pid + +. /lib/lsb/init-functions + +do_start() { + log_daemon_msg "Starting system $DAEMON_NAME daemon" + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS + log_end_msg $? +} + +do_stop() { + log_daemon_msg "Stopping system $DAEMON_NAME daemon" + start-stop-daemon --stop --pidfile $PIDFILE --retry 10 + log_end_msg $? +} + +case "$1" in + start|stop) + do_${1} + ;; + restart|reload|force-reload) + do_stop + do_start + ;; + status) + status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? + ;; + *) + echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" + exit 1 + ;; +esac +exit 0 diff --git a/scripts/etc/init.d/hostapd b/scripts/etc/init.d/hostapd new file mode 100755 index 00000000..0e17e57f --- /dev/null +++ b/scripts/etc/init.d/hostapd @@ -0,0 +1,70 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: hostapd +# Required-Start: $remote_fs +# Required-Stop: $remote_fs +# Should-Start: $network +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Advanced IEEE 802.11 management daemon +# Description: Userspace IEEE 802.11 AP and IEEE 802.1X/WPA/WPA2/EAP +# Authenticator +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON_SBIN=/usr/sbin/hostapd +DAEMON_DEFS=/etc/default/hostapd +DAEMON_CONF=/etc/hostapd/hostapd.conf +NAME=hostapd +DESC="advanced IEEE 802.11 management" +PIDFILE=/var/run/hostapd.pid + +[ -x "$DAEMON_SBIN" ] || exit 0 +[ -s "$DAEMON_DEFS" ] && . /etc/default/hostapd +[ -n "$DAEMON_CONF" ] || exit 0 + + +. /lib/lsb/init-functions + +do_start() { + log_daemon_msg "Starting $DESC" "$NAME" + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile \ + --startas $DAEMON_SBIN -- $DAEMON_CONF + log_end_msg $? +} + +do_stop() { + log_daemon_msg "Stopping $NAME" + start-stop-daemon --stop --pidfile $PIDFILE --retry 10 + log_end_msg $? +} + +case "$1" in + start|stop) + do_${1} + ;; + reload) + log_daemon_msg "Reloading $DESC" "$NAME" + start-stop-daemon --stop --signal HUP --exec "$DAEMON_SBIN" \ + --pidfile "$PIDFILE" + log_end_msg "$?" + ;; + restart|force-reload) + do_stop + sleep 8 + do_start + ;; + status) + status_of_proc "$DAEMON_SBIN" "$NAME" + exit $? + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|force-reload|reload|status}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/scripts/etc/init.d/pigpiod b/scripts/etc/init.d/pigpiod new file mode 100755 index 00000000..c4ae23fe --- /dev/null +++ b/scripts/etc/init.d/pigpiod @@ -0,0 +1,57 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: pigpiod +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: PI GPIO Server +# Description: Provides access to GPIO non-root users! +### END INIT INFO + +# Location of daemon python script +DIR=/usr/local/bin +DAEMON=$DIR/pigpiod +DAEMON_NAME=pigpiod + +# Put your command-line options here +DAEMON_OPTS="-s 10" + +# Must start as root to use GPIO +DAEMON_USER=root + +# The process ID of the script when it runs is stored here +PIDFILE=/var/run/$DAEMON_NAME.pid + +. /lib/lsb/init-functions + +do_start() { + log_daemon_msg "Starting system $DAEMON_NAME daemon" + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- ${DAEMON_OPTS} + log_end_msg $? +} + +do_stop() { + log_daemon_msg "Stopping system $DAEMON_NAME daemon" + start-stop-daemon --stop --pidfile $PIDFILE --retry 10 + log_end_msg $? +} + +case "$1" in + start|stop) + do_${1} + ;; + restart|reload|force-reload) + do_stop + do_start + ;; + status) + status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? + ;; + *) + echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" + exit 1 + ;; +esac +exit 0 diff --git a/scripts/etc/network/interfaces_ap b/scripts/etc/network/interfaces_ap new file mode 100644 index 00000000..e6970d05 --- /dev/null +++ b/scripts/etc/network/interfaces_ap @@ -0,0 +1,12 @@ +auto lo +iface lo inet loopback +iface eth0 inet dhcp +#allow-hotplug wlan0 +#iface wlan0 inet manual +#wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf +#iface default inet dhcp +iface wlan0 inet static +address 10.0.0.1 +netmask 255.255.255.0 +wireless-power off + diff --git a/scripts/etc/network/interfaces_cli b/scripts/etc/network/interfaces_cli new file mode 100644 index 00000000..74e4884f --- /dev/null +++ b/scripts/etc/network/interfaces_cli @@ -0,0 +1,9 @@ +auto lo +iface lo inet loopback +iface eth0 inet dhcp +allow-hotplug wlan0 +iface wlan0 inet manual +wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf +iface wlan0 inet dhcp +iface default inet dhcp +wireless-power off diff --git a/wifi.py b/wifi.py index ba7a8d7d..f0004815 100755 --- a/wifi.py +++ b/wifi.py @@ -48,15 +48,9 @@ def get_adapter_type(cls): @classmethod def start_hostapd(cls): - #adapter = cls.get_adapter_type() - #hostapd_type = cls.hostapds.get(adapter) try: print "starting hostapd..." - #os.system("start-stop-daemon --start --oknodo --quiet --exec /usr/sbin/" + hostapd_type + " -- /etc/hostapd/" + hostapd_type + " &") - #os.system("/usr/sbin/" + hostapd_type + " /etc/hostapd/" + hostapd_type + " -B") - #out = subprocess.check_output(["service", "hostapd", "restart"]) os.system("/etc/init.d/hostapd restart") - #something seems to mess with the static IP when hostapd restarts, quickfix: os.system("ifconfig wlan0 10.0.0.1") except subprocess.CalledProcessError as e: print e.output @@ -64,8 +58,6 @@ def start_hostapd(cls): @classmethod def stop_hostapd(cls): try: - #out = subprocess.check_output(["service", "hostapd", "stop"]) - #print out os.system("/etc/init.d/hostapd stop") except subprocess.CalledProcessError as e: print e.output @@ -217,22 +209,33 @@ def start_service(cls): def main(): w = WiFi() + + #ping hub router + response = os.system('ping -c 1 192.168.0.1') + #healthy response is 0 + + if response == 0: + print 'Router has been found, staying on client mode' + else: + print 'Router not found, switching to AP mode' + w.start_hostapd() + if len(sys.argv) > 2 and sys.argv[1] == "updatecfg": if len(sys.argv) > 2 and sys.argv[2] == "ap": if len(sys.argv) > 3: w.set_hostapd_params(sys.argv[3], sys.argv[4]) - w.set_start_as_ap() - w.start_as_ap() - elif len(sys.argv) > 2 and sys.argv[2] == "client": + #w.set_start_as_ap() + #w.start_as_ap() + elif len(sys.argv) > 2 and sys.argv[2] == "hub": if len(sys.argv) > 3: w.set_client_params(sys.argv[3], sys.argv[4]) - w.set_start_as_client() - w.stop_hostapd() - try: - w.start_as_client() - except: - print "Unable to register ip, revert to ap mode" - w.start_as_ap() + #w.set_start_as_client() + #w.stop_hostapd() + #try: + # w.start_as_client() + #except: + # print "Unable to register ip, revert to ap mode" + # w.start_as_ap() elif len(sys.argv) > 2 and sys.argv[2] == "local_client": if len(sys.argv) > 3: w.set_client_params(sys.argv[3], sys.argv[4]) @@ -243,8 +246,8 @@ def main(): print "Unable to connect to WLAN, revert to ap mode" w.start_as_ap() - else: - w.start_service() + #else: + # w.start_service() if __name__ == "__main__": main() From 39a914d982e49dbe5e48ce56ffe0a6cbddd0f828 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 21 Jul 2015 15:15:21 +0000 Subject: [PATCH 20/53] Added interfaces_hub to scipts, which is a default behaivour of client access. If this router is not found a hotspot will be make when wifi.py is executed. removed old interfaces files --- scripts/etc/network/interfaces_ap | 12 ------------ scripts/etc/network/interfaces_cli | 9 --------- scripts/etc/network/interfaces_hub | 13 +++++++++++++ 3 files changed, 13 insertions(+), 21 deletions(-) delete mode 100644 scripts/etc/network/interfaces_ap delete mode 100644 scripts/etc/network/interfaces_cli create mode 100644 scripts/etc/network/interfaces_hub diff --git a/scripts/etc/network/interfaces_ap b/scripts/etc/network/interfaces_ap deleted file mode 100644 index e6970d05..00000000 --- a/scripts/etc/network/interfaces_ap +++ /dev/null @@ -1,12 +0,0 @@ -auto lo -iface lo inet loopback -iface eth0 inet dhcp -#allow-hotplug wlan0 -#iface wlan0 inet manual -#wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf -#iface default inet dhcp -iface wlan0 inet static -address 10.0.0.1 -netmask 255.255.255.0 -wireless-power off - diff --git a/scripts/etc/network/interfaces_cli b/scripts/etc/network/interfaces_cli deleted file mode 100644 index 74e4884f..00000000 --- a/scripts/etc/network/interfaces_cli +++ /dev/null @@ -1,9 +0,0 @@ -auto lo -iface lo inet loopback -iface eth0 inet dhcp -allow-hotplug wlan0 -iface wlan0 inet manual -wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf -iface wlan0 inet dhcp -iface default inet dhcp -wireless-power off diff --git a/scripts/etc/network/interfaces_hub b/scripts/etc/network/interfaces_hub new file mode 100644 index 00000000..65676d54 --- /dev/null +++ b/scripts/etc/network/interfaces_hub @@ -0,0 +1,13 @@ +auto lo +iface lo inet loopback +iface eth0 inet dhcp + +allow-hotplug wlan0 +iface wlan0 inet dhcp +#iface default inet dhcp + +wpa-ssid "Lboro_Coderbot_Hub" +wpa-psk "welovepis" + +#wireless-power off + From 017878e183118eacf8ec593c18fbc2703cf36a8a Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 21 Jul 2015 15:38:14 +0000 Subject: [PATCH 21/53] Modified wifi.py, add rc.local Running wifi as a service dosnt seem to work for deciding weither to be client or ap, fixed this by using rc.local instead --- scripts/etc/rc.local | 22 ++++++++++++++++++++++ wifi.py | 4 ++++ 2 files changed, 26 insertions(+) create mode 100755 scripts/etc/rc.local diff --git a/scripts/etc/rc.local b/scripts/etc/rc.local new file mode 100755 index 00000000..8082d7b4 --- /dev/null +++ b/scripts/etc/rc.local @@ -0,0 +1,22 @@ +##!/bin/sh -e +# +# rc.local +# +# This script is executed at the end of each multiuser runlevel. +# Make sure that the script will "exit 0" on success or any other +# value on error. +# +# In order to enable or disable this script just change the execution +# bits. +# +# By default this script does nothing. + +# Print the IP address +_IP=$(hostname -I) || true +if [ "$_IP" ]; then + printf "My IP address is %s\n" "$_IP" +fi + +sudo python /home/pi/coderbot_lboro/wifi.py + +exit 0 diff --git a/wifi.py b/wifi.py index f0004815..1be11dad 100755 --- a/wifi.py +++ b/wifi.py @@ -11,6 +11,7 @@ import json import ConfigParser import StringIO +from time import sleep class WiFi(): @@ -210,6 +211,9 @@ def start_service(cls): def main(): w = WiFi() + print 'Wait 1 seconds before checking connection to router...' + sleep(1) + print 'pinging router...' #ping hub router response = os.system('ping -c 1 192.168.0.1') #healthy response is 0 From e7e8a4cc72c9d7f3709ac666d57d7077de521783 Mon Sep 17 00:00:00 2001 From: Stephen Withers Date: Tue, 21 Jul 2015 16:47:06 +0100 Subject: [PATCH 22/53] Delete coderbot_wifi.conf --- scripts/coderbot_wifi.conf | 1 - 1 file changed, 1 deletion(-) delete mode 100644 scripts/coderbot_wifi.conf diff --git a/scripts/coderbot_wifi.conf b/scripts/coderbot_wifi.conf deleted file mode 100644 index d59c3b93..00000000 --- a/scripts/coderbot_wifi.conf +++ /dev/null @@ -1 +0,0 @@ -{"wifi_mode": "ap"} \ No newline at end of file From 5158a9726793eb4e72904640d8e21484a6610fef Mon Sep 17 00:00:00 2001 From: Stephen Withers Date: Tue, 21 Jul 2015 16:47:19 +0100 Subject: [PATCH 23/53] Delete coderbot --- scripts/coderbot | 56 ------------------------------------------------ 1 file changed, 56 deletions(-) delete mode 100755 scripts/coderbot diff --git a/scripts/coderbot b/scripts/coderbot deleted file mode 100755 index a8e7f0a0..00000000 --- a/scripts/coderbot +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -### BEGIN INIT INFO -# Provides: coderbot -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Web-based Blockly Interface for Pi -# Description: Web-based Blockly Interface for programming the Pi, goto 10.0.0.1:8080 -### END INIT INFO - -# Location of daemon python script -DIR=/home/pi/coderbot_lboro -DAEMON=$DIR/init.py -DAEMON_NAME=coderbot - -# Put your command-line options here -DAEMON_OPTS="" - -DAEMON_USER=pi - -# The process ID of the script when it runs is stored here -PIDFILE=/var/run/$DAEMON_NAME.pid - -. /lib/lsb/init-functions - -do_start() { - log_daemon_msg "Starting system $DAEMON_NAME daemon" - start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS - log_end_msg $? -} - -do_stop() { - log_daemon_msg "Stopping system $DAEMON_NAME daemon" - start-stop-daemon --stop --pidfile $PIDFILE --retry 10 - log_end_msg $? -} - -case "$1" in - start|stop) - do_${1} - ;; - restart|reload|force-reload) - do_stop - do_start - ;; - status) - status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? - ;; - *) - echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" - exit 1 - ;; -esac -exit 0 From f8fceb825c0893175b91cb4ab0b56f784f8c198b Mon Sep 17 00:00:00 2001 From: Stephen Withers Date: Tue, 21 Jul 2015 16:47:31 +0100 Subject: [PATCH 24/53] Delete pigpiod --- scripts/pigpiod | 57 ------------------------------------------------- 1 file changed, 57 deletions(-) delete mode 100755 scripts/pigpiod diff --git a/scripts/pigpiod b/scripts/pigpiod deleted file mode 100755 index c4ae23fe..00000000 --- a/scripts/pigpiod +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh - -### BEGIN INIT INFO -# Provides: pigpiod -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: PI GPIO Server -# Description: Provides access to GPIO non-root users! -### END INIT INFO - -# Location of daemon python script -DIR=/usr/local/bin -DAEMON=$DIR/pigpiod -DAEMON_NAME=pigpiod - -# Put your command-line options here -DAEMON_OPTS="-s 10" - -# Must start as root to use GPIO -DAEMON_USER=root - -# The process ID of the script when it runs is stored here -PIDFILE=/var/run/$DAEMON_NAME.pid - -. /lib/lsb/init-functions - -do_start() { - log_daemon_msg "Starting system $DAEMON_NAME daemon" - start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- ${DAEMON_OPTS} - log_end_msg $? -} - -do_stop() { - log_daemon_msg "Stopping system $DAEMON_NAME daemon" - start-stop-daemon --stop --pidfile $PIDFILE --retry 10 - log_end_msg $? -} - -case "$1" in - start|stop) - do_${1} - ;; - restart|reload|force-reload) - do_stop - do_start - ;; - status) - status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? - ;; - *) - echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" - exit 1 - ;; -esac -exit 0 From 43524ce0537d333e4011a8e54f61e23e0d8912f0 Mon Sep 17 00:00:00 2001 From: Stephen Withers Date: Tue, 21 Jul 2015 16:47:38 +0100 Subject: [PATCH 25/53] Delete hostapd --- scripts/hostapd | 70 ------------------------------------------------- 1 file changed, 70 deletions(-) delete mode 100755 scripts/hostapd diff --git a/scripts/hostapd b/scripts/hostapd deleted file mode 100755 index 0e17e57f..00000000 --- a/scripts/hostapd +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh - -### BEGIN INIT INFO -# Provides: hostapd -# Required-Start: $remote_fs -# Required-Stop: $remote_fs -# Should-Start: $network -# Should-Stop: -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Advanced IEEE 802.11 management daemon -# Description: Userspace IEEE 802.11 AP and IEEE 802.1X/WPA/WPA2/EAP -# Authenticator -### END INIT INFO - -PATH=/sbin:/bin:/usr/sbin:/usr/bin -DAEMON_SBIN=/usr/sbin/hostapd -DAEMON_DEFS=/etc/default/hostapd -DAEMON_CONF=/etc/hostapd/hostapd.conf -NAME=hostapd -DESC="advanced IEEE 802.11 management" -PIDFILE=/var/run/hostapd.pid - -[ -x "$DAEMON_SBIN" ] || exit 0 -[ -s "$DAEMON_DEFS" ] && . /etc/default/hostapd -[ -n "$DAEMON_CONF" ] || exit 0 - - -. /lib/lsb/init-functions - -do_start() { - log_daemon_msg "Starting $DESC" "$NAME" - start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile \ - --startas $DAEMON_SBIN -- $DAEMON_CONF - log_end_msg $? -} - -do_stop() { - log_daemon_msg "Stopping $NAME" - start-stop-daemon --stop --pidfile $PIDFILE --retry 10 - log_end_msg $? -} - -case "$1" in - start|stop) - do_${1} - ;; - reload) - log_daemon_msg "Reloading $DESC" "$NAME" - start-stop-daemon --stop --signal HUP --exec "$DAEMON_SBIN" \ - --pidfile "$PIDFILE" - log_end_msg "$?" - ;; - restart|force-reload) - do_stop - sleep 8 - do_start - ;; - status) - status_of_proc "$DAEMON_SBIN" "$NAME" - exit $? - ;; - *) - N=/etc/init.d/$NAME - echo "Usage: $N {start|stop|restart|force-reload|reload|status}" >&2 - exit 1 - ;; -esac - -exit 0 From ac853aa9ecb39e822b6b06d6302b5f329b295ea0 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 21 Jul 2015 22:56:49 +0000 Subject: [PATCH 26/53] changed wifi.py to work with new dongles Changed wifi config, there are now 2 interfaces files, client and ap. TH pi now has to startup with client interfaces, try and connect and if cant, then change interfaces to ap and call sudo service networking restart. (this only works one way unfortunately, if you wanna go back to client restart the pi) --- scripts/etc/network/interfaces_ap | 12 ++++ scripts/etc/network/interfaces_cli | 13 +++++ wifi.py | 90 +++++------------------------- 3 files changed, 38 insertions(+), 77 deletions(-) create mode 100644 scripts/etc/network/interfaces_ap create mode 100644 scripts/etc/network/interfaces_cli diff --git a/scripts/etc/network/interfaces_ap b/scripts/etc/network/interfaces_ap new file mode 100644 index 00000000..2043ea4c --- /dev/null +++ b/scripts/etc/network/interfaces_ap @@ -0,0 +1,12 @@ +auto lo +iface lo inet loopback +iface eth0 inet dhcp +#allow-hotplug wlan0 +#iface wlan0 inet manual +#wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf +#iface default inet dhcp +iface wlan0 inet static +address 10.0.0.1 +netmask 255.255.255.0 +#wireless-power off + diff --git a/scripts/etc/network/interfaces_cli b/scripts/etc/network/interfaces_cli new file mode 100644 index 00000000..65676d54 --- /dev/null +++ b/scripts/etc/network/interfaces_cli @@ -0,0 +1,13 @@ +auto lo +iface lo inet loopback +iface eth0 inet dhcp + +allow-hotplug wlan0 +iface wlan0 inet dhcp +#iface default inet dhcp + +wpa-ssid "Lboro_Coderbot_Hub" +wpa-psk "welovepis" + +#wireless-power off + diff --git a/wifi.py b/wifi.py index 1be11dad..337e23ce 100755 --- a/wifi.py +++ b/wifi.py @@ -51,15 +51,14 @@ def get_adapter_type(cls): def start_hostapd(cls): try: print "starting hostapd..." - os.system("/etc/init.d/hostapd restart") - os.system("ifconfig wlan0 10.0.0.1") + os.system("sudo service hostapd restart") except subprocess.CalledProcessError as e: print e.output @classmethod def stop_hostapd(cls): try: - os.system("/etc/init.d/hostapd stop") + os.system("sudo service hostapd stop") except subprocess.CalledProcessError as e: print e.output @@ -115,78 +114,6 @@ def register_ipaddr(cls, ipaddr, botname): raise print botname, ": ", ipaddr - @classmethod - def get_wlans(cls): - out = subprocess.check_output(["iwlist", "wlan0", "scan"]) - - @classmethod - def set_client_params(cls, wssid, wpsk): - f = open (cls.wifi_client_conf_file, "w+") - f.write("""ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev -update_config=1 -network={\n""") - f.write(" ssid=\""+wssid+"\"\n") - f.write(" psk=\""+wpsk+"\"\n") - f.write("}") - - # set WIFI client mode - @classmethod - def set_start_as_client(cls): - shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") - # disable hostapd start-on-boot - os.system('sudo update-rc.d hostapd remove') - cls._config["wifi_mode"] = "client" - cls.save_config() - - # start as client to coderbot server - @classmethod - def start_as_client(cls): - cls.stop_hostapd() - try: - out = subprocess.check_output(["ifdown", "wlan0"]) - out = subprocess.check_output(["ifup", "wlan0"]) - print "registering ip..." - cls.register_ipaddr(cls.get_ipaddr("wlan0"), "CoderBot") - except subprocess.CalledProcessError as e: - print e.output - raise - - # set WIFI client mode and record as "local_client" - @classmethod - def set_start_as_local_client(cls): - shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") - # disable hostapd start-on-boot - os.system('sudo update-rc.d hostapd remove') - cls._config["wifi_mode"] = "local_client" - cls.save_config() - - # start as client without registering to coderbot server - @classmethod - def start_as_local_client(cls): - cls.stop_hostapd() - try: - out = subprocess.check_output(["ifdown", "wlan0"]) - out = subprocess.check_output(["ifup", "wlan0"]) - except: - print e.output - raise - - - @classmethod - def set_start_as_ap(cls): - shutil.copy("/etc/network/interfaces_ap", "/etc/network/interfaces") - # assuming /etc/init.d/hostapd start script exists - # enable hostapd start-on-boot - os.system('sudo update-rc.d hostapd defaults') - cls._config["wifi_mode"] = "ap" - cls.save_config() - - @classmethod - def start_as_ap(cls): - out = subprocess.check_output(["ifdown", "wlan0"]) - out = subprocess.check_output(["ifup", "wlan0"]) - cls.start_hostapd() - @classmethod def start_service(cls): config = cls.load_config() @@ -211,8 +138,9 @@ def start_service(cls): def main(): w = WiFi() - print 'Wait 1 seconds before checking connection to router...' - sleep(1) + print 'Testing Client Connection...' + print 'Wait 3 seconds before checking connection to router...' + sleep(3) print 'pinging router...' #ping hub router response = os.system('ping -c 1 192.168.0.1') @@ -222,7 +150,15 @@ def main(): print 'Router has been found, staying on client mode' else: print 'Router not found, switching to AP mode' + #setup hotspot + shutil.copy("/etc/network/interfaces_ap", "/etc/network/interfaces") + print 'restart networking...' + os.system('sudo service networking restart') w.start_hostapd() + print 'Waiting for hostapd to startup' + sleep(3) + print 'copying client interfaces back for next time' + shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") if len(sys.argv) > 2 and sys.argv[1] == "updatecfg": if len(sys.argv) > 2 and sys.argv[2] == "ap": From cc975076fdab1783f005c68fab0ffbc72befca13 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Thu, 23 Jul 2015 14:41:27 +0000 Subject: [PATCH 27/53] Added command to set pi number e.g sudo python wifi.py updatecfg 06 will change the client ip to 192.168.0.106 and set the ssid to Lboro Coderbot 06 --- scripts/etc/network/interfaces_cli | 14 ++-- wifi.py | 124 +++++++++++++++++------------ 2 files changed, 81 insertions(+), 57 deletions(-) diff --git a/scripts/etc/network/interfaces_cli b/scripts/etc/network/interfaces_cli index 65676d54..9fc6e507 100644 --- a/scripts/etc/network/interfaces_cli +++ b/scripts/etc/network/interfaces_cli @@ -1,13 +1,11 @@ auto lo iface lo inet loopback iface eth0 inet dhcp - allow-hotplug wlan0 -iface wlan0 inet dhcp -#iface default inet dhcp - -wpa-ssid "Lboro_Coderbot_Hub" -wpa-psk "welovepis" - -#wireless-power off +iface wlan0 inet static +address 192.168.0.105 +netmask 255.255.255.0 +gateway 192.168.0.1 +wpa-ssid Lboro_Coderbot_Hub +wpa-psk welovepis diff --git a/wifi.py b/wifi.py index 337e23ce..0e493ed0 100755 --- a/wifi.py +++ b/wifi.py @@ -17,6 +17,7 @@ class WiFi(): CONFIG_FILE = "/etc/coderbot_wifi.conf" HOSTAPD_CONF_FILE = "/etc/hostapd/hostapd.conf" + INTERFACES_FILE = "/etc/network/interfaces_cli" adapters = ["RT5370", "RTL8188CUS", "RT3572"] hostapds = {"RT5370": "hostapd.RT5370", "RTL8188CUS": "hostapd.RTL8188"} web_url = "http://coderbotsrv.appspot.com/register_ip" @@ -91,6 +92,42 @@ def set_hostapd_params(cls, wssid, wpsk): except IOError as e: print e + def set_client_params(cls, wssid, wpsk, number): + config = ConfigParser.ConfigParser() + # configparser requires sections like '[section]' + # open hostapd.conf with dummy section '[interfaces]' + + try: + with open(cls.INTERFACES_FILE) as f: + inter_split = f.read().split("static") + inter_start = inter_split[0] + "static\n" + conf_str = '[interfaces]\n' + inter_split[1] + #parser needs = to work + conf_str = conf_str.replace(" ","=") + conf_fp = StringIO.StringIO(conf_str) + config.readfp(conf_fp) + + except IOError as e: + print e + return + + config.set('interfaces','address',"192.168.0.1" + number) + config.set('interfaces','wpa-ssid',str(wssid)) + config.set('interfaces','wpa-psk',str(wpsk)) + + try: + with open(cls.INTERFACES_FILE, 'wb') as f: + conf_items = config.items('interfaces') + f.write(inter_start) + for (key,value) in conf_items: + #quick workaround for values with spaces in + value = value.replace("wlan0=inet=static","wlan0 inet static") + f.write("{0} {1}\n".format(key, value)) + f.write("\n") + except IOError as e: + print e + shutil.copy(cls.INTERFACES_FILE, "/etc/network/interfaces") + @classmethod def get_ipaddr(cls, ifname): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -137,57 +174,46 @@ def start_service(cls): def main(): w = WiFi() - - print 'Testing Client Connection...' - print 'Wait 3 seconds before checking connection to router...' - sleep(3) - print 'pinging router...' - #ping hub router - response = os.system('ping -c 1 192.168.0.1') - #healthy response is 0 - - if response == 0: - print 'Router has been found, staying on client mode' - else: - print 'Router not found, switching to AP mode' - #setup hotspot - shutil.copy("/etc/network/interfaces_ap", "/etc/network/interfaces") - print 'restart networking...' - os.system('sudo service networking restart') - w.start_hostapd() - print 'Waiting for hostapd to startup' + if len(sys.argv) < 2: + print 'Testing Client Connection...' + print 'Wait 3 seconds before checking connection to router...' sleep(3) - print 'copying client interfaces back for next time' - shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") + print 'pinging router...' + #ping hub router + response = os.system('ping -c 1 192.168.0.1') + #healthy response is 0 + + if response == 0: + print 'Router has been found, staying on client mode' + else: + print 'Router not found, switching to AP mode' + #setup hotspot + shutil.copy("/etc/network/interfaces_ap", "/etc/network/interfaces") + print 'restart networking...' + os.system('sudo service networking restart') + w.start_hostapd() + print 'Waiting for hostapd to startup' + sleep(3) + print 'copying client interfaces back for next time' + shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") - if len(sys.argv) > 2 and sys.argv[1] == "updatecfg": - if len(sys.argv) > 2 and sys.argv[2] == "ap": - if len(sys.argv) > 3: - w.set_hostapd_params(sys.argv[3], sys.argv[4]) - #w.set_start_as_ap() - #w.start_as_ap() - elif len(sys.argv) > 2 and sys.argv[2] == "hub": - if len(sys.argv) > 3: - w.set_client_params(sys.argv[3], sys.argv[4]) - #w.set_start_as_client() - #w.stop_hostapd() - #try: - # w.start_as_client() - #except: - # print "Unable to register ip, revert to ap mode" - # w.start_as_ap() - elif len(sys.argv) > 2 and sys.argv[2] == "local_client": - if len(sys.argv) > 3: - w.set_client_params(sys.argv[3], sys.argv[4]) - w.set_start_as_client() - try: - w.start_as_local_client() - except: - print "Unable to connect to WLAN, revert to ap mode" - w.start_as_ap() - - #else: - # w.start_service() + elif sys.argv[1] == "updatecfg": + if len(sys.argv) == 3: + print "updating configs..." + #Update config to use this number, eg. for 7 + #client ip goes to 192.168.0.7 + w.set_client_params("Lboro_Coderbot_Hub","welovepis", sys.argv[2]) + #hostspot ssid goes to Lboro Coderbot 7 + w.set_hostapd_params("Lboro Coderbot " + sys.argv[2], "coderbot") + print "done!" + else: + if len(sys.argv) > 2 and sys.argv[2] == "ap": + if len(sys.argv) > 3: + w.set_hostapd_params(sys.argv[3], sys.argv[4]) + elif len(sys.argv) > 2 and sys.argv[2] == "client": + if len(sys.argv) > 4: + w.set_client_params(sys.argv[3], sys.argv[4], sys.argv[5]) + if __name__ == "__main__": main() From 78558e70b4199fa62ea3e5bfdf5eb37bc8464c59 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 27 Jul 2015 12:29:00 +0100 Subject: [PATCH 28/53] added bounds to dutycycle for servo control --- coderbot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/coderbot.py b/coderbot.py index a12debb0..9135d03c 100644 --- a/coderbot.py +++ b/coderbot.py @@ -116,6 +116,8 @@ def _servo_motor_control(self, pin, speed): # transform speed value from -100 to +100 range # to servo duty cycle range: 100 to 200 speed = 150 + speed/2 + if (speed < 90): speed = 90 + if (speed > 210): speed = 210 self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, speed) @@ -125,6 +127,8 @@ def _servo_control(self, pin, angle): # assuming angle range is 0 to 120 # transform from angle range to servo duty cycle range (100 to 200) duty = angle + 90 # (90-210) + if (duty < 90): duty = 90 + if (duty > 210): duty = 210 self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, duty) From 1da8ef4ab3e1eb366689e68139742bac56a5f275 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Mon, 27 Jul 2015 12:57:20 +0000 Subject: [PATCH 29/53] Changed wifi.py timings --- wifi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wifi.py b/wifi.py index 0e493ed0..d880eeee 100755 --- a/wifi.py +++ b/wifi.py @@ -176,8 +176,8 @@ def main(): w = WiFi() if len(sys.argv) < 2: print 'Testing Client Connection...' - print 'Wait 3 seconds before checking connection to router...' - sleep(3) + print 'Wait 5 seconds before checking connection to router...' + sleep(5) print 'pinging router...' #ping hub router response = os.system('ping -c 1 192.168.0.1') From a43425b63df5a56c86739330af966ec815d7f1a4 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Mon, 27 Jul 2015 13:22:55 +0000 Subject: [PATCH 30/53] Fixed servo limit checks --- coderbot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coderbot.py b/coderbot.py index 9135d03c..1ceac5b5 100644 --- a/coderbot.py +++ b/coderbot.py @@ -116,8 +116,8 @@ def _servo_motor_control(self, pin, speed): # transform speed value from -100 to +100 range # to servo duty cycle range: 100 to 200 speed = 150 + speed/2 - if (speed < 90): speed = 90 - if (speed > 210): speed = 210 + if speed < 90: speed = 90 + if speed > 210: speed = 210 self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, speed) @@ -127,8 +127,8 @@ def _servo_control(self, pin, angle): # assuming angle range is 0 to 120 # transform from angle range to servo duty cycle range (100 to 200) duty = angle + 90 # (90-210) - if (duty < 90): duty = 90 - if (duty > 210): duty = 210 + if duty < 90: duty = 90 + if duty > 210: duty = 210 self.pi.set_PWM_range(pin, PWM_RANGE) self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) self.pi.set_PWM_dutycycle(pin, duty) From 1cfee933c2be44d55c2c63d2c68fa67b641b5c5a Mon Sep 17 00:00:00 2001 From: Matt Blickem Date: Mon, 27 Jul 2015 14:23:28 +0100 Subject: [PATCH 31/53] fixed table formatting in scripts readme --- scripts/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/README.md b/scripts/README.md index f52d2c82..ccd809a4 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,4 +1,5 @@ |Script|Live Dir| +|------|--------| |coderbot|/etc/init.d/| |hostapd|/etc/init.d/| |interfaces_ap|/etc/network/| From 24fb6c1db5be46bb81172083ece29d7aa07ffe4a Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Mon, 27 Jul 2015 13:43:14 +0000 Subject: [PATCH 32/53] fixed wifi internet issue --- scripts/etc/network/interfaces_cli | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/etc/network/interfaces_cli b/scripts/etc/network/interfaces_cli index 9fc6e507..80e54eeb 100644 --- a/scripts/etc/network/interfaces_cli +++ b/scripts/etc/network/interfaces_cli @@ -1,6 +1,7 @@ auto lo iface lo inet loopback -iface eth0 inet dhcp +#iface eth0 inet dhcp +#iface default inet dhcp allow-hotplug wlan0 iface wlan0 inet static address 192.168.0.105 From 048deec095182b031715e838ad3fe713e1cf1c6e Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 29 Jul 2015 09:17:59 +0000 Subject: [PATCH 33/53] Added led blocks to all run levels. --- coderbot.py | 32 +++++++- static/js/blockly/blocks.js | 127 ++++++++++++++++++++++++++++++++ static/js/blockly/bot_en.js | 3 + templates/blocks_adv.xml | 14 +++- templates/blocks_basic.xml | 10 +++ templates/blocks_basic_move.xml | 9 +++ templates/blocks_std.xml | 13 +++- 7 files changed, 205 insertions(+), 3 deletions(-) diff --git a/coderbot.py b/coderbot.py index 1ceac5b5..4280f09a 100644 --- a/coderbot.py +++ b/coderbot.py @@ -10,6 +10,9 @@ PIN_PUSHBUTTON = 18 PIN_SERVO_3 = 9 # ARM PIN PIN_SERVO_4 = 10 +LED_RED = 21 +LED_YELLOW = 20 +LED_GREEN = 16 PWM_FREQUENCY = 50 #Hz PWM_RANGE = 2000 #100-200 duty cycle operating range for servos @@ -174,4 +177,31 @@ def reboot(self): os.system ('sudo reboot') - + def LED_on(self, Colour, seconds, pin=0): + if Colour == "Red LED": + pin = 21 + elif Colour == "Green LED": + pin = 16 + elif Colour == "Yellow LED": + pin = 20 + self.pi.write(pin, 1) + time.sleep(seconds) + self.pi.write(pin, 0) + + def LED_on_indef(self, Colour, pin=0): + if Colour == "Red LED": + pin = 21 + elif Colour == "Green LED": + pin = 16 + elif Colour == "Yellow LED": + pin = 20 + self.pi.write(pin, 1) + + def LED_off_indef(self, Colour, pin=0): + if Colour == "Red LED": + pin = 21 + elif Colour == "Green LED": + pin = 16 + elif Colour == "Yellow LED": + pin = 20 + self.pi.write(pin, 0) diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 6d2b284f..5f14c451 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -820,3 +820,130 @@ Blockly.Python['coderbot_armLower'] = function(block) { // Generate Python code for lowering the arm return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_LOWERED + ')\n'; }; + + +Blockly.Blocks['coderbot_printCircle'] = { + /** + * Block for printing a circle on the screen at a given coordinate + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("colourX") + .setCheck("Number") + .appendField("Print circle of colour") + .appendField(new Blockly.FieldColour("#ff0000"), "COLOUR_PARAM") + .appendField("at coordinates X"); + this.appendValueInput("colourY") + .setCheck("Number") + .appendField(" and Y"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(30); + this.setTooltip(''); + this.setHelpUrl('http://www.example.com/'); + } +}; + +Blockly.JavaScript['coderbot_printCircle'] = function(block) { + var colour_param = block.getFieldValue('COLOUR_PARAM'); + var value_colourx = Blockly.JavaScript.valueToCode(block, 'colourX', Blockly.JavaScript.ORDER_ATOMIC); + var value_coloury = Blockly.JavaScript.valueToCode(block, 'colourY', Blockly.JavaScript.ORDER_ATOMIC); + // TODO: Assemble JavaScript into code variable. + return 'get_cam().drawCircle(' + colour_param + ',' + value_colourx + ',' + value_coloury + ';\n'; +}; + +Blockly.Python['coderbot_printCircle'] = function(block) { + var colour_param = block.getFieldValue('COLOUR_PARAM'); + var value_colourx = Blockly.Python.valueToCode(block, 'colourX', Blockly.Python.ORDER_ATOMIC); + var value_coloury = Blockly.Python.valueToCode(block, 'colourY', Blockly.Python.ORDER_ATOMIC); + // TODO: Assemble Python into code variable. + return 'get_cam().drawCircle(' + colour_param + ',' + value_colourx + ',' + value_coloury + ';\n'; +}; + +Blockly.Blocks['turn_led_on'] = { + init: function() { + this.appendValueInput("seconds") + .setCheck("Number") + .appendField("Turn the ") + .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["yellow", "Yellow LED"]]), "Colour") + .appendField("LED on for "); + this.appendDummyInput() + .appendField("seconds"); + this.setInputsInline(true); + this.setHelpUrl(Blockly.Msg.CODERBOT_LED); + this.setColour(355); + this.setPreviousStatement(true,null); + this.setNextStatement(true,null); + this.setTooltip('Coderbot_LED'); + } +}; + +Blockly.JavaScript['turn_led_on'] = function(block) { + // Generate JavaScrip code for LED lights + var dropdown_colour = block.getFieldValue('Colour'); + var time = Blockly.Javascript.valueToCode(block, 'seconds', Blockly.Javascript.ORDER_ATOMIC); + return 'get_bot().LED_on();\n'; +}; + +Blockly.Python['turn_led_on'] = function(block) { + // Generate Python code for LED lights + var dropdown_colour = block.getFieldValue('Colour'); + var time = Blockly.Python.valueToCode(block, 'seconds', Blockly.Python.ORDER_ATOMIC); + var code = "get_bot().LED_on(Colour=\"" + dropdown_colour + "\", seconds=" +time + ")\n"; + return code; +}; + +Blockly.Blocks['led_on'] = { + init: function() { + this.appendDummyInput() + .appendField("Turn the ") + .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["yellow", "Yellow LED"]]), "Colour") + .appendField("LED on"); + this.setHelpUrl(Blockly.Msg.CODERBOT_LED_ON); + this.setColour(355); + this.setPreviousStatement(true,null); + this.setNextStatement(true,null); + this.setTooltip('Coderbot_LED'); + } +}; + +Blockly.JavaScript['led_on'] = function(block) { + // Generate JavaScrip code for LED lights + var dropdown_colour = block.getFieldValue('Colour'); + return 'get_bot().LED_on_indef();\n'; +}; + +Blockly.Python['led_on'] = function(block) { + // Generate Python code for LED lights + var dropdown_colour = block.getFieldValue('Colour'); + var code = "get_bot().LED_on_indef(Colour=\"" + dropdown_colour + "\"" + ")\n"; + return code; +}; + +Blockly.Blocks['led_off'] = { + init: function() { + this.appendDummyInput() + .appendField("Turn the ") + .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["yellow", "Yellow LED"]]), "Colour") + .appendField("LED off"); + this.setHelpUrl(Blockly.Msg.CODERBOT_LED_OFF); + this.setColour(355); + this.setPreviousStatement(true,null); + this.setNextStatement(true,null); + this.setTooltip('Coderbot_LED'); + } +}; + +Blockly.JavaScript['led_off'] = function(block) { + // Generate JavaScrip code for LED lights + var dropdown_colour = block.getFieldValue('Colour'); + return 'get_bot().LED_on_indef();\n'; +}; + +Blockly.Python['led_off'] = function(block) { + // Generate Python code for LED lights + var dropdown_colour = block.getFieldValue('Colour'); + var code = "get_bot().LED_off_indef(Colour=\"" + dropdown_colour + "\"" + ")\n"; + return code; +}; diff --git a/static/js/blockly/bot_en.js b/static/js/blockly/bot_en.js index 1fb0f1a7..7b341ae0 100644 --- a/static/js/blockly/bot_en.js +++ b/static/js/blockly/bot_en.js @@ -48,3 +48,6 @@ Blockly.Msg.CODERBOT_SENSOR_FINDFACE_Y = "y coord"; Blockly.Msg.CODERBOT_SENSOR_FINDFACE_SIZE = "size"; Blockly.Msg.CODERBOT_SENSOR_FINDFACE_ALL = "x, y, size (as list)"; Blockly.Msg.CODERBOT_SENSOR_FINDLOGO = "find logo"; +Blockly.Msg.CODERBOT_LED = "turn LED on for a set time"; +Blockly.Msg.CODERBOT_LED_ON = "turns LED on" +Blockly.Msg.CODERBOT_LED_OFF = "turns LED off" \ No newline at end of file diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index e0e0b53e..d9d88b48 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -166,6 +166,7 @@ +
@@ -249,5 +250,16 @@ - + + + + + + 3 + + + + + + diff --git a/templates/blocks_basic.xml b/templates/blocks_basic.xml index cd6a601d..c709e3cc 100644 --- a/templates/blocks_basic.xml +++ b/templates/blocks_basic.xml @@ -5,6 +5,7 @@ + @@ -22,4 +23,13 @@ + + + + 3 + + + + + diff --git a/templates/blocks_basic_move.xml b/templates/blocks_basic_move.xml index 18e73883..0937011d 100644 --- a/templates/blocks_basic_move.xml +++ b/templates/blocks_basic_move.xml @@ -5,4 +5,13 @@ + + + + 3 + + + + + diff --git a/templates/blocks_std.xml b/templates/blocks_std.xml index a930ab3a..2520c1f9 100644 --- a/templates/blocks_std.xml +++ b/templates/blocks_std.xml @@ -76,5 +76,16 @@ - + + + + + + 3 + + + + + + From 31078c3ad135bd64a7749ff57923a578ba1ef2c9 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 29 Jul 2015 13:28:02 +0000 Subject: [PATCH 34/53] WIP changes to camera functions --- camera.py | 167 +++++++++---- camera.py.original | 382 +++++++++++++++++++++++++++++ data/program_findcolortest.data | 1 + data/program_no_name.data | 1 + scripts/READMEOLD.md | 16 ++ scripts/etc/network/interfaces_hub | 13 - scripts/interfaces_ap | 12 - scripts/interfaces_cli | 9 - scripts/wifi | 2 +- static/js/blockly/blocks.js | 40 +++ templates/blocks_adv.xml | 1 + viz/camera.py | 6 +- viz/image.py | 9 + 13 files changed, 576 insertions(+), 83 deletions(-) create mode 100644 camera.py.original create mode 100644 data/program_findcolortest.data create mode 100644 data/program_no_name.data create mode 100644 scripts/READMEOLD.md delete mode 100644 scripts/etc/network/interfaces_hub delete mode 100644 scripts/interfaces_ap delete mode 100644 scripts/interfaces_cli diff --git a/camera.py b/camera.py index 6b0df8a8..cf9f6a6c 100644 --- a/camera.py +++ b/camera.py @@ -11,6 +11,10 @@ from viz import camera, streamer, image, blob import config +import picamera +import picamera.array +import io + CAMERA_REFRESH_INTERVAL=0.1 MAX_IMAGE_AGE = 0.0 PHOTO_PATH = "./photos" @@ -20,6 +24,10 @@ PHOTO_THUMB_SUFFIX = "_thumb" PHOTO_THUMB_SIZE = (240,180) VIDEO_ELAPSE_MAX = 900 +PHOTO_FILE_EXT = ".jpg" +FFMPEG_CMD = "MP4Box" +VIDEO_FILE_EXT = ".mp4" +VIDEO_FILE_EXT_H264 = ".h264" class Camera(Thread): @@ -28,6 +36,8 @@ class Camera(Thread): _warp_corners_1 = [(0, -120), (640, -120), (380, 480), (260, 480)] _warp_corners_2 = [(0, -60), (320, -60), (190, 240), (130, 240)] _warp_corners_4 = [(0, -30), (160, -30), (95, 120), (65, 120)] + _image_cache = None + _image_cache_updated = False stream_port = 9080 @classmethod @@ -39,19 +49,7 @@ def get_instance(cls): def __init__(self): logging.info("starting camera") - cam_props = { - "width":640, - "height":480, - "exposure_mode": config.Config.get().get("camera_exposure_mode"), - "rotation": config.Config.get().get("camera_rotation") - } - #try initialising the camera - try: - self._camera = camera.Camera(props=cam_props) - except: - self._camera = None - logging.error("Unexpected error:" + str(sys.exc_info()[0])) - pass + self.init_camera() self._streamer = streamer.JpegStreamer("0.0.0.0:"+str(self.stream_port), st=0.1) #self._cam_off_img.save(self._streamer) @@ -70,22 +68,82 @@ def __init__(self): super(Camera, self).__init__() + def init_camera(self): + try: + props = { + "width":640, + "height":480, + "exposure_mode":config.Config.get().get("camera_exposure_mode"), + "rotation":config.Config.get().get("camera_rotation") + } + self._camera = picamera.PiCamera() + cam = self._camera + res = (props.get("width",640), props.get("height",480)) + cam.resolution = res + cam.framerate = 30 + cam.exposure_mode = props.get("exposure_mode") + cam.rotation = props.get("rotation") + self.out_jpeg = io.BytesIO() + self.out_rgb = picamera.array.PiRGBArray(cam, size=res) + self.h264_encoder = None + self.recording = None + self.video_filename = None + except: + self._camera = None + logging.error("Could not initialise camera:" + str(sys.exc_info()[0])) + pass + + def grab_start(self): + logging.debug("grab_start") + camera_port_0, output_port_0 = self._camera._get_ports(True, 0) + self.jpeg_encoder = self._camera._get_image_encoder(camera_port_0, output_port_0, 'jpeg', None, quality=40) + camera_port_1, output_port_1 = self._camera._get_ports(True, 1) + self.rgb_encoder = self._camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', res) + + with self._camera._encoders_lock: + self._camera._encoders[0] = self.jpeg_encoder + self._camera._encoders[1] = self.rgb_encoder + + def grab_one(self): + self.out_jpeg.seek(0) + self.out_rgb.seek(0) + self.jpeg_encoder.start(self.out_jpeg) + self.rgb_encoder.start(self.out_rgb) + if not self.jpeg_encoder.wait(10): + raise picamera.PiCameraError('Timed Out') + if not self.rgb_encoder.wait(10): + raise picamera.PiCameraError('Timed Out') + + def grab_stop(self): + logging.debug("grab_stop") + + with self._camera._encoders_lock: + del self._camera._encoders[0] + del self._camera._encoders[1] + + self.jpeg_encoder.close() + self.rgb_encoder.close() + def run(self): if self._camera is None: return try: - self._camera.grab_start() + self.grab_start() while self._run: sleep_time = CAMERA_REFRESH_INTERVAL - (time.time() - self._image_time) if sleep_time <= 0: ts = time.time() #print "run.1" self._image_lock.acquire() - self._camera.grab_one() + self.grab_one() self._image_lock.release() #print "run.2: " + str(time.time()-ts) #self.save_image(image.Image(self._camera.get_image_bgr()).open().binarize().to_jpeg()) - self.save_image(self._camera.get_image_jpeg()) + if self._image_cache_updated is True: + self.save_image(self._image_cache.to_jpeg()) + self._image_cache_updated = False + else: + self.save_image(self.out_jpeg.getvalue()) #print "run.3: " + str(time.time()-ts) else: time.sleep(sleep_time) @@ -93,16 +151,17 @@ def run(self): if self.recording and time.time() - self.video_start_time > VIDEO_ELAPSE_MAX: self.video_stop() - self._camera.grab_stop() + self.grab_stop() except: logging.error("Unexpected error:" + str(sys.exc_info()[0])) raise def get_image(self, maxage = MAX_IMAGE_AGE): if self._camera is None: + print "get_image: Default image" return Image(cv2.imread(DEFAULT_IMAGE)) else: - return image.Image(self._camera.get_image_bgr()) + return image.Image(self.out_rgb.array) def save_image(self, image_jpeg): self._streamer.set_image(image_jpeg) @@ -111,13 +170,13 @@ def save_image(self, image_jpeg): def set_text(self, text): if self._camera is None: return - self._camera.set_overlay_text(str(text)) + self._camera.annotate_text(str(text)) def get_next_photo_index(self): last_photo_index = 0 for p in self._photos: try: - index = int(p[len(PHOTO_PREFIX):-len(self._camera.PHOTO_FILE_EXT)]) + index = int(p[len(PHOTO_PREFIX):-len(self.PHOTO_FILE_EXT)]) if index > last_photo_index: last_photo_index = index except: @@ -127,21 +186,21 @@ def get_next_photo_index(self): def rotate(self): if self._camera is None: return - rot = self._camera.camera.rotation + rot = self._camera.rotation rot = rot + 90 if rot > 271: rot = 0 - self._camera.camera.rotation = rot + self._camera.rotation = rot def photo_take(self): if self._camera is None: return photo_index = self.get_next_photo_index() - filename = PHOTO_PREFIX + str(photo_index) + self._camera.PHOTO_FILE_EXT; - filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + filename = PHOTO_PREFIX + str(photo_index) + PHOTO_FILE_EXT; + filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; of = open(PHOTO_PATH + "/" + filename, "w+") oft = open(PHOTO_PATH + "/" + filename_thumb, "w+") - im_str = self._camera.get_image_jpeg() + im_str = self.out_jpeg.getvalue() of.write(im_str) # thumb im_pil = PILImage.open(StringIO(im_str)) @@ -152,12 +211,11 @@ def is_recording(self): return self.recording def video_rec(self, video_name=None): - if self.is_recording(): + if self.recording: return - self.recording = True if self._camera is None: return - + self.recording = True if video_name is None: video_index = self.get_next_photo_index() filename = VIDEO_PREFIX + str(video_index) + self._camera.VIDEO_FILE_EXT; @@ -328,29 +386,48 @@ def find_color(self, s_color): self._image_lock.acquire() img = self.get_image(0) self._image_lock.release() + if img is None: + print "Image from get_image is None!" + return [0,0] bw = img.filter_color(color) + contours, hierarchy = bw.find_contours() + for contour in contours: + img.draw_contour_bound_circle(contour) + self._image_cache = img + self._image_cache_updated = True + if self._image_cache is None: + print "Image cache is None!" #self.save_image(bw.to_jpeg()) - objects = bw.find_blobs(minsize=20, maxsize=1000) - logging.debug("objects: " + str(objects)) - dist = -1 - angle = 180 - - if objects and len(objects): - obj = objects[-1] - bottom = obj.bottom - logging.info("bottom: " + str(obj.center[0]) + " " +str(obj.bottom)) - coords = bw.transform([(obj.center[0], obj.bottom)]) - logging.info("coordinates: " + str(coords)) - x = coords[0][0] - y = coords[0][1] - dist = math.sqrt(math.pow(12 + (68 * (120 - y) / 100),2) + (math.pow((x-80)*60/160,2))) - angle = math.atan2(x - 80, 120 - y) * 180 / math.pi - logging.info("object found, dist: " + str(dist) + " angle: " + str(angle)) + #objects = bw.find_blobs(minsize=5, maxsize=100) + #logging.debug("objects: " + str(objects)) + #dist = -1 + #angle = 180 + + #if objects and len(objects): + #obj = objects[-1] + #bottom = obj.bottom + #logging.info("bottom: " + str(obj.center[0]) + " " +str(obj.bottom)) + #coords = bw.transform([(obj.center[0], obj.bottom)]) + #logging.info("coordinates: " + str(coords)) + #x = coords[0][0] + #y = coords[0][1] + #dist = math.sqrt(math.pow(12 + (68 * (120 - y) / 100),2) + (math.pow((x-80)*60/160,2))) + #angle = math.atan2(x - 80, 120 - y) * 180 / math.pi + #logging.info("object found, dist: " + str(dist) + " angle: " + str(angle)) #self.save_image(img.to_jpeg()) #print "object: " + str(time.time() - ts) - return [dist, angle] + #return [dist, angle] + return [0,0] + def sleep(self, elapse): logging.debug("sleep: " + str(elapse)) time.sleep(elapse) + def drawCircle(self,colour,x,y): + cv2.circle(self._camera.get_image_bgr(),(x,y),30,(0,0,255), -1) + #cv2.circle(self.get_image(0) + + + + diff --git a/camera.py.original b/camera.py.original new file mode 100644 index 00000000..e24c55f0 --- /dev/null +++ b/camera.py.original @@ -0,0 +1,382 @@ +import time +import copy +import os +import sys +import math +from PIL import Image as PILImage +from StringIO import StringIO +from threading import Thread, Lock +import logging + +from viz import camera, streamer, image, blob +import config + +CAMERA_REFRESH_INTERVAL=0.1 +MAX_IMAGE_AGE = 0.0 +PHOTO_PATH = "./photos" +DEFAULT_IMAGE = "./photos/broken.jpg" +PHOTO_PREFIX = "DSC" +VIDEO_PREFIX = "VID" +PHOTO_THUMB_SUFFIX = "_thumb" +PHOTO_THUMB_SIZE = (240,180) +VIDEO_ELAPSE_MAX = 900 + +class Camera(Thread): + + _instance = None + _img_template = image.Image.load("coderdojo-logo.png") + _warp_corners_1 = [(0, -120), (640, -120), (380, 480), (260, 480)] + _warp_corners_2 = [(0, -60), (320, -60), (190, 240), (130, 240)] + _warp_corners_4 = [(0, -30), (160, -30), (95, 120), (65, 120)] + _image_cache = None + _image_cache_updated = False + stream_port = 9080 + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = Camera() + cls._instance.start() + return cls._instance + + def __init__(self): + logging.info("starting camera") + cam_props = { + "width":640, + "height":480, + "exposure_mode": config.Config.get().get("camera_exposure_mode"), + "rotation": config.Config.get().get("camera_rotation") + } + #try initialising the camera + try: + self._camera = camera.Camera(props=cam_props) + except: + self._camera = None + logging.error("Unexpected error:" + str(sys.exc_info()[0])) + pass + + self._streamer = streamer.JpegStreamer("0.0.0.0:"+str(self.stream_port), st=0.1) + #self._cam_off_img.save(self._streamer) + self.recording = False + self.video_start_time = time.time() + 8640000 + self._run = True + self._image_time = 0 + self._image_lock = Lock() + + self._photos = [] + + for dirname, dirnames, filenames, in os.walk(PHOTO_PATH): + for filename in filenames: + if (PHOTO_PREFIX in filename or VIDEO_PREFIX in filename) and PHOTO_THUMB_SUFFIX not in filename: + self._photos.append(filename) + + super(Camera, self).__init__() + + def run(self): + if self._camera is None: + return + try: + self._camera.grab_start() + while self._run: + sleep_time = CAMERA_REFRESH_INTERVAL - (time.time() - self._image_time) + if sleep_time <= 0: + ts = time.time() + #print "run.1" + self._image_lock.acquire() + self._camera.grab_one() + self._image_lock.release() + #print "run.2: " + str(time.time()-ts) + #self.save_image(image.Image(self._camera.get_image_bgr()).open().binarize().to_jpeg()) + if self._image_cache_updated is True: + self.save_image(self._image_cache.to_jpeg()) + self._image_cache_updated = False + else: + self.save_image(self._camera.get_image_jpeg()) + #print "run.3: " + str(time.time()-ts) + else: + time.sleep(sleep_time) + + if self.recording and time.time() - self.video_start_time > VIDEO_ELAPSE_MAX: + self.video_stop() + + self._camera.grab_stop() + except: + logging.error("Unexpected error:" + str(sys.exc_info()[0])) + raise + + def get_image(self, maxage = MAX_IMAGE_AGE): + if self._camera is None: + print "get_image: Default image" + return Image(cv2.imread(DEFAULT_IMAGE)) + else: + return image.Image(self._camera.get_image_bgr()) + + def save_image(self, image_jpeg): + self._streamer.set_image(image_jpeg) + self._image_time=time.time() + + def set_text(self, text): + if self._camera is None: + return + self._camera.set_overlay_text(str(text)) + + def get_next_photo_index(self): + last_photo_index = 0 + for p in self._photos: + try: + index = int(p[len(PHOTO_PREFIX):-len(self._camera.PHOTO_FILE_EXT)]) + if index > last_photo_index: + last_photo_index = index + except: + pass + return last_photo_index + 1 + + def rotate(self): + if self._camera is None: + return + rot = self._camera.camera.rotation + rot = rot + 90 + if rot > 271: + rot = 0 + self._camera.camera.rotation = rot + + def photo_take(self): + if self._camera is None: + return + photo_index = self.get_next_photo_index() + filename = PHOTO_PREFIX + str(photo_index) + self._camera.PHOTO_FILE_EXT; + filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + of = open(PHOTO_PATH + "/" + filename, "w+") + oft = open(PHOTO_PATH + "/" + filename_thumb, "w+") + im_str = self._camera.get_image_jpeg() + of.write(im_str) + # thumb + im_pil = PILImage.open(StringIO(im_str)) + im_pil.resize(PHOTO_THUMB_SIZE).save(oft) + self._photos.append(filename) + + def is_recording(self): + return self.recording + + def video_rec(self, video_name=None): + if self.is_recording(): + return + self.recording = True + if self._camera is None: + return + + if video_name is None: + video_index = self.get_next_photo_index() + filename = VIDEO_PREFIX + str(video_index) + self._camera.VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + str(video_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + else: + filename = VIDEO_PREFIX + video_name + self._camera.VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + video_name + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + try: + os.remove(PHOTO_PATH + "/" + filename) + except: + pass + + oft = open(PHOTO_PATH + "/" + filename_thumb, "w") + im_str = self._camera.get_image_jpeg() + im_pil = PILImage.open(StringIO(im_str)) + im_pil.resize(PHOTO_THUMB_SIZE).save(oft) + self._photos.append(filename) + self._camera.video_rec(PHOTO_PATH + "/" + filename) + self.video_start_time = time.time() + + def video_stop(self): + if self._camera is None: + return + if self.recording: + self._camera.video_stop() + self.recording = False + + def get_photo_list(self): + return self._photos + + def get_photo_file(self, filename): + return open(PHOTO_PATH + "/" + filename) + + def get_photo_thumb_file(self, filename): + return open(PHOTO_PATH + "/" + filename[:-len(PHOTO_FILE_EXT)] + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT) + + def delete_photo(self, filename): + logging.info("delete photo: " + filename) + os.remove(PHOTO_PATH + "/" + filename) + if self._camera is not None: + os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT) + self._photos.remove(filename) + + def exit(self): + self._run = False + self.join() + + def calibrate(self): + if self._camera is None: + return + img = self._camera.getImage() + self._background = img.hueHistogram()[-1] + + def set_rotation(self, rotation): + if self._camera is None: + return + self._camera.rotation = rotation + + def find_line(self): + self._image_lock.acquire() + img = self.get_image(0).binarize() + slices = [0,0,0] + blobs = [0,0,0] + slices[0] = img.crop(0, 100, 160, 120) + slices[1] = img.crop(0, 80, 160, 100) + slices[2] = img.crop(0, 60, 160, 80) + coords = [-1, -1, -1] + for idx, slice in enumerate(slices): + blobs[idx] = slice.find_blobs(minsize=30, maxsize=160) + if len(blobs[idx]): + coords[idx] = (blobs[idx][0].center[0] * 100) / 160 + logging.info("line coord: " + str(idx) + " " + str(coords[idx])+ " area: " + str(blobs[idx][0].area())) + + self._image_lock.release() + return coords[0] + + def find_signal(self): + #print "signal" + angle = None + ts = time.time() + self._image_lock.acquire() + img = self.get_image(0) + signals = img.find_template(self._img_template) + + logging.info("signal: " + str(time.time() - ts)) + if len(signals): + angle = signals[0].angle + + self._image_lock.release() + + return angle + + def find_face(self): + faceX = faceY = faceS = None + self._image_lock.acquire() + img = self.get_image(0) + ts = time.time() + faces = img.grayscale().find_faces() + print "face.detect: " + str(time.time() - ts) + self._image_lock.release() + print faces + if len(faces): + # Get the largest face, face is a rectangle + x, y, w, h = faces[0] + centerX = x + (w/2) + faceX = ((centerX * 100) / 160) - 50 #center = 0 + centerY = y + (h/2) + faceY = 50 - (centerY * 100) / 120 #center = 0 + size = h + faceS = (size * 100) / 120 + return [faceX, faceY, faceS] + + def path_ahead(self): + #print "path ahead" + ts = time.time() + self._image_lock.acquire() + img = self.get_image(0) + #print "path_ahead.get_image: " + str(time.time() - ts) + #img.crop(0, 100, 160, 120) + + #control_color = control.meanColor() + #color_distance = cropped.dilate().color_distance(control_color) + + #control_hue = control.getNumpy().mean() + #hue_distance = cropped.dilate().hueDistance(control_hue) + + #print "path_ahead.crop: " + str(time.time() - ts) + #control_hue = control_hue - 20 if control_hue > 127 else control_hue + 20 + #binarized = cropped.dilate().binarize(control_hue) + #binarized = cropped.dilate().binarize().invert() + #control_hue = control_hue - 10 + #binarized = color_distance.binarize(control_hue).invert() + #print "path_ahead.binarize: " + str(time.time() - ts) + blobs = img.binarize().find_blobs(minsize=100, maxsize=8000) + #print "path_ahead.blobs: " + str(time.time() - ts) + coordY = 60 + if len(blobs): + obstacle = blob.Blob.sort_distance((80,120), blobs)[0] + #for b in blobs: + # print "blobs.bottom: " + str(b.bottom) + " area: " + str(b.area()) + + logging.info("obstacle:" + str(obstacle.bottom)) + #print "path_ahead.sortdistnace: " + str(time.time() - ts) + #dw_x = 260 + obstacle.coordinates()[0] - (obstacle.width()/2) + #dw_y = 160 + obstacle.coordinates()[1] - (obstacle.height()/2) + #img.drawRectangle(dw_x, dw_y, obstacle.width(), obstacle.height(), color=(255,0,0)) + x, y = img.transform((obstacle.center[0], obstacle.bottom)) + coordY = 60 - ((y * 48) / 100) + logging.info("coordY: " + str(coordY)) + #print obstacle.coordinates()[1]+(obstacle.height()/2) + #ar_layer.centeredRectangle(obstacle.coordinates(), (obstacle.width(), obstacle.height())) + #warped.addDrawingLayer(ar_layer) + #warped.applyLayers() + #self.save_image(warped.warp(self._unwarp_corners), expire=10) + + #img.drawText("path ahead clear for " + str(coordY) + " cm", 0, 0, fontsize=32 ) + #print "path_ahead.drawtext: " + str(time.time() - ts) + #self.save_image(img) + #print "path_ahead.save_image: " + str(time.time() - ts) + self._image_lock.release() + #print "path_ahead: " + str(time.time() - ts) + return coordY + + def find_color(self, s_color): + color = (int(s_color[1:3],16), int(s_color[3:5],16), int(s_color[5:7],16)) + code_data = None + ts = time.time() + self._image_lock.acquire() + img = self.get_image(0) + self._image_lock.release() + if img is None: + print "Image from get_image is None!" + return [0,0] + bw = img.filter_color(color) + contours, hierarchy = bw.find_contours() + for contour in contours: + img.draw_contour_bound_circle(contour) + self._image_cache = img + self._image_cache_updated = True + if self._image_cache is None: + print "Image cache is None!" + #self.save_image(bw.to_jpeg()) + #objects = bw.find_blobs(minsize=5, maxsize=100) + #logging.debug("objects: " + str(objects)) + #dist = -1 + #angle = 180 + + #if objects and len(objects): + #obj = objects[-1] + #bottom = obj.bottom + #logging.info("bottom: " + str(obj.center[0]) + " " +str(obj.bottom)) + #coords = bw.transform([(obj.center[0], obj.bottom)]) + #logging.info("coordinates: " + str(coords)) + #x = coords[0][0] + #y = coords[0][1] + #dist = math.sqrt(math.pow(12 + (68 * (120 - y) / 100),2) + (math.pow((x-80)*60/160,2))) + #angle = math.atan2(x - 80, 120 - y) * 180 / math.pi + #logging.info("object found, dist: " + str(dist) + " angle: " + str(angle)) + #self.save_image(img.to_jpeg()) + #print "object: " + str(time.time() - ts) + #return [dist, angle] + return [0,0] + + + def sleep(self, elapse): + logging.debug("sleep: " + str(elapse)) + time.sleep(elapse) + + def drawCircle(self,colour,x,y): + cv2.circle(self._camera.get_image_bgr(),(x,y),30,(0,0,255), -1) + #cv2.circle(self.get_image(0) + + + + diff --git a/data/program_findcolortest.data b/data/program_findcolortest.data new file mode 100644 index 00000000..ccf0d155 --- /dev/null +++ b/data/program_findcolortest.data @@ -0,0 +1 @@ +{"dom_code": "10DIST#ff00001.0", "code": "for count in range(10):\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().find_color('#ff0000')[0])\n get_cam().sleep(1)\n", "name": "findcolortest"} \ No newline at end of file diff --git a/data/program_no_name.data b/data/program_no_name.data new file mode 100644 index 00000000..f3c53747 --- /dev/null +++ b/data/program_no_name.data @@ -0,0 +1 @@ +{"dom_code": "WHILETRUEANGLE#ffff00", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().find_color('#ffff00')[1])\n", "name": "no_name"} \ No newline at end of file diff --git a/scripts/READMEOLD.md b/scripts/READMEOLD.md new file mode 100644 index 00000000..c624fece --- /dev/null +++ b/scripts/READMEOLD.md @@ -0,0 +1,16 @@ +Thses are all files that need to be copied on the pi outside the git directory + +|Script|Live Dir| +|coderbot|/etc/init.d/| +|hostapd|/etc/init.d/| +|interfaces_ap|/etc/network/| +|interfaces_cli|/etc/network/| +|pigpiod|/etc/init.d| +|hostapd.conf|/etc/hostapd/| +|dnsmasq.conf|/etc/| + + +Note that scripts in the init.d folder need to be enabled/disabled as +described in [Starting Coderbot](https://github.com/explosivose/coderbot_lboro/wiki/Starting-Coderbot). + + diff --git a/scripts/etc/network/interfaces_hub b/scripts/etc/network/interfaces_hub deleted file mode 100644 index 65676d54..00000000 --- a/scripts/etc/network/interfaces_hub +++ /dev/null @@ -1,13 +0,0 @@ -auto lo -iface lo inet loopback -iface eth0 inet dhcp - -allow-hotplug wlan0 -iface wlan0 inet dhcp -#iface default inet dhcp - -wpa-ssid "Lboro_Coderbot_Hub" -wpa-psk "welovepis" - -#wireless-power off - diff --git a/scripts/interfaces_ap b/scripts/interfaces_ap deleted file mode 100644 index e6970d05..00000000 --- a/scripts/interfaces_ap +++ /dev/null @@ -1,12 +0,0 @@ -auto lo -iface lo inet loopback -iface eth0 inet dhcp -#allow-hotplug wlan0 -#iface wlan0 inet manual -#wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf -#iface default inet dhcp -iface wlan0 inet static -address 10.0.0.1 -netmask 255.255.255.0 -wireless-power off - diff --git a/scripts/interfaces_cli b/scripts/interfaces_cli deleted file mode 100644 index 74e4884f..00000000 --- a/scripts/interfaces_cli +++ /dev/null @@ -1,9 +0,0 @@ -auto lo -iface lo inet loopback -iface eth0 inet dhcp -allow-hotplug wlan0 -iface wlan0 inet manual -wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf -iface wlan0 inet dhcp -iface default inet dhcp -wireless-power off diff --git a/scripts/wifi b/scripts/wifi index 81729d82..2405e2d8 100755 --- a/scripts/wifi +++ b/scripts/wifi @@ -11,7 +11,7 @@ ### END INIT INFO # Change the next 3 lines to suit where you install your script and what you want to call it -DIR=/home/pi/coderbot +DIR=/home/pi/coderbot_lboro DAEMON=$DIR/wifi.py DAEMON_NAME=wifi diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 6d2b284f..41dc1d87 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -820,3 +820,43 @@ Blockly.Python['coderbot_armLower'] = function(block) { // Generate Python code for lowering the arm return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_LOWERED + ')\n'; }; + + +Blockly.Blocks['coderbot_printCircle'] = { + /** + * Block for printing a circle on the screen at a given coordinate + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("colourX") + .setCheck("Number") + .appendField("Print circle of colour") + .appendField(new Blockly.FieldColour("#ff0000"), "COLOUR_PARAM") + .appendField("at coordinates X"); + this.appendValueInput("colourY") + .setCheck("Number") + .appendField(" and Y"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(30); + this.setTooltip(''); + this.setHelpUrl('http://www.example.com/'); + } +}; + +Blockly.JavaScript['coderbot_printCircle'] = function(block) { + var colour_param = block.getFieldValue('COLOUR_PARAM'); + var value_colourx = Blockly.JavaScript.valueToCode(block, 'colourX', Blockly.JavaScript.ORDER_ATOMIC); + var value_coloury = Blockly.JavaScript.valueToCode(block, 'colourY', Blockly.JavaScript.ORDER_ATOMIC); + // TODO: Assemble JavaScript into code variable. + return 'get_cam().drawCircle(' + colour_param + ',' + value_colourx + ',' + value_coloury + ';\n'; +}; + +Blockly.Python['coderbot_printCircle'] = function(block) { + var colour_param = block.getFieldValue('COLOUR_PARAM'); + var value_colourx = Blockly.Python.valueToCode(block, 'colourX', Blockly.Python.ORDER_ATOMIC); + var value_coloury = Blockly.Python.valueToCode(block, 'colourY', Blockly.Python.ORDER_ATOMIC); + // TODO: Assemble Python into code variable. + return 'get_cam().drawCircle(' + colour_param + ',' + value_colourx + ',' + value_coloury + ';\n'; +}; diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index e0e0b53e..acec45c5 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -166,6 +166,7 @@ + diff --git a/viz/camera.py b/viz/camera.py index ffc479f5..0a261481 100644 --- a/viz/camera.py +++ b/viz/camera.py @@ -21,7 +21,7 @@ def __init__(self, props): self.camera.exposure_mode = props.get('exposure_mode') self.camera.rotation = props.get('rotation') self.out_jpeg = io.BytesIO() - self.out_rgb = picamera.array.PiRGBArray(self.camera, size=(160,120)) + self.out_rgb = picamera.array.PiRGBArray(self.camera, size=(640,480))#160,120 self.h264_encoder = None self.recording = None self.video_filename = None @@ -60,7 +60,7 @@ def grab(self): camera_port_0, output_port_0 = self.camera._get_ports(True, 0) self.jpeg_encoder = self.camera._get_image_encoder(camera_port_0, output_port_0, 'jpeg', None, quality=40) camera_port_1, output_port_1 = self.camera._get_ports(True, 1) - self.rgb_encoder = self.camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', (160, 120)) + self.rgb_encoder = self.camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', (640, 480))#160,120 #print "g.1: " + str(ts - time.time()) #ts = time.time() @@ -103,7 +103,7 @@ def grab_start(self): camera_port_0, output_port_0 = self.camera._get_ports(True, 0) self.jpeg_encoder = self.camera._get_image_encoder(camera_port_0, output_port_0, 'jpeg', None, quality=40) camera_port_1, output_port_1 = self.camera._get_ports(True, 1) - self.rgb_encoder = self.camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', (160, 120)) + self.rgb_encoder = self.camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', (640, 480)) #160,120 with self.camera._encoders_lock: self.camera._encoders[0] = self.jpeg_encoder diff --git a/viz/image.py b/viz/image.py index 09855e18..331e0215 100644 --- a/viz/image.py +++ b/viz/image.py @@ -101,6 +101,10 @@ def find_blobs(self, minsize=0, maxsize=10000000): return blobs + def find_contours(self): + contours, hierarchy = cv2.findContours(self._data, cv2.cv.CV_RETR_TREE, cv2.cv.CV_CHAIN_APPROX_SIMPLE) + return contours, hierarchy + def find_template(self, img_template): # Initiate SIFT detector sift = cv2.SIFT() @@ -149,3 +153,8 @@ def to_jpeg(self): return np.array(jpeg_array).tostring() + def draw_contour_bound_circle(self, contour, color=(0,255,0)): + (x,y),radius = cv2.minEnclosingCircle(contour) + center = (int(x), int(y)) + radius = int(radius) + cv2.circle(self._data, center, radius, color, 2) From 7c538f8a368a775827f3b0584f217feca162a36b Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 29 Jul 2015 13:38:13 +0000 Subject: [PATCH 35/53] added draw circle block --- scripts/wifi | 2 +- templates/blocks_adv.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/wifi b/scripts/wifi index 81729d82..2405e2d8 100755 --- a/scripts/wifi +++ b/scripts/wifi @@ -11,7 +11,7 @@ ### END INIT INFO # Change the next 3 lines to suit where you install your script and what you want to call it -DIR=/home/pi/coderbot +DIR=/home/pi/coderbot_lboro DAEMON=$DIR/wifi.py DAEMON_NAME=wifi diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index d9d88b48..7b1cc3b4 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -166,7 +166,7 @@ - + From 5567be61ccf022e8d682092ea916f163be32d763 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 29 Jul 2015 17:01:55 +0000 Subject: [PATCH 36/53] completed moving viz code into camera.py --- camera.py | 178 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 142 insertions(+), 36 deletions(-) diff --git a/camera.py b/camera.py index cf9f6a6c..7ab129ac 100644 --- a/camera.py +++ b/camera.py @@ -14,6 +14,9 @@ import picamera import picamera.array import io +import cv2 +import colorsys +import numpy as np CAMERA_REFRESH_INTERVAL=0.1 MAX_IMAGE_AGE = 0.0 @@ -28,16 +31,20 @@ FFMPEG_CMD = "MP4Box" VIDEO_FILE_EXT = ".mp4" VIDEO_FILE_EXT_H264 = ".h264" +MIN_MATCH_COUNT = 10 + class Camera(Thread): _instance = None - _img_template = image.Image.load("coderdojo-logo.png") + _img_template = cv2.imread("coderdojo-logo.png") _warp_corners_1 = [(0, -120), (640, -120), (380, 480), (260, 480)] _warp_corners_2 = [(0, -60), (320, -60), (190, 240), (130, 240)] _warp_corners_4 = [(0, -30), (160, -30), (95, 120), (65, 120)] _image_cache = None _image_cache_updated = False + _face_cascade = cv2.CascadeClassifier('/usr/local/share/OpenCV/lbpcascades/lbpcascade_frontalface.xml') + stream_port = 9080 @classmethod @@ -95,10 +102,13 @@ def init_camera(self): def grab_start(self): logging.debug("grab_start") + + rgb_res = (640,480) + camera_port_0, output_port_0 = self._camera._get_ports(True, 0) self.jpeg_encoder = self._camera._get_image_encoder(camera_port_0, output_port_0, 'jpeg', None, quality=40) camera_port_1, output_port_1 = self._camera._get_ports(True, 1) - self.rgb_encoder = self._camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', res) + self.rgb_encoder = self._camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', rgb_res) with self._camera._encoders_lock: self._camera._encoders[0] = self.jpeg_encoder @@ -140,10 +150,11 @@ def run(self): #print "run.2: " + str(time.time()-ts) #self.save_image(image.Image(self._camera.get_image_bgr()).open().binarize().to_jpeg()) if self._image_cache_updated is True: - self.save_image(self._image_cache.to_jpeg()) + ret, jpeg_array = cv2.imencode('.jpeg', self._image_cache) + self.jpeg_to_stream(np.array(jpeg_array).tostring()) self._image_cache_updated = False else: - self.save_image(self.out_jpeg.getvalue()) + self.jpeg_to_stream(self.out_jpeg.getvalue()) #print "run.3: " + str(time.time()-ts) else: time.sleep(sleep_time) @@ -156,21 +167,28 @@ def run(self): logging.error("Unexpected error:" + str(sys.exc_info()[0])) raise - def get_image(self, maxage = MAX_IMAGE_AGE): + def get_image_array(self, maxage = MAX_IMAGE_AGE): if self._camera is None: print "get_image: Default image" - return Image(cv2.imread(DEFAULT_IMAGE)) + return cv2.imread(DEFAULT_IMAGE) else: - return image.Image(self.out_rgb.array) + return self.out_rgb.array + + def get_image_binarized(self, maxage = MAX_IMAGE_AGE): + data = get_image_array(maxage) + data = cv2.cvtColor(data, cv2.cv.CV_BGR2GRAY) + data = cv2.adaptiveThreshold(data, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 5, 3) + return data + - def save_image(self, image_jpeg): + def jpeg_to_stream(self, image_jpeg): self._streamer.set_image(image_jpeg) self._image_time=time.time() def set_text(self, text): if self._camera is None: return - self._camera.annotate_text(str(text)) + self._camera.annotate_text = str(text) def get_next_photo_index(self): last_photo_index = 0 @@ -218,29 +236,53 @@ def video_rec(self, video_name=None): self.recording = True if video_name is None: video_index = self.get_next_photo_index() - filename = VIDEO_PREFIX + str(video_index) + self._camera.VIDEO_FILE_EXT; - filename_thumb = VIDEO_PREFIX + str(video_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + filename = VIDEO_PREFIX + str(video_index) + VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + str(video_index) + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; else: - filename = VIDEO_PREFIX + video_name + self._camera.VIDEO_FILE_EXT; - filename_thumb = VIDEO_PREFIX + video_name + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + filename = VIDEO_PREFIX + video_name + VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + video_name + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; try: os.remove(PHOTO_PATH + "/" + filename) except: pass oft = open(PHOTO_PATH + "/" + filename_thumb, "w") - im_str = self._camera.get_image_jpeg() + im_str = self.out_jpeg.getvalue() im_pil = PILImage.open(StringIO(im_str)) im_pil.resize(PHOTO_THUMB_SIZE).save(oft) self._photos.append(filename) - self._camera.video_rec(PHOTO_PATH + "/" + filename) + + filename = PHOTO_PATH + "/" + filename + self.video_filename = filename[:filename.rfind(".")] + + camera_port_2, output_port_2 = self._camera._get_ports(True, 2) + self.h264_encoder = self._camera._get_video_encoder(camera_port_2, output_port_2, 'h264', None) + + with self._camera._encoders_lock: + self._camera._encoders[2] = self.h264_encoder + logging.debug( self.video_filename + VIDEO_FILE_EXT_H264 ) + + self.h264_encoder.start(self.video_filename + VIDEO_FILE_EXT_H264) + + #self._camera.video_rec(PHOTO_PATH + "/" + filename) self.video_start_time = time.time() def video_stop(self): if self._camera is None: return if self.recording: - self._camera.video_stop() + logging.debug("video_stop") + self.h264_encoder.stop() + + with self.camera._encoders_lock: + del self.camera._encoders[2] + + self.h264_encoder.close() + # pack in mp4 container + params = " -fps 12 -add " + self.video_filename + VIDEO_FILE_EXT_H264 + " " + self.video_filename + VIDEO_FILE_EXT + os.system(self.FFMPEG_CMD + params) + # remove h264 file + os.remove(self.video_filename + VIDEO_FILE_EXT_H264) self.recording = False def get_photo_list(self): @@ -256,7 +298,7 @@ def delete_photo(self, filename): logging.info("delete photo: " + filename) os.remove(PHOTO_PATH + "/" + filename) if self._camera is not None: - os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT) + os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT) self._photos.remove(filename) def exit(self): @@ -266,7 +308,7 @@ def exit(self): def calibrate(self): if self._camera is None: return - img = self._camera.getImage() + img = self._camera.getImage() # ?? self._background = img.hueHistogram()[-1] def set_rotation(self, rotation): @@ -276,7 +318,7 @@ def set_rotation(self, rotation): def find_line(self): self._image_lock.acquire() - img = self.get_image(0).binarize() + img = self.get_image_binarized(0) slices = [0,0,0] blobs = [0,0,0] slices[0] = img.crop(0, 100, 160, 120) @@ -292,13 +334,48 @@ def find_line(self): self._image_lock.release() return coords[0] + def find_template(self, img, template): + # initiate sift detector + sift = cv2.SIFT() + # find the keypoints and descriptors with SIFT + kp1, des1 = sift.detectAndCompute(template, None) + kp2, des2 = sift.detectAndCompute(img, None) + FLANN_INDEX_KDTREE = 0 + index_params = dict(algorithm = FLAN_INDEX_KDTREE, trees = 5) + search_params = dict(checks = 50) + flann = cv2.FlannBasedMatcher(index_params, search_params) + matches = flan.knnMatch(des1, des2, k=2) + # store all the good matches as per Lowe's ratio test + good = [] + templates = [] + for m,n in matches: + if m.distance < 0.7 * n.distance: + good.append(m) + + if len(good) > MIN_MATCH_COUNT: + src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2) + dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2) + + M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) + matchesMask = mask.ravel().tolist() + + h,w = template.shape + pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2) + dst = cv2.perspectiveTransform(pts,M) + logging.info("found template: " + dst) + templates[0] = dst + else: + logging.info("Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)) + matchesMask = None + return templates + def find_signal(self): #print "signal" angle = None ts = time.time() self._image_lock.acquire() - img = self.get_image(0) - signals = img.find_template(self._img_template) + img = self.get_image_array(0) + signals = self.find_template(img, self._img_template) logging.info("signal: " + str(time.time() - ts)) if len(signals): @@ -311,9 +388,10 @@ def find_signal(self): def find_face(self): faceX = faceY = faceS = None self._image_lock.acquire() - img = self.get_image(0) + img = self.get_image_array(0) ts = time.time() - faces = img.grayscale().find_faces() + img = cv2.cvtColor(img, cv2.cv.CV_BGR2GRAY) + faces = self._face_cascade.detectMultiScale(img) print "face.detect: " + str(time.time() - ts) self._image_lock.release() print faces @@ -321,6 +399,7 @@ def find_face(self): # Get the largest face, face is a rectangle x, y, w, h = faces[0] centerX = x + (w/2) + # ASSUMING RGB 160X120 RESOLUTION! faceX = ((centerX * 100) / 160) - 50 #center = 0 centerY = y + (h/2) faceY = 50 - (centerY * 100) / 120 #center = 0 @@ -328,11 +407,26 @@ def find_face(self): faceS = (size * 100) / 120 return [faceX, faceY, faceS] + def find_blobs(self, img, minsize=0, maxsize=1000000): + blobs = [] + contours, hierarchy = cv2.findContours(img, cv2.cv.CV_RETR_TREE, cv2.cv.CV_CHAIN_APPROX_SIMPLE) + for c in contours: + area = cv2.contourArea(c) + if area > minsize and area < maxsize: + if len(blobs) and area > blobs[0].area: + blobs.insert(0, blob.Blob(c)) + else: + blobs.append(blob.Blob(c)) + + return blobs + def path_ahead(self): #print "path ahead" ts = time.time() self._image_lock.acquire() - img = self.get_image(0) + img = self.get_image_array(0) + img_binarized = self.get_image_binarized(0) + blobs = self.find_blobs(img=img_binarized, minsize=100, maxsize=8000) #print "path_ahead.get_image: " + str(time.time() - ts) #img.crop(0, 100, 160, 120) @@ -349,7 +443,7 @@ def path_ahead(self): #control_hue = control_hue - 10 #binarized = color_distance.binarize(control_hue).invert() #print "path_ahead.binarize: " + str(time.time() - ts) - blobs = img.binarize().find_blobs(minsize=100, maxsize=8000) + #blobs = img.binarize().find_blobs(minsize=100, maxsize=8000) #print "path_ahead.blobs: " + str(time.time() - ts) coordY = 60 if len(blobs): @@ -362,8 +456,11 @@ def path_ahead(self): #dw_x = 260 + obstacle.coordinates()[0] - (obstacle.width()/2) #dw_y = 160 + obstacle.coordinates()[1] - (obstacle.height()/2) #img.drawRectangle(dw_x, dw_y, obstacle.width(), obstacle.height(), color=(255,0,0)) - x, y = img.transform((obstacle.center[0], obstacle.bottom)) - coordY = 60 - ((y * 48) / 100) + + # TRANSFORM ASSUMES RGB 160X120 RESOLUTION! + #x, y = img.transform((obstacle.center[0], obstacle.bottom)) + #coordY = 60 - ((y * 48) / 100) + coordY = 60 - ((obstacle.center[1] * 48) / 100) logging.info("coordY: " + str(coordY)) #print obstacle.coordinates()[1]+(obstacle.height()/2) #ar_layer.centeredRectangle(obstacle.coordinates(), (obstacle.width(), obstacle.height())) @@ -384,19 +481,28 @@ def find_color(self, s_color): code_data = None ts = time.time() self._image_lock.acquire() - img = self.get_image(0) + img = self.get_image_array(0) self._image_lock.release() - if img is None: - print "Image from get_image is None!" - return [0,0] - bw = img.filter_color(color) - contours, hierarchy = bw.find_contours() + #bw = img.filter_color(color) + h, s, v = colorsys.rgb_to_hsv(color[0]/255.0, color[1]/255.0, color[2]/255.0) + img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + h = h * 180 + s = s * 255 + v = v * 255 + logging.debug("color_hsv: " + str(h) + " " + str(s) + " " + str(v)) + lower_color = np.array([h-10, 50, 50]) + upper_color = np.array([h+10, 255, 255]) + logging.debug("lower: " + str(lower_color) + " upper: " + str(upper_color)) + bw = cv2.inRange(img_hsv, lower_color, upper_color) + #contours, hierarchy = bw.find_contours() + contours, hierarchy = cv2.findContours(bw, cv2.cv.CV_RETR_TREE, cv2.cv.CV_CHAIN_APPROX_SIMPLE) for contour in contours: - img.draw_contour_bound_circle(contour) + (x,y),radius = cv2.minEnclosingCircle(contour) + center = (int(x), int(y)) + radius = int(radius) + cv2.circle(img, center, radius, (0,255,0), 2) self._image_cache = img self._image_cache_updated = True - if self._image_cache is None: - print "Image cache is None!" #self.save_image(bw.to_jpeg()) #objects = bw.find_blobs(minsize=5, maxsize=100) #logging.debug("objects: " + str(objects)) From 3b110f4d328630b866204ab4b84758b02b69573c Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Thu, 30 Jul 2015 13:30:54 +0000 Subject: [PATCH 37/53] updated find_color block --- camera.py | 22 ++++++++++++++-------- static/js/blockly/blocks.js | 6 +++--- static/js/blockly/bot_en.js | 7 ++++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/camera.py b/camera.py index 7ab129ac..d028e9ea 100644 --- a/camera.py +++ b/camera.py @@ -18,7 +18,7 @@ import colorsys import numpy as np -CAMERA_REFRESH_INTERVAL=0.1 +CAMERA_REFRESH_INTERVAL=0.01 MAX_IMAGE_AGE = 0.0 PHOTO_PATH = "./photos" DEFAULT_IMAGE = "./photos/broken.jpg" @@ -494,15 +494,21 @@ def find_color(self, s_color): upper_color = np.array([h+10, 255, 255]) logging.debug("lower: " + str(lower_color) + " upper: " + str(upper_color)) bw = cv2.inRange(img_hsv, lower_color, upper_color) - #contours, hierarchy = bw.find_contours() - contours, hierarchy = cv2.findContours(bw, cv2.cv.CV_RETR_TREE, cv2.cv.CV_CHAIN_APPROX_SIMPLE) - for contour in contours: - (x,y),radius = cv2.minEnclosingCircle(contour) + # get unordered list of contours in filtered image + contours, _ = cv2.findContours(bw, cv2.cv.CV_RETR_LIST, cv2.cv.CV_CHAIN_APPROX_SIMPLE) + center = (0,0) + radius = 0 + if not contours is None and len(contours) > 0: + # get the contour with the largest area + largest = sorted(contours, key = cv2.contourArea, reverse=True)[0] + (x,y),radius = cv2.minEnclosingCircle(largest) center = (int(x), int(y)) radius = int(radius) + # draw a circle around the largest cv2.circle(img, center, radius, (0,255,0), 2) - self._image_cache = img - self._image_cache_updated = True + # copy to image cache for jpegging and streaming [see run()] + self._image_cache = img + self._image_cache_updated = True #self.save_image(bw.to_jpeg()) #objects = bw.find_blobs(minsize=5, maxsize=100) #logging.debug("objects: " + str(objects)) @@ -523,7 +529,7 @@ def find_color(self, s_color): #self.save_image(img.to_jpeg()) #print "object: " + str(time.time() - ts) #return [dist, angle] - return [0,0] + return [center[0],center[1],radius*2] def sleep(self, elapse): diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 41dc1d87..93fe85c8 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -706,7 +706,7 @@ Blockly.Blocks['coderbot_adv_findColor'] = { this.setColour(290); this.appendDummyInput() .appendField(Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_FIND) - .appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_DIST, 'DIST'], [Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_ANGLE, 'ANGLE'],[Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_BOTH,'BOTH']]), 'RETVAL') + .appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_X, 'X'], [Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_Y, 'Y'],[Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_DIAMETER,'DIAMETER'],[Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_ALL,'ALL']]), 'RETVAL') .appendField(Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_COLOR); this.appendValueInput('COLOR') .setCheck('Colour'); @@ -720,7 +720,7 @@ Blockly.JavaScript['coderbot_adv_findColor'] = function(block) { // Boolean values true and false. var color = Blockly.Python.valueToCode(block, 'COLOR', Blockly.Python.ORDER_NONE); var retval = block.getFieldValue('RETVAL'); - var ret_code = {'DIST': '[0]', 'ANGLE': '[1]', 'BOTH': ''}[retval]; + var ret_code = {'X': '[0]', 'Y': '[1]', 'DIAMETER': '[2]', 'ALL': ''}[retval]; var code = 'get_cam().find_color(' + color + ')' + ret_code + ';'; return [code, Blockly.Python.ORDER_ATOMIC]; }; @@ -729,7 +729,7 @@ Blockly.Python['coderbot_adv_findColor'] = function(block) { // Boolean values true and false. var color = Blockly.Python.valueToCode(block, 'COLOR', Blockly.Python.ORDER_NONE); var retval = block.getFieldValue('RETVAL'); - var ret_code = {'DIST': '[0]', 'ANGLE': '[1]', 'BOTH': ''}[retval]; + var ret_code = {'X': '[0]', 'Y': '[1]', 'DIAMETER': '[2]', 'ALL': ''}[retval]; var code = 'get_cam().find_color(' + color + ')' + ret_code; return [code, Blockly.Python.ORDER_ATOMIC]; }; diff --git a/static/js/blockly/bot_en.js b/static/js/blockly/bot_en.js index 1fb0f1a7..b2c4d6a4 100644 --- a/static/js/blockly/bot_en.js +++ b/static/js/blockly/bot_en.js @@ -40,9 +40,10 @@ Blockly.Msg.CODERBOT_SENSOR_FINDSIGNAL = "find signal"; Blockly.Msg.CODERBOT_SENSOR_FINDCODE = "find code"; Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_FIND = "find"; Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_COLOR = "from color"; -Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_DIST = "distance"; -Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_ANGLE = "angle"; -Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_BOTH = "both"; +Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_X = "x coord"; +Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_Y = "ycoord"; +Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_DIAMETER = "diameter"; +Blockly.Msg.CODERBOT_SENSOR_FINDCOLOR_ALL = "x, y, diameter (as list)"; Blockly.Msg.CODERBOT_SENSOR_FINDFACE_X = "x coord"; Blockly.Msg.CODERBOT_SENSOR_FINDFACE_Y = "y coord"; Blockly.Msg.CODERBOT_SENSOR_FINDFACE_SIZE = "size"; From 56f3b7fc860fbaf28d8076a012448c7cec44f099 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Thu, 30 Jul 2015 13:31:18 +0000 Subject: [PATCH 38/53] camera annotation text is reset when program ends --- program.py | 1 + 1 file changed, 1 insertion(+) diff --git a/program.py b/program.py index 80d507de..cdc336f1 100644 --- a/program.py +++ b/program.py @@ -129,6 +129,7 @@ def run(self): logging.info("quit: " + str(re)) finally: get_cam().video_stop() #if video is running, stop it + get_cam().set_text(' ') get_motion().stop() self._running = False From e1c42634ccb1d9880ba264b9adf08a617fc13ea0 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Thu, 30 Jul 2015 13:32:35 +0000 Subject: [PATCH 39/53] new saved find_color blockly program data2 --- data/program_find_color_test.data | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/program_find_color_test.data diff --git a/data/program_find_color_test.data b/data/program_find_color_test.data new file mode 100644 index 00000000..126bc234 --- /dev/null +++ b/data/program_find_color_test.data @@ -0,0 +1 @@ +{"dom_code": "100X#ff66000.1", "code": "for count in range(100):\n get_prog_eng().check_end()\n get_cam().set_text(get_cam().find_color('#ff6600')[0])\n get_cam().sleep(0.1)\n", "name": "find_color_test"} \ No newline at end of file From a1ef81a7e83a6028ddae3895da83ec186748543f Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Mon, 3 Aug 2015 10:17:44 +0000 Subject: [PATCH 40/53] added left and right servo offset --- coderbot.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/coderbot.py b/coderbot.py index 4280f09a..a0e5b55b 100644 --- a/coderbot.py +++ b/coderbot.py @@ -11,8 +11,11 @@ PIN_SERVO_3 = 9 # ARM PIN PIN_SERVO_4 = 10 LED_RED = 21 -LED_YELLOW = 20 -LED_GREEN = 16 +LED_YELLOW = 16 +LED_GREEN = 20 + +LEFT_OFFSET = 0 +RIGHT_OFFSET = 0 PWM_FREQUENCY = 50 #Hz PWM_RANGE = 2000 #100-200 duty cycle operating range for servos @@ -51,10 +54,10 @@ def get_instance(cls, servo=False): return cls.the_bot def move(self, speed=100, elapse=-1): - self.motor_control(speed_left=speed, speed_right=speed, elapse=elapse) + self.motor_control(speed_left=speed + LEFT_OFFSET, speed_right=speed + RIGHT_OFFSET, elapse=elapse) def turn(self, speed=100, elapse=-1): - self.motor_control(speed_left=speed, speed_right=-speed, elapse=elapse) + self.motor_control(speed_left=speed + LEFT_OFFSET, speed_right=-speed + RIGHT_OFFSET, elapse=elapse) def forward(self, speed=100, elapse=-1): self.move(speed=speed, elapse=elapse) From cfcca28623376ff57d17ab0d6604aed187940055 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Mon, 3 Aug 2015 13:00:54 +0000 Subject: [PATCH 41/53] fixed LED pin numbers and fixed coderdot deamon script not starting --- coderbot.py | 18 +++++++++--------- scripts/etc/init.d/coderbot | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/coderbot.py b/coderbot.py index a0e5b55b..ca15c838 100644 --- a/coderbot.py +++ b/coderbot.py @@ -182,29 +182,29 @@ def reboot(self): def LED_on(self, Colour, seconds, pin=0): if Colour == "Red LED": - pin = 21 + pin = LED_RED elif Colour == "Green LED": - pin = 16 + pin = LED_GREEN elif Colour == "Yellow LED": - pin = 20 + pin = LED_YELLOW self.pi.write(pin, 1) time.sleep(seconds) self.pi.write(pin, 0) def LED_on_indef(self, Colour, pin=0): if Colour == "Red LED": - pin = 21 + pin = LED_RED elif Colour == "Green LED": - pin = 16 + pin = LED_GREEN elif Colour == "Yellow LED": - pin = 20 + pin = LED_YELLOW self.pi.write(pin, 1) def LED_off_indef(self, Colour, pin=0): if Colour == "Red LED": - pin = 21 + pin = LED_RED elif Colour == "Green LED": - pin = 16 + pin = LED_GREEN elif Colour == "Yellow LED": - pin = 20 + pin = LED_YELLOW self.pi.write(pin, 0) diff --git a/scripts/etc/init.d/coderbot b/scripts/etc/init.d/coderbot index a8e7f0a0..a0209fca 100755 --- a/scripts/etc/init.d/coderbot +++ b/scripts/etc/init.d/coderbot @@ -18,7 +18,7 @@ DAEMON_NAME=coderbot # Put your command-line options here DAEMON_OPTS="" -DAEMON_USER=pi +DAEMON_USER=root # The process ID of the script when it runs is stored here PIDFILE=/var/run/$DAEMON_NAME.pid From 06f59ade10f41c4ad36dfa3a10cd857d23c1151e Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 5 Aug 2015 15:30:03 +0000 Subject: [PATCH 42/53] Changed coderbot.py to work with pulsewidth values --- coderbot.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/coderbot.py b/coderbot.py index a0e5b55b..da45e3aa 100644 --- a/coderbot.py +++ b/coderbot.py @@ -14,11 +14,10 @@ LED_YELLOW = 16 LED_GREEN = 20 -LEFT_OFFSET = 0 -RIGHT_OFFSET = 0 - PWM_FREQUENCY = 50 #Hz -PWM_RANGE = 2000 #100-200 duty cycle operating range for servos +PWM_UPPER = 1700 # 1.7ms for full clockwise +PWM_LOWER = 1300 # 1.3ms for full anti-clockwise + def coderbot_callback(gpio, level, tick): return CoderBot.get_instance().callback(gpio, level, tick) @@ -40,7 +39,6 @@ def __init__(self, servo=False): cb1 = self.pi.callback(PIN_PUSHBUTTON, pigpio.EITHER_EDGE, coderbot_callback) for pin in self._pin_out: self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) - self.pi.set_PWM_range(pin, PWM_RANGE) self.stop() self._is_moving = False @@ -54,10 +52,10 @@ def get_instance(cls, servo=False): return cls.the_bot def move(self, speed=100, elapse=-1): - self.motor_control(speed_left=speed + LEFT_OFFSET, speed_right=speed + RIGHT_OFFSET, elapse=elapse) + self.motor_control(speed_left=speed, speed_right=speed, elapse=elapse) def turn(self, speed=100, elapse=-1): - self.motor_control(speed_left=speed + LEFT_OFFSET, speed_right=-speed + RIGHT_OFFSET, elapse=elapse) + self.motor_control(speed_left=speed, speed_right=-speed, elapse=elapse) def forward(self, speed=100, elapse=-1): self.move(speed=speed, elapse=elapse) @@ -119,14 +117,13 @@ def _servo_motor(self, speed_left=100, speed_right=100, elapse=-1): def _servo_motor_control(self, pin, speed): self._is_moving = True - # transform speed value from -100 to +100 range - # to servo duty cycle range: 100 to 200 - speed = 150 + speed/2 - if speed < 90: speed = 90 - if speed > 210: speed = 210 - self.pi.set_PWM_range(pin, PWM_RANGE) - self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) - self.pi.set_PWM_dutycycle(pin, speed) + #clamp between -100 and 100 + speed = self.clamp(speed,-100,100) + + #calculate pwn value between the range of UPPER_PWM and LOWER_PWM + half_range = ((PWM_UPPER - PWM_LOWER) / 2) + pwm_out = PWM_LOWER + half_range + ((speed / 100.0) * half_range) + self.pi.set_servo_pulsewidth(pin, pwm_out) def _servo_control(self, pin, angle): @@ -144,6 +141,12 @@ def stop(self): self.pi.write(pin, 0) self._is_moving = False + def clamp(self,x,lower,upper): + if x < lower: + x = lower + if x > upper: + x = upper + return x def is_moving(self): return self._is_moving From b59bff11f85af8b10801b8b19a394dcc97129a7e Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 11 Aug 2015 11:12:19 +0000 Subject: [PATCH 43/53] Changed control button to move arm up and down --- main.py | 4 ++++ static/js/coderbot.js | 8 ++++---- static/js/control.js | 4 ++-- templates/control.html | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index e878371a..d569d990 100644 --- a/main.py +++ b/main.py @@ -109,6 +109,10 @@ def handle_bot(): elif cmd == "reboot": logging.info("rebooting") bot.reboot() + elif cmd == "armup": + bot.arm_up() + elif cmd == "armdown": + bot.arm_down() return "ok" diff --git a/static/js/coderbot.js b/static/js/coderbot.js index a40eba9c..d943c111 100644 --- a/static/js/coderbot.js +++ b/static/js/coderbot.js @@ -59,12 +59,12 @@ CoderBot.prototype.takePhoto = function() { this.command('take_photo', 0); } -CoderBot.prototype.videoRec = function() { - this.command('video_rec', 0); +CoderBot.prototype.armDown = function() { + this.command('armdown', 0); } -CoderBot.prototype.videoStop = function() { - this.command('video_stop', 0); +CoderBot.prototype.armUp = function() { + this.command('armup', 0); } CoderBot.prototype.say = function(h) { diff --git a/static/js/control.js b/static/js/control.js index e3db6eb6..0c7b645c 100644 --- a/static/js/control.js +++ b/static/js/control.js @@ -82,10 +82,10 @@ $(document).on( "pagecreate", '#page-control', function( event ) { bot.rotateCamera(); }); $('#b_video_rec').on("click", function (){ - bot.videoRec(); + bot.armDown(); }); $('#b_video_stop').on("click", function (){ - bot.videoStop(); + bot.armUp(); }); $('#b_photos').on("click", function (){ $.mobile.pageContainer.pagecontainer('change', '#page-photos'); diff --git a/templates/control.html b/templates/control.html index 35870816..9784be53 100644 --- a/templates/control.html +++ b/templates/control.html @@ -45,8 +45,8 @@

CoderBot

- - + +
From 633756f5e8b2b86ad316add69179bad2bc09e7c6 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 11 Aug 2015 11:21:38 +0000 Subject: [PATCH 44/53] added arm up and down functions --- coderbot.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/coderbot.py b/coderbot.py index 148da7fc..acf82ae8 100644 --- a/coderbot.py +++ b/coderbot.py @@ -70,7 +70,17 @@ def right(self, speed=100, elapse=-1): self.turn(speed=speed, elapse=elapse) def servo3(self, angle): - self._servo_control(PIN_SERVO_3, angle) + #self._servo_control(PIN_SERVO_3, angle) + angle = self.clamp(angle,0,100) + pwm = PWM_LOWER + ((angle/100)*(PWM_UPPER - PWM_LOWER)) + self.pi.set_servo_pulsewidth(PIN_SERVO_3,pwm) + + def arm_up(self): + self.servo3(120) + + def arm_down(self): + self.servo3(60) + def servo4(self, angle): self._servo_control(PIN_SERVO_4, angle) From 770529d57e14a25b4cb8f50c5447fe68282646a4 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 12 Aug 2015 09:23:27 +0000 Subject: [PATCH 45/53] Fixed servo pen arm inconsistency also remaned pen buttons --- coderbot.py | 7 ++++--- templates/control.html | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/coderbot.py b/coderbot.py index acf82ae8..8b829f72 100644 --- a/coderbot.py +++ b/coderbot.py @@ -72,14 +72,14 @@ def right(self, speed=100, elapse=-1): def servo3(self, angle): #self._servo_control(PIN_SERVO_3, angle) angle = self.clamp(angle,0,100) - pwm = PWM_LOWER + ((angle/100)*(PWM_UPPER - PWM_LOWER)) + pwm = PWM_LOWER + ((angle/100.0)*(PWM_UPPER - PWM_LOWER)) self.pi.set_servo_pulsewidth(PIN_SERVO_3,pwm) def arm_up(self): self.servo3(120) def arm_down(self): - self.servo3(60) + self.servo3(0) def servo4(self, angle): @@ -148,7 +148,8 @@ def _servo_control(self, pin, angle): def stop(self): for pin in self._pin_out: - self.pi.write(pin, 0) + if pin != PIN_SERVO_3 and pin != PIN_SERVO_4: + self.pi.write(pin, 0) self._is_moving = False def clamp(self,x,lower,upper): diff --git a/templates/control.html b/templates/control.html index 9784be53..59596b68 100644 --- a/templates/control.html +++ b/templates/control.html @@ -45,8 +45,8 @@

CoderBot

- - + +
From 4995b43f9820a389a2f51ce4ac58156754f91b1d Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 12 Aug 2015 13:38:51 +0000 Subject: [PATCH 46/53] Added program levels for PIcasso and PIoneer --- camera.py | 329 ++++++++--------------------------- templates/blocks_Picasso.xml | 157 +++++++++++++++++ templates/blocks_Pioneer.xml | 256 +++++++++++++++++++++++++++ templates/config.html | 6 +- 4 files changed, 488 insertions(+), 260 deletions(-) create mode 100644 templates/blocks_Picasso.xml create mode 100644 templates/blocks_Pioneer.xml diff --git a/camera.py b/camera.py index d028e9ea..6b0df8a8 100644 --- a/camera.py +++ b/camera.py @@ -11,14 +11,7 @@ from viz import camera, streamer, image, blob import config -import picamera -import picamera.array -import io -import cv2 -import colorsys -import numpy as np - -CAMERA_REFRESH_INTERVAL=0.01 +CAMERA_REFRESH_INTERVAL=0.1 MAX_IMAGE_AGE = 0.0 PHOTO_PATH = "./photos" DEFAULT_IMAGE = "./photos/broken.jpg" @@ -27,24 +20,14 @@ PHOTO_THUMB_SUFFIX = "_thumb" PHOTO_THUMB_SIZE = (240,180) VIDEO_ELAPSE_MAX = 900 -PHOTO_FILE_EXT = ".jpg" -FFMPEG_CMD = "MP4Box" -VIDEO_FILE_EXT = ".mp4" -VIDEO_FILE_EXT_H264 = ".h264" -MIN_MATCH_COUNT = 10 - class Camera(Thread): _instance = None - _img_template = cv2.imread("coderdojo-logo.png") + _img_template = image.Image.load("coderdojo-logo.png") _warp_corners_1 = [(0, -120), (640, -120), (380, 480), (260, 480)] _warp_corners_2 = [(0, -60), (320, -60), (190, 240), (130, 240)] _warp_corners_4 = [(0, -30), (160, -30), (95, 120), (65, 120)] - _image_cache = None - _image_cache_updated = False - _face_cascade = cv2.CascadeClassifier('/usr/local/share/OpenCV/lbpcascades/lbpcascade_frontalface.xml') - stream_port = 9080 @classmethod @@ -56,7 +39,19 @@ def get_instance(cls): def __init__(self): logging.info("starting camera") - self.init_camera() + cam_props = { + "width":640, + "height":480, + "exposure_mode": config.Config.get().get("camera_exposure_mode"), + "rotation": config.Config.get().get("camera_rotation") + } + #try initialising the camera + try: + self._camera = camera.Camera(props=cam_props) + except: + self._camera = None + logging.error("Unexpected error:" + str(sys.exc_info()[0])) + pass self._streamer = streamer.JpegStreamer("0.0.0.0:"+str(self.stream_port), st=0.1) #self._cam_off_img.save(self._streamer) @@ -75,86 +70,22 @@ def __init__(self): super(Camera, self).__init__() - def init_camera(self): - try: - props = { - "width":640, - "height":480, - "exposure_mode":config.Config.get().get("camera_exposure_mode"), - "rotation":config.Config.get().get("camera_rotation") - } - self._camera = picamera.PiCamera() - cam = self._camera - res = (props.get("width",640), props.get("height",480)) - cam.resolution = res - cam.framerate = 30 - cam.exposure_mode = props.get("exposure_mode") - cam.rotation = props.get("rotation") - self.out_jpeg = io.BytesIO() - self.out_rgb = picamera.array.PiRGBArray(cam, size=res) - self.h264_encoder = None - self.recording = None - self.video_filename = None - except: - self._camera = None - logging.error("Could not initialise camera:" + str(sys.exc_info()[0])) - pass - - def grab_start(self): - logging.debug("grab_start") - - rgb_res = (640,480) - - camera_port_0, output_port_0 = self._camera._get_ports(True, 0) - self.jpeg_encoder = self._camera._get_image_encoder(camera_port_0, output_port_0, 'jpeg', None, quality=40) - camera_port_1, output_port_1 = self._camera._get_ports(True, 1) - self.rgb_encoder = self._camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', rgb_res) - - with self._camera._encoders_lock: - self._camera._encoders[0] = self.jpeg_encoder - self._camera._encoders[1] = self.rgb_encoder - - def grab_one(self): - self.out_jpeg.seek(0) - self.out_rgb.seek(0) - self.jpeg_encoder.start(self.out_jpeg) - self.rgb_encoder.start(self.out_rgb) - if not self.jpeg_encoder.wait(10): - raise picamera.PiCameraError('Timed Out') - if not self.rgb_encoder.wait(10): - raise picamera.PiCameraError('Timed Out') - - def grab_stop(self): - logging.debug("grab_stop") - - with self._camera._encoders_lock: - del self._camera._encoders[0] - del self._camera._encoders[1] - - self.jpeg_encoder.close() - self.rgb_encoder.close() - def run(self): if self._camera is None: return try: - self.grab_start() + self._camera.grab_start() while self._run: sleep_time = CAMERA_REFRESH_INTERVAL - (time.time() - self._image_time) if sleep_time <= 0: ts = time.time() #print "run.1" self._image_lock.acquire() - self.grab_one() + self._camera.grab_one() self._image_lock.release() #print "run.2: " + str(time.time()-ts) #self.save_image(image.Image(self._camera.get_image_bgr()).open().binarize().to_jpeg()) - if self._image_cache_updated is True: - ret, jpeg_array = cv2.imencode('.jpeg', self._image_cache) - self.jpeg_to_stream(np.array(jpeg_array).tostring()) - self._image_cache_updated = False - else: - self.jpeg_to_stream(self.out_jpeg.getvalue()) + self.save_image(self._camera.get_image_jpeg()) #print "run.3: " + str(time.time()-ts) else: time.sleep(sleep_time) @@ -162,39 +93,31 @@ def run(self): if self.recording and time.time() - self.video_start_time > VIDEO_ELAPSE_MAX: self.video_stop() - self.grab_stop() + self._camera.grab_stop() except: logging.error("Unexpected error:" + str(sys.exc_info()[0])) raise - def get_image_array(self, maxage = MAX_IMAGE_AGE): + def get_image(self, maxage = MAX_IMAGE_AGE): if self._camera is None: - print "get_image: Default image" - return cv2.imread(DEFAULT_IMAGE) + return Image(cv2.imread(DEFAULT_IMAGE)) else: - return self.out_rgb.array + return image.Image(self._camera.get_image_bgr()) - def get_image_binarized(self, maxage = MAX_IMAGE_AGE): - data = get_image_array(maxage) - data = cv2.cvtColor(data, cv2.cv.CV_BGR2GRAY) - data = cv2.adaptiveThreshold(data, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 5, 3) - return data - - - def jpeg_to_stream(self, image_jpeg): + def save_image(self, image_jpeg): self._streamer.set_image(image_jpeg) self._image_time=time.time() def set_text(self, text): if self._camera is None: return - self._camera.annotate_text = str(text) + self._camera.set_overlay_text(str(text)) def get_next_photo_index(self): last_photo_index = 0 for p in self._photos: try: - index = int(p[len(PHOTO_PREFIX):-len(self.PHOTO_FILE_EXT)]) + index = int(p[len(PHOTO_PREFIX):-len(self._camera.PHOTO_FILE_EXT)]) if index > last_photo_index: last_photo_index = index except: @@ -204,21 +127,21 @@ def get_next_photo_index(self): def rotate(self): if self._camera is None: return - rot = self._camera.rotation + rot = self._camera.camera.rotation rot = rot + 90 if rot > 271: rot = 0 - self._camera.rotation = rot + self._camera.camera.rotation = rot def photo_take(self): if self._camera is None: return photo_index = self.get_next_photo_index() - filename = PHOTO_PREFIX + str(photo_index) + PHOTO_FILE_EXT; - filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; + filename = PHOTO_PREFIX + str(photo_index) + self._camera.PHOTO_FILE_EXT; + filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; of = open(PHOTO_PATH + "/" + filename, "w+") oft = open(PHOTO_PATH + "/" + filename_thumb, "w+") - im_str = self.out_jpeg.getvalue() + im_str = self._camera.get_image_jpeg() of.write(im_str) # thumb im_pil = PILImage.open(StringIO(im_str)) @@ -229,60 +152,37 @@ def is_recording(self): return self.recording def video_rec(self, video_name=None): - if self.recording: + if self.is_recording(): return + self.recording = True if self._camera is None: return - self.recording = True + if video_name is None: video_index = self.get_next_photo_index() - filename = VIDEO_PREFIX + str(video_index) + VIDEO_FILE_EXT; - filename_thumb = VIDEO_PREFIX + str(video_index) + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; + filename = VIDEO_PREFIX + str(video_index) + self._camera.VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + str(video_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; else: - filename = VIDEO_PREFIX + video_name + VIDEO_FILE_EXT; - filename_thumb = VIDEO_PREFIX + video_name + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; + filename = VIDEO_PREFIX + video_name + self._camera.VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + video_name + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; try: os.remove(PHOTO_PATH + "/" + filename) except: pass oft = open(PHOTO_PATH + "/" + filename_thumb, "w") - im_str = self.out_jpeg.getvalue() + im_str = self._camera.get_image_jpeg() im_pil = PILImage.open(StringIO(im_str)) im_pil.resize(PHOTO_THUMB_SIZE).save(oft) self._photos.append(filename) - - filename = PHOTO_PATH + "/" + filename - self.video_filename = filename[:filename.rfind(".")] - - camera_port_2, output_port_2 = self._camera._get_ports(True, 2) - self.h264_encoder = self._camera._get_video_encoder(camera_port_2, output_port_2, 'h264', None) - - with self._camera._encoders_lock: - self._camera._encoders[2] = self.h264_encoder - logging.debug( self.video_filename + VIDEO_FILE_EXT_H264 ) - - self.h264_encoder.start(self.video_filename + VIDEO_FILE_EXT_H264) - - #self._camera.video_rec(PHOTO_PATH + "/" + filename) + self._camera.video_rec(PHOTO_PATH + "/" + filename) self.video_start_time = time.time() def video_stop(self): if self._camera is None: return if self.recording: - logging.debug("video_stop") - self.h264_encoder.stop() - - with self.camera._encoders_lock: - del self.camera._encoders[2] - - self.h264_encoder.close() - # pack in mp4 container - params = " -fps 12 -add " + self.video_filename + VIDEO_FILE_EXT_H264 + " " + self.video_filename + VIDEO_FILE_EXT - os.system(self.FFMPEG_CMD + params) - # remove h264 file - os.remove(self.video_filename + VIDEO_FILE_EXT_H264) + self._camera.video_stop() self.recording = False def get_photo_list(self): @@ -298,7 +198,7 @@ def delete_photo(self, filename): logging.info("delete photo: " + filename) os.remove(PHOTO_PATH + "/" + filename) if self._camera is not None: - os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT) + os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT) self._photos.remove(filename) def exit(self): @@ -308,7 +208,7 @@ def exit(self): def calibrate(self): if self._camera is None: return - img = self._camera.getImage() # ?? + img = self._camera.getImage() self._background = img.hueHistogram()[-1] def set_rotation(self, rotation): @@ -318,7 +218,7 @@ def set_rotation(self, rotation): def find_line(self): self._image_lock.acquire() - img = self.get_image_binarized(0) + img = self.get_image(0).binarize() slices = [0,0,0] blobs = [0,0,0] slices[0] = img.crop(0, 100, 160, 120) @@ -334,48 +234,13 @@ def find_line(self): self._image_lock.release() return coords[0] - def find_template(self, img, template): - # initiate sift detector - sift = cv2.SIFT() - # find the keypoints and descriptors with SIFT - kp1, des1 = sift.detectAndCompute(template, None) - kp2, des2 = sift.detectAndCompute(img, None) - FLANN_INDEX_KDTREE = 0 - index_params = dict(algorithm = FLAN_INDEX_KDTREE, trees = 5) - search_params = dict(checks = 50) - flann = cv2.FlannBasedMatcher(index_params, search_params) - matches = flan.knnMatch(des1, des2, k=2) - # store all the good matches as per Lowe's ratio test - good = [] - templates = [] - for m,n in matches: - if m.distance < 0.7 * n.distance: - good.append(m) - - if len(good) > MIN_MATCH_COUNT: - src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2) - dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2) - - M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) - matchesMask = mask.ravel().tolist() - - h,w = template.shape - pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2) - dst = cv2.perspectiveTransform(pts,M) - logging.info("found template: " + dst) - templates[0] = dst - else: - logging.info("Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)) - matchesMask = None - return templates - def find_signal(self): #print "signal" angle = None ts = time.time() self._image_lock.acquire() - img = self.get_image_array(0) - signals = self.find_template(img, self._img_template) + img = self.get_image(0) + signals = img.find_template(self._img_template) logging.info("signal: " + str(time.time() - ts)) if len(signals): @@ -388,10 +253,9 @@ def find_signal(self): def find_face(self): faceX = faceY = faceS = None self._image_lock.acquire() - img = self.get_image_array(0) + img = self.get_image(0) ts = time.time() - img = cv2.cvtColor(img, cv2.cv.CV_BGR2GRAY) - faces = self._face_cascade.detectMultiScale(img) + faces = img.grayscale().find_faces() print "face.detect: " + str(time.time() - ts) self._image_lock.release() print faces @@ -399,7 +263,6 @@ def find_face(self): # Get the largest face, face is a rectangle x, y, w, h = faces[0] centerX = x + (w/2) - # ASSUMING RGB 160X120 RESOLUTION! faceX = ((centerX * 100) / 160) - 50 #center = 0 centerY = y + (h/2) faceY = 50 - (centerY * 100) / 120 #center = 0 @@ -407,26 +270,11 @@ def find_face(self): faceS = (size * 100) / 120 return [faceX, faceY, faceS] - def find_blobs(self, img, minsize=0, maxsize=1000000): - blobs = [] - contours, hierarchy = cv2.findContours(img, cv2.cv.CV_RETR_TREE, cv2.cv.CV_CHAIN_APPROX_SIMPLE) - for c in contours: - area = cv2.contourArea(c) - if area > minsize and area < maxsize: - if len(blobs) and area > blobs[0].area: - blobs.insert(0, blob.Blob(c)) - else: - blobs.append(blob.Blob(c)) - - return blobs - def path_ahead(self): #print "path ahead" ts = time.time() self._image_lock.acquire() - img = self.get_image_array(0) - img_binarized = self.get_image_binarized(0) - blobs = self.find_blobs(img=img_binarized, minsize=100, maxsize=8000) + img = self.get_image(0) #print "path_ahead.get_image: " + str(time.time() - ts) #img.crop(0, 100, 160, 120) @@ -443,7 +291,7 @@ def path_ahead(self): #control_hue = control_hue - 10 #binarized = color_distance.binarize(control_hue).invert() #print "path_ahead.binarize: " + str(time.time() - ts) - #blobs = img.binarize().find_blobs(minsize=100, maxsize=8000) + blobs = img.binarize().find_blobs(minsize=100, maxsize=8000) #print "path_ahead.blobs: " + str(time.time() - ts) coordY = 60 if len(blobs): @@ -456,11 +304,8 @@ def path_ahead(self): #dw_x = 260 + obstacle.coordinates()[0] - (obstacle.width()/2) #dw_y = 160 + obstacle.coordinates()[1] - (obstacle.height()/2) #img.drawRectangle(dw_x, dw_y, obstacle.width(), obstacle.height(), color=(255,0,0)) - - # TRANSFORM ASSUMES RGB 160X120 RESOLUTION! - #x, y = img.transform((obstacle.center[0], obstacle.bottom)) - #coordY = 60 - ((y * 48) / 100) - coordY = 60 - ((obstacle.center[1] * 48) / 100) + x, y = img.transform((obstacle.center[0], obstacle.bottom)) + coordY = 60 - ((y * 48) / 100) logging.info("coordY: " + str(coordY)) #print obstacle.coordinates()[1]+(obstacle.height()/2) #ar_layer.centeredRectangle(obstacle.coordinates(), (obstacle.width(), obstacle.height())) @@ -481,65 +326,31 @@ def find_color(self, s_color): code_data = None ts = time.time() self._image_lock.acquire() - img = self.get_image_array(0) + img = self.get_image(0) self._image_lock.release() - #bw = img.filter_color(color) - h, s, v = colorsys.rgb_to_hsv(color[0]/255.0, color[1]/255.0, color[2]/255.0) - img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) - h = h * 180 - s = s * 255 - v = v * 255 - logging.debug("color_hsv: " + str(h) + " " + str(s) + " " + str(v)) - lower_color = np.array([h-10, 50, 50]) - upper_color = np.array([h+10, 255, 255]) - logging.debug("lower: " + str(lower_color) + " upper: " + str(upper_color)) - bw = cv2.inRange(img_hsv, lower_color, upper_color) - # get unordered list of contours in filtered image - contours, _ = cv2.findContours(bw, cv2.cv.CV_RETR_LIST, cv2.cv.CV_CHAIN_APPROX_SIMPLE) - center = (0,0) - radius = 0 - if not contours is None and len(contours) > 0: - # get the contour with the largest area - largest = sorted(contours, key = cv2.contourArea, reverse=True)[0] - (x,y),radius = cv2.minEnclosingCircle(largest) - center = (int(x), int(y)) - radius = int(radius) - # draw a circle around the largest - cv2.circle(img, center, radius, (0,255,0), 2) - # copy to image cache for jpegging and streaming [see run()] - self._image_cache = img - self._image_cache_updated = True + bw = img.filter_color(color) #self.save_image(bw.to_jpeg()) - #objects = bw.find_blobs(minsize=5, maxsize=100) - #logging.debug("objects: " + str(objects)) - #dist = -1 - #angle = 180 - - #if objects and len(objects): - #obj = objects[-1] - #bottom = obj.bottom - #logging.info("bottom: " + str(obj.center[0]) + " " +str(obj.bottom)) - #coords = bw.transform([(obj.center[0], obj.bottom)]) - #logging.info("coordinates: " + str(coords)) - #x = coords[0][0] - #y = coords[0][1] - #dist = math.sqrt(math.pow(12 + (68 * (120 - y) / 100),2) + (math.pow((x-80)*60/160,2))) - #angle = math.atan2(x - 80, 120 - y) * 180 / math.pi - #logging.info("object found, dist: " + str(dist) + " angle: " + str(angle)) + objects = bw.find_blobs(minsize=20, maxsize=1000) + logging.debug("objects: " + str(objects)) + dist = -1 + angle = 180 + + if objects and len(objects): + obj = objects[-1] + bottom = obj.bottom + logging.info("bottom: " + str(obj.center[0]) + " " +str(obj.bottom)) + coords = bw.transform([(obj.center[0], obj.bottom)]) + logging.info("coordinates: " + str(coords)) + x = coords[0][0] + y = coords[0][1] + dist = math.sqrt(math.pow(12 + (68 * (120 - y) / 100),2) + (math.pow((x-80)*60/160,2))) + angle = math.atan2(x - 80, 120 - y) * 180 / math.pi + logging.info("object found, dist: " + str(dist) + " angle: " + str(angle)) #self.save_image(img.to_jpeg()) #print "object: " + str(time.time() - ts) - #return [dist, angle] - return [center[0],center[1],radius*2] - + return [dist, angle] def sleep(self, elapse): logging.debug("sleep: " + str(elapse)) time.sleep(elapse) - def drawCircle(self,colour,x,y): - cv2.circle(self._camera.get_image_bgr(),(x,y),30,(0,0,255), -1) - #cv2.circle(self.get_image(0) - - - - diff --git a/templates/blocks_Picasso.xml b/templates/blocks_Picasso.xml new file mode 100644 index 00000000..bf9d1dc5 --- /dev/null +++ b/templates/blocks_Picasso.xml @@ -0,0 +1,157 @@ + diff --git a/templates/blocks_Pioneer.xml b/templates/blocks_Pioneer.xml new file mode 100644 index 00000000..5d067325 --- /dev/null +++ b/templates/blocks_Pioneer.xml @@ -0,0 +1,256 @@ + diff --git a/templates/config.html b/templates/config.html index 2831381e..25aab5e8 100644 --- a/templates/config.html +++ b/templates/config.html @@ -51,7 +51,11 @@

CoderBot

- + + + + +
{% trans %}Program editor details{% endtrans %} From 734eeb5f2dad1e7eb9da60d295402c9bb64cbe17 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 12 Aug 2015 16:23:21 +0000 Subject: [PATCH 47/53] changed back camera.py --- camera.py | 338 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 267 insertions(+), 71 deletions(-) diff --git a/camera.py b/camera.py index 6b0df8a8..b0236f0c 100644 --- a/camera.py +++ b/camera.py @@ -11,7 +11,14 @@ from viz import camera, streamer, image, blob import config -CAMERA_REFRESH_INTERVAL=0.1 +import picamera +import picamera.array +import io +import cv2 +import colorsys +import numpy as np + +CAMERA_REFRESH_INTERVAL=0.01 MAX_IMAGE_AGE = 0.0 PHOTO_PATH = "./photos" DEFAULT_IMAGE = "./photos/broken.jpg" @@ -20,14 +27,25 @@ PHOTO_THUMB_SUFFIX = "_thumb" PHOTO_THUMB_SIZE = (240,180) VIDEO_ELAPSE_MAX = 900 +PHOTO_FILE_EXT = ".jpg" +FFMPEG_CMD = "MP4Box" +VIDEO_FILE_EXT = ".mp4" +VIDEO_FILE_EXT_H264 = ".h264" + +MIN_MATCH_COUNT = 10 + class Camera(Thread): _instance = None - _img_template = image.Image.load("coderdojo-logo.png") + _img_template = cv2.imread("coderdojo-logo.png") _warp_corners_1 = [(0, -120), (640, -120), (380, 480), (260, 480)] _warp_corners_2 = [(0, -60), (320, -60), (190, 240), (130, 240)] _warp_corners_4 = [(0, -30), (160, -30), (95, 120), (65, 120)] + _image_cache = None + _image_cache_updated = False + _face_cascade = cv2.CascadeClassifier('/usr/local/share/OpenCV/lbpcascades/lbpcascade_frontalface.xml') + stream_port = 9080 @classmethod @@ -39,21 +57,9 @@ def get_instance(cls): def __init__(self): logging.info("starting camera") - cam_props = { - "width":640, - "height":480, - "exposure_mode": config.Config.get().get("camera_exposure_mode"), - "rotation": config.Config.get().get("camera_rotation") - } - #try initialising the camera - try: - self._camera = camera.Camera(props=cam_props) - except: - self._camera = None - logging.error("Unexpected error:" + str(sys.exc_info()[0])) - pass + self.init_camera() - self._streamer = streamer.JpegStreamer("0.0.0.0:"+str(self.stream_port), st=0.1) + self._streamer = streamer.JpegStreamer("0.0.0.0:"+str(self.stream_port), st=0.05) #self._cam_off_img.save(self._streamer) self.recording = False self.video_start_time = time.time() + 8640000 @@ -70,22 +76,86 @@ def __init__(self): super(Camera, self).__init__() + def init_camera(self): + try: + props = { + "width":640, + "height":480, + "exposure_mode":config.Config.get().get("camera_exposure_mode"), + "rotation":config.Config.get().get("camera_rotation") + } + self._camera = picamera.PiCamera() + cam = self._camera + res = (props.get("width",640), props.get("height",480)) + cam.resolution = res + cam.framerate = 30 + cam.exposure_mode = props.get("exposure_mode") + cam.rotation = props.get("rotation") + self.out_jpeg = io.BytesIO() + self.out_rgb = picamera.array.PiRGBArray(cam, size=res) + self.h264_encoder = None + self.recording = None + self.video_filename = None + except: + self._camera = None + logging.error("Could not initialise camera:" + str(sys.exc_info()[0])) + pass + + def grab_start(self): + logging.debug("grab_start") + + rgb_res = (640,480) + + camera_port_0, output_port_0 = self._camera._get_ports(True, 0) + self.jpeg_encoder = self._camera._get_image_encoder(camera_port_0, output_port_0, 'jpeg', None, quality=40) + camera_port_1, output_port_1 = self._camera._get_ports(True, 1) + self.rgb_encoder = self._camera._get_image_encoder(camera_port_1, output_port_1, 'bgr', rgb_res) + + with self._camera._encoders_lock: + self._camera._encoders[0] = self.jpeg_encoder + self._camera._encoders[1] = self.rgb_encoder + + def grab_one(self): + self.out_jpeg.seek(0) + self.out_rgb.seek(0) + self.jpeg_encoder.start(self.out_jpeg) + self.rgb_encoder.start(self.out_rgb) + if not self.jpeg_encoder.wait(10): + raise picamera.PiCameraError('Timed Out') + if not self.rgb_encoder.wait(10): + raise picamera.PiCameraError('Timed Out') + + def grab_stop(self): + logging.debug("grab_stop") + + with self._camera._encoders_lock: + del self._camera._encoders[0] + del self._camera._encoders[1] + + self.jpeg_encoder.close() + self.rgb_encoder.close() + def run(self): if self._camera is None: return try: - self._camera.grab_start() + self.grab_start() while self._run: sleep_time = CAMERA_REFRESH_INTERVAL - (time.time() - self._image_time) if sleep_time <= 0: ts = time.time() #print "run.1" self._image_lock.acquire() - self._camera.grab_one() + self.grab_one() self._image_lock.release() #print "run.2: " + str(time.time()-ts) #self.save_image(image.Image(self._camera.get_image_bgr()).open().binarize().to_jpeg()) - self.save_image(self._camera.get_image_jpeg()) + if self._image_cache_updated is True: + ret, jpeg_array = cv2.imencode('.jpeg', self._image_cache) + self.jpeg_to_stream(np.array(jpeg_array).tostring()) + self._image_cache_updated = False + else: + self.jpeg_to_stream(self.out_jpeg.getvalue()) #print "run.3: " + str(time.time()-ts) else: time.sleep(sleep_time) @@ -93,31 +163,45 @@ def run(self): if self.recording and time.time() - self.video_start_time > VIDEO_ELAPSE_MAX: self.video_stop() - self._camera.grab_stop() + self.grab_stop() except: logging.error("Unexpected error:" + str(sys.exc_info()[0])) raise - def get_image(self, maxage = MAX_IMAGE_AGE): + def get_image_array(self, maxage = MAX_IMAGE_AGE): if self._camera is None: - return Image(cv2.imread(DEFAULT_IMAGE)) + print "get_image: Default image" + return cv2.imread(DEFAULT_IMAGE) else: - return image.Image(self._camera.get_image_bgr()) + return self.out_rgb.array + + def get_image_binarized(self, maxage = MAX_IMAGE_AGE): + data = self.get_image_array(maxage) + data = cv2.cvtColor(data, cv2.cv.CV_BGR2GRAY) + data = cv2.adaptiveThreshold(data, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 5, 3) + return data - def save_image(self, image_jpeg): + def get_image_copy(self,maxage = MAX_IMAGE_AGE): + if self._camera is None: + return cv2.imread(DEFAULT_IMAGE) + else: + return image.Image(self.out_rgb.array) + + + def jpeg_to_stream(self, image_jpeg): self._streamer.set_image(image_jpeg) self._image_time=time.time() def set_text(self, text): if self._camera is None: return - self._camera.set_overlay_text(str(text)) + self._camera.annotate_text = str(text) def get_next_photo_index(self): last_photo_index = 0 for p in self._photos: try: - index = int(p[len(PHOTO_PREFIX):-len(self._camera.PHOTO_FILE_EXT)]) + index = int(p[len(PHOTO_PREFIX):-len(self.PHOTO_FILE_EXT)]) if index > last_photo_index: last_photo_index = index except: @@ -127,21 +211,21 @@ def get_next_photo_index(self): def rotate(self): if self._camera is None: return - rot = self._camera.camera.rotation + rot = self._camera.rotation rot = rot + 90 if rot > 271: rot = 0 - self._camera.camera.rotation = rot + self._camera.rotation = rot def photo_take(self): if self._camera is None: return photo_index = self.get_next_photo_index() - filename = PHOTO_PREFIX + str(photo_index) + self._camera.PHOTO_FILE_EXT; - filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + filename = PHOTO_PREFIX + str(photo_index) + PHOTO_FILE_EXT; + filename_thumb = PHOTO_PREFIX + str(photo_index) + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; of = open(PHOTO_PATH + "/" + filename, "w+") oft = open(PHOTO_PATH + "/" + filename_thumb, "w+") - im_str = self._camera.get_image_jpeg() + im_str = self.out_jpeg.getvalue() of.write(im_str) # thumb im_pil = PILImage.open(StringIO(im_str)) @@ -152,37 +236,60 @@ def is_recording(self): return self.recording def video_rec(self, video_name=None): - if self.is_recording(): + if self.recording: return - self.recording = True if self._camera is None: return - + self.recording = True if video_name is None: video_index = self.get_next_photo_index() - filename = VIDEO_PREFIX + str(video_index) + self._camera.VIDEO_FILE_EXT; - filename_thumb = VIDEO_PREFIX + str(video_index) + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + filename = VIDEO_PREFIX + str(video_index) + VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + str(video_index) + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; else: - filename = VIDEO_PREFIX + video_name + self._camera.VIDEO_FILE_EXT; - filename_thumb = VIDEO_PREFIX + video_name + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT; + filename = VIDEO_PREFIX + video_name + VIDEO_FILE_EXT; + filename_thumb = VIDEO_PREFIX + video_name + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT; try: os.remove(PHOTO_PATH + "/" + filename) except: pass oft = open(PHOTO_PATH + "/" + filename_thumb, "w") - im_str = self._camera.get_image_jpeg() + im_str = self.out_jpeg.getvalue() im_pil = PILImage.open(StringIO(im_str)) im_pil.resize(PHOTO_THUMB_SIZE).save(oft) self._photos.append(filename) - self._camera.video_rec(PHOTO_PATH + "/" + filename) + + filename = PHOTO_PATH + "/" + filename + self.video_filename = filename[:filename.rfind(".")] + + camera_port_2, output_port_2 = self._camera._get_ports(True, 2) + self.h264_encoder = self._camera._get_video_encoder(camera_port_2, output_port_2, 'h264', None) + + with self._camera._encoders_lock: + self._camera._encoders[2] = self.h264_encoder + logging.debug( self.video_filename + VIDEO_FILE_EXT_H264 ) + + self.h264_encoder.start(self.video_filename + VIDEO_FILE_EXT_H264) + + #self._camera.video_rec(PHOTO_PATH + "/" + filename) self.video_start_time = time.time() def video_stop(self): if self._camera is None: return if self.recording: - self._camera.video_stop() + logging.debug("video_stop") + self.h264_encoder.stop() + + with self.camera._encoders_lock: + del self.camera._encoders[2] + + self.h264_encoder.close() + # pack in mp4 container + params = " -fps 12 -add " + self.video_filename + VIDEO_FILE_EXT_H264 + " " + self.video_filename + VIDEO_FILE_EXT + os.system(self.FFMPEG_CMD + params) + # remove h264 file + os.remove(self.video_filename + VIDEO_FILE_EXT_H264) self.recording = False def get_photo_list(self): @@ -198,7 +305,7 @@ def delete_photo(self, filename): logging.info("delete photo: " + filename) os.remove(PHOTO_PATH + "/" + filename) if self._camera is not None: - os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + self._camera.PHOTO_FILE_EXT) + os.remove(PHOTO_PATH + "/" + filename[:filename.rfind(".")] + PHOTO_THUMB_SUFFIX + PHOTO_FILE_EXT) self._photos.remove(filename) def exit(self): @@ -208,7 +315,7 @@ def exit(self): def calibrate(self): if self._camera is None: return - img = self._camera.getImage() + img = self._camera.getImage() # ?? self._background = img.hueHistogram()[-1] def set_rotation(self, rotation): @@ -218,7 +325,7 @@ def set_rotation(self, rotation): def find_line(self): self._image_lock.acquire() - img = self.get_image(0).binarize() + img = self.get_image_binarized(0) slices = [0,0,0] blobs = [0,0,0] slices[0] = img.crop(0, 100, 160, 120) @@ -234,13 +341,48 @@ def find_line(self): self._image_lock.release() return coords[0] + def find_template(self, img, template): + # initiate sift detector + sift = cv2.SIFT() + # find the keypoints and descriptors with SIFT + kp1, des1 = sift.detectAndCompute(template, None) + kp2, des2 = sift.detectAndCompute(img, None) + FLANN_INDEX_KDTREE = 0 + index_params = dict(algorithm = FLAN_INDEX_KDTREE, trees = 5) + search_params = dict(checks = 50) + flann = cv2.FlannBasedMatcher(index_params, search_params) + matches = flan.knnMatch(des1, des2, k=2) + # store all the good matches as per Lowe's ratio test + good = [] + templates = [] + for m,n in matches: + if m.distance < 0.7 * n.distance: + good.append(m) + + if len(good) > MIN_MATCH_COUNT: + src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2) + dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2) + + M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) + matchesMask = mask.ravel().tolist() + + h,w = template.shape + pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2) + dst = cv2.perspectiveTransform(pts,M) + logging.info("found template: " + dst) + templates[0] = dst + else: + logging.info("Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)) + matchesMask = None + return templates + def find_signal(self): #print "signal" angle = None ts = time.time() self._image_lock.acquire() - img = self.get_image(0) - signals = img.find_template(self._img_template) + img = self.get_image_array(0) + signals = self.find_template(img, self._img_template) logging.info("signal: " + str(time.time() - ts)) if len(signals): @@ -253,9 +395,10 @@ def find_signal(self): def find_face(self): faceX = faceY = faceS = None self._image_lock.acquire() - img = self.get_image(0) + img = self.get_image_array(0) ts = time.time() - faces = img.grayscale().find_faces() + img = cv2.cvtColor(img, cv2.cv.CV_BGR2GRAY) + faces = self._face_cascade.detectMultiScale(img) print "face.detect: " + str(time.time() - ts) self._image_lock.release() print faces @@ -263,6 +406,7 @@ def find_face(self): # Get the largest face, face is a rectangle x, y, w, h = faces[0] centerX = x + (w/2) + # ASSUMING RGB 160X120 RESOLUTION! faceX = ((centerX * 100) / 160) - 50 #center = 0 centerY = y + (h/2) faceY = 50 - (centerY * 100) / 120 #center = 0 @@ -270,11 +414,26 @@ def find_face(self): faceS = (size * 100) / 120 return [faceX, faceY, faceS] + def find_blobs(self, img, minsize=0, maxsize=1000000): + blobs = [] + contours, hierarchy = cv2.findContours(img, cv2.cv.CV_RETR_TREE, cv2.cv.CV_CHAIN_APPROX_SIMPLE) + for c in contours: + area = cv2.contourArea(c) + if area > minsize and area < maxsize: + if len(blobs) and area > blobs[0].area: + blobs.insert(0, blob.Blob(c)) + else: + blobs.append(blob.Blob(c)) + + return blobs + def path_ahead(self): #print "path ahead" ts = time.time() self._image_lock.acquire() - img = self.get_image(0) + img = self.get_image_array(0) + img_binarized = self.get_image_binarized(0) + blobs = self.find_blobs(img=img_binarized, minsize=100, maxsize=8000) #print "path_ahead.get_image: " + str(time.time() - ts) #img.crop(0, 100, 160, 120) @@ -291,7 +450,7 @@ def path_ahead(self): #control_hue = control_hue - 10 #binarized = color_distance.binarize(control_hue).invert() #print "path_ahead.binarize: " + str(time.time() - ts) - blobs = img.binarize().find_blobs(minsize=100, maxsize=8000) + #blobs = img.binarize().find_blobs(minsize=100, maxsize=8000) #print "path_ahead.blobs: " + str(time.time() - ts) coordY = 60 if len(blobs): @@ -304,8 +463,11 @@ def path_ahead(self): #dw_x = 260 + obstacle.coordinates()[0] - (obstacle.width()/2) #dw_y = 160 + obstacle.coordinates()[1] - (obstacle.height()/2) #img.drawRectangle(dw_x, dw_y, obstacle.width(), obstacle.height(), color=(255,0,0)) - x, y = img.transform((obstacle.center[0], obstacle.bottom)) - coordY = 60 - ((y * 48) / 100) + + # TRANSFORM ASSUMES RGB 160X120 RESOLUTION! + #x, y = img.transform((obstacle.center[0], obstacle.bottom)) + #coordY = 60 - ((y * 48) / 100) + coordY = 60 - ((obstacle.center[1] * 48) / 100) logging.info("coordY: " + str(coordY)) #print obstacle.coordinates()[1]+(obstacle.height()/2) #ar_layer.centeredRectangle(obstacle.coordinates(), (obstacle.width(), obstacle.height())) @@ -326,31 +488,65 @@ def find_color(self, s_color): code_data = None ts = time.time() self._image_lock.acquire() - img = self.get_image(0) + img = self.get_image_array(0) self._image_lock.release() - bw = img.filter_color(color) + #bw = img.filter_color(color) + h, s, v = colorsys.rgb_to_hsv(color[0]/255.0, color[1]/255.0, color[2]/255.0) + img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + h = h * 180 + s = s * 255 + v = v * 255 + logging.debug("color_hsv: " + str(h) + " " + str(s) + " " + str(v)) + lower_color = np.array([h-10, 50, 50]) + upper_color = np.array([h+10, 255, 255]) + logging.debug("lower: " + str(lower_color) + " upper: " + str(upper_color)) + bw = cv2.inRange(img_hsv, lower_color, upper_color) + # get unordered list of contours in filtered image + contours, _ = cv2.findContours(bw, cv2.cv.CV_RETR_LIST, cv2.cv.CV_CHAIN_APPROX_SIMPLE) + center = (0,0) + radius = 0 + if not contours is None and len(contours) > 0: + # get the contour with the largest area + largest = sorted(contours, key = cv2.contourArea, reverse=True)[0] + (x,y),radius = cv2.minEnclosingCircle(largest) + center = (int(x), int(y)) + radius = int(radius) + # draw a circle around the largest + cv2.circle(img, center, radius, (0,255,0), 2) + # copy to image cache for jpegging and streaming [see run()] + self._image_cache = img + self._image_cache_updated = True #self.save_image(bw.to_jpeg()) - objects = bw.find_blobs(minsize=20, maxsize=1000) - logging.debug("objects: " + str(objects)) - dist = -1 - angle = 180 - - if objects and len(objects): - obj = objects[-1] - bottom = obj.bottom - logging.info("bottom: " + str(obj.center[0]) + " " +str(obj.bottom)) - coords = bw.transform([(obj.center[0], obj.bottom)]) - logging.info("coordinates: " + str(coords)) - x = coords[0][0] - y = coords[0][1] - dist = math.sqrt(math.pow(12 + (68 * (120 - y) / 100),2) + (math.pow((x-80)*60/160,2))) - angle = math.atan2(x - 80, 120 - y) * 180 / math.pi - logging.info("object found, dist: " + str(dist) + " angle: " + str(angle)) + #objects = bw.find_blobs(minsize=5, maxsize=100) + #logging.debug("objects: " + str(objects)) + #dist = -1 + #angle = 180 + + #if objects and len(objects): + #obj = objects[-1] + #bottom = obj.bottom + #logging.info("bottom: " + str(obj.center[0]) + " " +str(obj.bottom)) + #coords = bw.transform([(obj.center[0], obj.bottom)]) + #logging.info("coordinates: " + str(coords)) + #x = coords[0][0] + #y = coords[0][1] + #dist = math.sqrt(math.pow(12 + (68 * (120 - y) / 100),2) + (math.pow((x-80)*60/160,2))) + #angle = math.atan2(x - 80, 120 - y) * 180 / math.pi + #logging.info("object found, dist: " + str(dist) + " angle: " + str(angle)) #self.save_image(img.to_jpeg()) #print "object: " + str(time.time() - ts) - return [dist, angle] + #return [dist, angle] + return [center[0],center[1],radius*2] + def sleep(self, elapse): logging.debug("sleep: " + str(elapse)) time.sleep(elapse) + def drawCircle(self,colour,x,y): + cv2.circle(self._camera.get_image_bgr(),(x,y),30,(0,0,255), -1) + #cv2.circle(self.get_image(0) + + + + From 5ea0817c7cb536456f5a6f8d66b1ede62ad8509a Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 18 Aug 2015 13:15:19 +0000 Subject: [PATCH 48/53] RGB LED stuff. --- coderbot.py | 31 ++++++++++++++---- static/js/blockly/blocks.js | 63 ++++++++++++++++++++++++++++++++++-- static/js/blockly/bot_en.js | 2 ++ templates/blocks_Picasso.xml | 9 +++++- templates/blocks_Pioneer.xml | 7 ++++ templates/blocks_adv.xml | 7 ++++ templates/blocks_std.xml | 9 +++++- 7 files changed, 116 insertions(+), 12 deletions(-) diff --git a/coderbot.py b/coderbot.py index 8b829f72..a5c10e55 100644 --- a/coderbot.py +++ b/coderbot.py @@ -11,12 +11,13 @@ PIN_SERVO_3 = 9 # ARM PIN PIN_SERVO_4 = 10 LED_RED = 21 -LED_YELLOW = 16 +LED_BLUE = 16 LED_GREEN = 20 PWM_FREQUENCY = 50 #Hz PWM_UPPER = 1700 # 1.7ms for full clockwise PWM_LOWER = 1300 # 1.3ms for full anti-clockwise +LEDPinFreq =100 def coderbot_callback(gpio, level, tick): @@ -40,6 +41,9 @@ def __init__(self, servo=False): for pin in self._pin_out: self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) + self.pi.set_PWM_frequency(LED_RED, LEDPinFreq) + self.pi.set_PWM_frequency(LED_GREEN, LEDPinFreq) + self.pi.set_PWM_frequency(LED_BLUE, LEDPinFreq) self.stop() self._is_moving = False @@ -199,8 +203,8 @@ def LED_on(self, Colour, seconds, pin=0): pin = LED_RED elif Colour == "Green LED": pin = LED_GREEN - elif Colour == "Yellow LED": - pin = LED_YELLOW + elif Colour == "Blue LED": + pin = LED_BLUE self.pi.write(pin, 1) time.sleep(seconds) self.pi.write(pin, 0) @@ -210,8 +214,8 @@ def LED_on_indef(self, Colour, pin=0): pin = LED_RED elif Colour == "Green LED": pin = LED_GREEN - elif Colour == "Yellow LED": - pin = LED_YELLOW + elif Colour == "Blue LED": + pin = LED_BLUE self.pi.write(pin, 1) def LED_off_indef(self, Colour, pin=0): @@ -219,6 +223,19 @@ def LED_off_indef(self, Colour, pin=0): pin = LED_RED elif Colour == "Green LED": pin = LED_GREEN - elif Colour == "Yellow LED": - pin = LED_YELLOW + elif Colour == "Blue LED": + pin = LED_BLUE self.pi.write(pin, 0) + + def RGB_Blend(self, s_color): + colorR = int(s_color[1:3],16) + colorG = int(s_color[3:5],16) + colorB = int(s_color[5:7],16) + self.pi.set_PWM_dutycycle(LED_RED, colorR) + self.pi.set_PWM_dutycycle(LED_GREEN, colorG) + self.pi.set_PWM_dutycycle(LED_BLUE, colorB) + + def alloff(self): + self.pi.write(LED_GREEN, 0) + self.pi.write(LED_RED, 0) + self.pi.write(LED_BLUE, 0) diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 5d403e0a..3419d4f3 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -866,7 +866,7 @@ Blockly.Blocks['turn_led_on'] = { this.appendValueInput("seconds") .setCheck("Number") .appendField("Turn the ") - .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["yellow", "Yellow LED"]]), "Colour") + .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["blue", "Blue LED"]]), "Colour") .appendField("LED on for "); this.appendDummyInput() .appendField("seconds"); @@ -898,7 +898,7 @@ Blockly.Blocks['led_on'] = { init: function() { this.appendDummyInput() .appendField("Turn the ") - .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["yellow", "Yellow LED"]]), "Colour") + .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["blue", "Blue LED"]]), "Colour") .appendField("LED on"); this.setHelpUrl(Blockly.Msg.CODERBOT_LED_ON); this.setColour(355); @@ -925,7 +925,7 @@ Blockly.Blocks['led_off'] = { init: function() { this.appendDummyInput() .appendField("Turn the ") - .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["yellow", "Yellow LED"]]), "Colour") + .appendField(new Blockly.FieldDropdown([["red", "Red LED"], ["green", "Green LED"], ["blue", "Blue LED"]]), "Colour") .appendField("LED off"); this.setHelpUrl(Blockly.Msg.CODERBOT_LED_OFF); this.setColour(355); @@ -948,3 +948,60 @@ Blockly.Python['led_off'] = function(block) { return code; }; +Blockly.Blocks['rgb_blend'] = { + /** + * Block for findSignal function. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("COLOR") + .setCheck('Colour') + .appendField("Turn the LED on with the specific colour "); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setHelpUrl(Blockly.Msg.CODERBOT_LED_BLEND); + this.setColour(355); + } +}; + + +Blockly.JavaScript['rgb_blend'] = function(block) { + // Generate JavaScrip code for LED lights + var color = Blockly.Javascript.valueToCode(block, 'COLOR', Blockly.Javascript.ORDER_ATOMIC); + var code = "get_bot().RGB_Blend(s_color=" + s_color + ")\n"; + return code +}; + +Blockly.Python['rgb_blend'] = function(block) { + // Generate Python code for LED lights + var s_color = Blockly.Python.valueToCode(block, 'COLOR', Blockly.Python.ORDER_ATOMIC); + var code = "get_bot().RGB_Blend(s_color=" + s_color + ")\n"; + return code +}; + +Blockly.Blocks['all_off'] = { + /** + * Block for armLower function. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("Turn all LEDs off"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setTooltip('Coderbot_alloffTooltip'); + this.setHelpUrl(Blockly.Msg.CODERBOT_ALL_OFF_HELPURL); + this.setColour(355); + } +}; + +Blockly.JavaScript['all_off'] = function(block) { + // Generate JavaScript code for lowering the arm + return 'get_bot().alloff();\n'; +}; + +Blockly.Python['all_off'] = function(block) { + // Generate Python code for lowering the arm + return 'get_bot().alloff()\n'; +}; \ No newline at end of file diff --git a/static/js/blockly/bot_en.js b/static/js/blockly/bot_en.js index 5192a73a..f655a35b 100644 --- a/static/js/blockly/bot_en.js +++ b/static/js/blockly/bot_en.js @@ -52,3 +52,5 @@ Blockly.Msg.CODERBOT_SENSOR_FINDLOGO = "find logo"; Blockly.Msg.CODERBOT_LED = "turn LED on for a set time"; Blockly.Msg.CODERBOT_LED_ON = "turns LED on" Blockly.Msg.CODERBOT_LED_OFF = "turns LED off" +Blockly.Msg.CODERBOT_LED_BLEND = "Blended colour as LED output" +Blockly.Msg.CODERBOT_ALL_OFF = "turn all the LEDs off" \ No newline at end of file diff --git a/templates/blocks_Picasso.xml b/templates/blocks_Picasso.xml index bf9d1dc5..6201c81b 100644 --- a/templates/blocks_Picasso.xml +++ b/templates/blocks_Picasso.xml @@ -69,7 +69,14 @@ - + + + + + + + + diff --git a/templates/blocks_Pioneer.xml b/templates/blocks_Pioneer.xml index 5d067325..9461fe3c 100644 --- a/templates/blocks_Pioneer.xml +++ b/templates/blocks_Pioneer.xml @@ -78,6 +78,13 @@ + + + + + + + diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index be0790c4..a5e153a4 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -260,5 +260,12 @@ + + + + + + + diff --git a/templates/blocks_std.xml b/templates/blocks_std.xml index 2520c1f9..b0dfbe50 100644 --- a/templates/blocks_std.xml +++ b/templates/blocks_std.xml @@ -86,6 +86,13 @@ - + + + + + + + + From eb06d72a4420c7b6d6dcb4291585df73de1f861e Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 18 Aug 2015 16:47:30 +0000 Subject: [PATCH 49/53] Changed coderbot to use custom wait function that calls check_end periodically This solves to issue of the bot dosnt stop when told to --- coderbot.py | 22 ++++++++++++++++++---- program.py | 4 +++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/coderbot.py b/coderbot.py index a5c10e55..791668c5 100644 --- a/coderbot.py +++ b/coderbot.py @@ -26,7 +26,9 @@ def coderbot_callback(gpio, level, tick): class CoderBot: _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): + self.check_end = None self.pi = pigpio.pi('localhost') self.pi.set_mode(PIN_PUSHBUTTON, pigpio.INPUT) self._cb = dict() @@ -50,11 +52,15 @@ def __init__(self, servo=False): the_bot = None @classmethod - def get_instance(cls, servo=False): + def get_instance(cls, servo = False): if not cls.the_bot: cls.the_bot = CoderBot(servo) return cls.the_bot + #add a method to be called periodically while waiting + def setCheckEndMethod(self,check_end): + self.check_end = check_end + def move(self, speed=100, elapse=-1): self.motor_control(speed_left=speed, speed_right=speed, elapse=elapse) @@ -71,7 +77,7 @@ 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) + self.turn(speed=speed, elapse=elapse) def servo3(self, angle): #self._servo_control(PIN_SERVO_3, angle) @@ -110,7 +116,7 @@ def _dc_motor(self, speed_left=100, speed_right=100, elapse=-1): self.pi.write(PIN_MOTOR_ENABLE, 1) if elapse > 0: - time.sleep(elapse) + self.sleep(elapse) self.stop() def _servo_motor(self, speed_left=100, speed_right=100, elapse=-1): @@ -124,7 +130,7 @@ def _servo_motor(self, speed_left=100, speed_right=100, elapse=-1): 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.sleep(elapse) self.stop() @@ -163,6 +169,14 @@ def clamp(self,x,lower,upper): x = upper return x + def sleep(self, elapse): + for i in range(int(elapse / 0.1)): + time.sleep(0.1) + if self.check_end != None: + self.check_end() + + time.sleep(elapse % 0.1) + def is_moving(self): return self._is_moving diff --git a/program.py b/program.py index cdc336f1..e02cdc39 100644 --- a/program.py +++ b/program.py @@ -13,6 +13,7 @@ PROGRAM_PREFIX = "program_" PROGRAM_SUFFIX = ".data" + def get_cam(): return camera.Camera.get_instance() @@ -118,18 +119,19 @@ def run(self): try: #print "run.1" bot = coderbot.CoderBot.get_instance() + bot.setCheckEndMethod(self.check_end) cam = camera.Camera.get_instance() program = self if config.Config.get().get("prog_video_rec") == "true": get_cam().video_rec(program.name) logging.debug("starting video") exec(self._code) - #print "run.2" except RuntimeError as re: logging.info("quit: " + str(re)) finally: get_cam().video_stop() #if video is running, stop it get_cam().set_text(' ') + #get_cam._streamer.end_stream() get_motion().stop() self._running = False From c3a1951a5efcf37fd6e0c53ddb08bc79375edae3 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 19 Aug 2015 16:31:45 +0000 Subject: [PATCH 50/53] Fixed servo up/down commands --- coderbot.py | 4 ++-- static/js/blockly/blocks.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coderbot.py b/coderbot.py index 791668c5..804ec7e6 100644 --- a/coderbot.py +++ b/coderbot.py @@ -86,10 +86,10 @@ def servo3(self, angle): self.pi.set_servo_pulsewidth(PIN_SERVO_3,pwm) def arm_up(self): - self.servo3(120) + self.servo3(0) def arm_down(self): - self.servo3(0) + self.servo3(120) def servo4(self, angle): diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 3419d4f3..4ce4bf4e 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -783,12 +783,12 @@ Blockly.Blocks['coderbot_armRaise'] = { Blockly.JavaScript['coderbot_armRaise'] = function(block) { // Generate JavaScript for raising the arm - return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_RAISED + ');\n'; + return 'get_bot().arm_up();\n'; }; Blockly.Python['coderbot_armRaise'] = function(block) { // Generate Python code for raising the arm - return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_RAISED + ')\n'; + return 'get_bot().arm_up()\n'; }; Blockly.Blocks['coderbot_armLower'] = { @@ -813,12 +813,12 @@ Blockly.Blocks['coderbot_armLower'] = { Blockly.JavaScript['coderbot_armLower'] = function(block) { // Generate JavaScript code for lowering the arm - return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_LOWERED + ');\n'; + return 'get_bot().arm_down();\n'; }; Blockly.Python['coderbot_armLower'] = function(block) { // Generate Python code for lowering the arm - return 'get_bot().servo3(' + CODERBOT_ARM_ANGLE_LOWERED + ')\n'; + return 'get_bot().arm_down()\n'; }; From e19f56c33502e6c2383309aa39410068cc194250 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Tue, 25 Aug 2015 10:47:32 +0000 Subject: [PATCH 51/53] fixed image processing motion code --- motion.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/motion.py b/motion.py index 258e8e81..fdccac54 100644 --- a/motion.py +++ b/motion.py @@ -20,10 +20,18 @@ blockSize = 7 ) -PI_CAM_FOV_H_DEG = 53.0 +#PI_CAM_FOV_H_DEG = 53.0 +#PI_CAM_FOV_V_CM = 100.0 +PI_CAM_FOV_H_DEG = 100.0 PI_CAM_FOV_V_CM = 100.0 -IMAGE_WIDTH = 160.0 -IMAGE_HEIGHT = 120.0 +#IMAGE_WIDTH = 160.0 +#IMAGE_HEIGHT = 120.0 +IMAGE_WIDTH = 640.0 +IMAGE_HEIGHT = 480.0 + +TURN_PERIOD = 0.05 +TURN_SPEED = 70 + class Motion: def __init__(self): @@ -74,7 +82,7 @@ def stop(self): def loop_move(self): self.running = True while self.running: - frame = self.cam.get_image() + frame = self.cam.get_image_copy() self.frame_gray = frame.grayscale() if len(self.tracks) < 2 or self.frame_idx % self.detect_interval == 0: @@ -94,7 +102,7 @@ def loop_move(self): def loop_turn(self): self.running = True while self.running: - frame = self.cam.get_image() + frame = self.cam.get_image_copy() self.frame_gray = frame.grayscale() if len(self.tracks) < 2 or self.frame_idx % self.detect_interval == 0: @@ -189,6 +197,9 @@ def calc_motion(self): self.delta_dist += (avg_delta_y * PI_CAM_FOV_V_CM) / IMAGE_HEIGHT #print "count: ", count, "delta_a: ", self.delta_angle, " avg_delta_x: ", avg_delta_x, " delta_y: ", self.delta_dist, " avg_delta_y: ", avg_delta_y + print "angle : " + print self.delta_angle + #cv2.line(self.vis, (int(80+deltaAngle),20), (80,20), (0, 0, 255)) #cv2.putText(self.vis, "delta: " + str(int((self.deltaAngle*53.0)/160.0)) + " avg_delta: " + str(int(((avg_delta_x*53.0)/160.0))), (0,20), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (0, 0, 255)) return self.delta_angle, self.delta_dist @@ -200,7 +211,8 @@ def bot_turn(self, target_angle, delta_angle): for p_a in self.power_angles: if abs(target_angle - delta_angle) > p_a[0] and self.running: #print "pow: ", p_a[1][0], " duration: ", p_a[1][1] - self.bot.motor_control(sign * p_a[1][0], -1 * sign * p_a[1][0], p_a[1][1]) + #self.bot.motor_control(sign * p_a[1][0], -1 * sign * p_a[1][0], p_a[1][1]) + self.bot.motor_control(sign * TURN_SPEED, -1 * sign * TURN_SPEED, TURN_PERIOD) run = p_a[1][0] > 0 #stopped break From 8cd215c4de192f671f3cf6729f14d0c5124f4618 Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 26 Aug 2015 12:57:14 +0000 Subject: [PATCH 52/53] removed motion control commands they dont work and the motion control sucks anyway --- templates/blocks_Picasso.xml | 14 -------------- templates/blocks_Pioneer.xml | 14 -------------- templates/blocks_adv.xml | 14 -------------- 3 files changed, 42 deletions(-) diff --git a/templates/blocks_Picasso.xml b/templates/blocks_Picasso.xml index 6201c81b..28b87644 100644 --- a/templates/blocks_Picasso.xml +++ b/templates/blocks_Picasso.xml @@ -4,20 +4,6 @@ - - - - 10 - - - - - - - 90 - - - diff --git a/templates/blocks_Pioneer.xml b/templates/blocks_Pioneer.xml index 9461fe3c..62923a55 100644 --- a/templates/blocks_Pioneer.xml +++ b/templates/blocks_Pioneer.xml @@ -4,20 +4,6 @@ - - - - 10 - - - - - - - 90 - - - diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index a5e153a4..0f3516be 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -174,20 +174,6 @@ - - - - 10 - - - - - - - 90 - - - From da61e47421d08dfc033a2783ce24a19df915e4da Mon Sep 17 00:00:00 2001 From: Steve Withers Date: Wed, 26 Aug 2015 13:03:31 +0000 Subject: [PATCH 53/53] added variables to picasso --- templates/blocks_Picasso.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/blocks_Picasso.xml b/templates/blocks_Picasso.xml index 28b87644..d9c5d4c0 100644 --- a/templates/blocks_Picasso.xml +++ b/templates/blocks_Picasso.xml @@ -74,6 +74,7 @@ +