diff --git a/.gitignore b/.gitignore index b17f9758..a6994bd3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,10 @@ *.sqlite # Pictures taken -photos/ +*.jpg +*.jpeg +*.png + # Logs logs/ @@ -49,3 +52,7 @@ static/blockly-tutorial .Trashes ehthumbs.db Thumbs.db + +# autosave 'tilde' files # +########################## +*~ 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/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/camera.py b/camera.py index 3dffd619..b0236f0c 100644 --- a/camera.py +++ b/camera.py @@ -11,22 +11,41 @@ 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" PHOTO_PREFIX = "DSC" VIDEO_PREFIX = "VID" 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 @@ -38,9 +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")} - self._camera = camera.Camera(props=cam_props) - self._streamer = streamer.JpegStreamer("0.0.0.0:"+str(self.stream_port), st=0.1) + self.init_camera() + + 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 @@ -57,20 +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) @@ -78,39 +163,69 @@ 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): - return image.Image(self._camera.get_image_bgr()) + def get_image_array(self, maxage = MAX_IMAGE_AGE): + if self._camera is None: + print "get_image: Default image" + return cv2.imread(DEFAULT_IMAGE) + else: + 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): - self._camera.set_overlay_text(str(text)) + if self._camera is None: + return + 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: pass return last_photo_index + 1 + def rotate(self): + if self._camera is None: + return + rot = self._camera.rotation + rot = rot + 90 + if rot > 271: + rot = 0 + 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)) @@ -121,33 +236,60 @@ def is_recording(self): return self.recording def video_rec(self, video_name=None): - if self.is_recording(): + if self.recording: + return + 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): @@ -162,7 +304,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 + PHOTO_FILE_EXT) self._photos.remove(filename) def exit(self): @@ -170,12 +313,19 @@ def exit(self): self.join() def calibrate(self): - img = self._camera.getImage() + 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() + img = self.get_image_binarized(0) slices = [0,0,0] blobs = [0,0,0] slices[0] = img.crop(0, 100, 160, 120) @@ -191,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): @@ -210,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 @@ -220,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 @@ -227,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) @@ -248,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): @@ -261,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())) @@ -283,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: " + 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/coderbot.cfg b/coderbot.cfg index 910d219b..1e8b5218 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", "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/coderbot.py b/coderbot.py index 439a1c86..804ec7e6 100644 --- a/coderbot.py +++ b/coderbot.py @@ -4,15 +4,21 @@ 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_3 = 9 # ARM PIN PIN_SERVO_4 = 10 +LED_RED = 21 +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 -PWM_FREQUENCY = 100 #Hz -PWM_RANGE = 100 #0-100 def coderbot_callback(gpio, level, tick): return CoderBot.get_instance().callback(gpio, level, tick) @@ -20,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() @@ -34,19 +42,25 @@ 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.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 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) @@ -63,10 +77,20 @@ 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) + #self._servo_control(PIN_SERVO_3, angle) + angle = self.clamp(angle,0,100) + 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(0) + + def arm_down(self): + self.servo3(120) + def servo4(self, angle): self._servo_control(PIN_SERVO_4, angle) @@ -92,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): @@ -106,30 +130,53 @@ 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() def _servo_motor_control(self, pin, speed): self._is_moving = True - speed = ((speed + 100) * 50 / 200) + 52 - self.pi.set_PWM_range(pin, 1000) - self.pi.set_PWM_frequency(pin, 50) - self.pi.set_PWM_dutycycle(pin, speed) + #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): - duty = ((angle + 90) * 100 / 180) + 25 - self.pi.set_PWM_range(pin, 1000) - self.pi.set_PWM_frequency(pin, 50) + # 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) 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): + if x < lower: + x = lower + if x > 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 @@ -137,7 +184,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 @@ -165,4 +212,44 @@ def reboot(self): os.system ('sudo reboot') - + def LED_on(self, Colour, seconds, pin=0): + if Colour == "Red LED": + pin = LED_RED + elif Colour == "Green LED": + pin = LED_GREEN + elif Colour == "Blue LED": + pin = LED_BLUE + 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 = LED_RED + elif Colour == "Green LED": + pin = LED_GREEN + elif Colour == "Blue LED": + pin = LED_BLUE + self.pi.write(pin, 1) + + def LED_off_indef(self, Colour, pin=0): + if Colour == "Red LED": + pin = LED_RED + elif Colour == "Green LED": + pin = LED_GREEN + 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/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/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/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 diff --git a/init.py b/init.py index cc459059..1f050b0f 100755 --- a/init.py +++ b/init.py @@ -1,5 +1,9 @@ #!/usr/bin/python +import os + +os.chdir("/home/pi/coderbot_lboro/") + import coderbot import main diff --git a/main.py b/main.py index 8858e5b4..d569d990 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("/") @@ -57,13 +57,13 @@ 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": - return "http://coder.bot:8080/"; - else: + return "http://coder.bot:8080/" + elif mode == "client": return "http://coderbotsrv.appspot.com/" @app.route("/bot", methods=["GET"]) @@ -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")) @@ -107,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" @@ -122,6 +128,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/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/motion.py b/motion.py index bf41ae9c..fdccac54 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), @@ -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 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! diff --git a/pigpio_test.py b/pigpio_test.py new file mode 100644 index 00000000..d0cba354 --- /dev/null +++ b/pigpio_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +import time +import pigpio + + +PIN = 25 # RIGHT=4 LEFT=25 ARM=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) + +# duty cycle values for range 2000, frequency 50 +# + +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.02) + print x + + +pi.set_PWM_dutycycle(PIN,0) +print 'test complete.' + diff --git a/program.py b/program.py index 80d507de..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,17 +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 diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..ccd809a4 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,15 @@ +|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/coderbot b/scripts/coderbot deleted file mode 100755 index 2d0f3d1a..00000000 --- a/scripts/coderbot +++ /dev/null @@ -1,58 +0,0 @@ -#!/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 -### 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 -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: -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 --chdir $DIR --chuid $DAEMON_USER --exec /usr/bin/python -- ./init.py - 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/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 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/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/scripts/etc/init.d/coderbot b/scripts/etc/init.d/coderbot new file mode 100755 index 00000000..a0209fca --- /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=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/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..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..80e54eeb --- /dev/null +++ b/scripts/etc/network/interfaces_cli @@ -0,0 +1,12 @@ +auto lo +iface lo inet loopback +#iface eth0 inet dhcp +#iface default inet dhcp +allow-hotplug wlan0 +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/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 + 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/scripts/hostapd b/scripts/hostapd deleted file mode 100755 index b19a99f7..00000000 --- a/scripts/hostapd +++ /dev/null @@ -1,72 +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_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= -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 -[ -n "$DAEMON_CONF" ] || exit 0 - -DAEMON_OPTS="-B -P $PIDFILE $DAEMON_OPTS $DAEMON_CONF" - -. /lib/lsb/init-functions - -case "$1" in - 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 "$?" - ;; - 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" - log_end_msg "$?" - ;; - restart|force-reload) - $0 stop - sleep 8 - $0 start - ;; - status) - status_of_proc "$DAEMON_SBIN_1" "$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/pigpiod b/scripts/pigpiod deleted file mode 100755 index 408a9cc9..00000000 --- a/scripts/pigpiod +++ /dev/null @@ -1,58 +0,0 @@ -#!/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 -### END INIT INFO - -PATH='/sbin:/bin:/usr/bin' - -NAME=pigpiod -PIGPIOD=/usr/local/bin/pigpiod -FIND_PID="ps -eo pid,args | grep /usr/local/bin/pigpiod | awk '{print $1}'" - -RET=0 - -. /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 -} - -case "$1" in - start|reload|restart|force-reload) - kill_pid - log_daemon_msg "Starting PIGPIO Daemon" "pigpiod" - echo - $PIGPIOD - RET=$? - ;; - stop) - kill_pid - RET=$? - ;; - status) - ;; - *) - log_failure_msg "Usage: /etc/init.d/$NAME {start|stop|restart}" - RET=1 - ;; -esac - -exit $RET - -: 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/images/blocks/arm_lower.png b/static/images/blocks/arm_lower.png new file mode 100644 index 00000000..e1f85d4c Binary files /dev/null and b/static/images/blocks/arm_lower.png differ diff --git a/static/images/blocks/arm_raise.png b/static/images/blocks/arm_raise.png new file mode 100644 index 00000000..035f8356 Binary files /dev/null and b/static/images/blocks/arm_raise.png differ diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index dadc0a59..4ce4bf4e 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]; }; @@ -760,3 +760,248 @@ 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(290); + 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().arm_up();\n'; +}; + +Blockly.Python['coderbot_armRaise'] = function(block) { + // Generate Python code for raising the arm + return 'get_bot().arm_up()\n'; +}; + +Blockly.Blocks['coderbot_armLower'] = { + /** + * Block for armLower function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.CODERBOT_ARM_RAISE_HELPURL); + 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, '*')); + } 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().arm_down();\n'; +}; + +Blockly.Python['coderbot_armLower'] = function(block) { + // Generate Python code for lowering the arm + return 'get_bot().arm_down()\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"], ["blue", "Blue 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"], ["blue", "Blue 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"], ["blue", "Blue 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; +}; + +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 665dc0fc..f655a35b 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)"; @@ -38,11 +40,17 @@ 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"; 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" +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/static/js/coderbot.js b/static/js/coderbot.js index a28fa626..d943c111 100644 --- a/static/js/coderbot.js +++ b/static/js/coderbot.js @@ -51,16 +51,20 @@ CoderBot.prototype.stop = function() { } } +CoderBot.prototype.rotateCamera = function() { + this.command('rotate_camera',0); +} + 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 46bdc447..0c7b645c 100644 --- a/static/js/control.js +++ b/static/js/control.js @@ -78,11 +78,14 @@ $(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(); + bot.armDown(); }); $('#b_video_stop').on("click", function (){ - bot.videoStop(); + bot.armUp(); }); $('#b_photos').on("click", function (){ $.mobile.pageContainer.pagecontainer('change', '#page-photos'); @@ -111,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; }); @@ -154,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); diff --git a/templates/blocks_Picasso.xml b/templates/blocks_Picasso.xml new file mode 100644 index 00000000..d9c5d4c0 --- /dev/null +++ b/templates/blocks_Picasso.xml @@ -0,0 +1,151 @@ + diff --git a/templates/blocks_Pioneer.xml b/templates/blocks_Pioneer.xml new file mode 100644 index 00000000..62923a55 --- /dev/null +++ b/templates/blocks_Pioneer.xml @@ -0,0 +1,249 @@ + diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index eb4ccdfd..0f3516be 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -174,20 +174,6 @@ - - - - 10 - - - - - - - 90 - - - @@ -226,6 +212,10 @@ + + + + @@ -245,5 +235,23 @@ - + + + + + + 3 + + + + + + + + + + + + + diff --git a/templates/blocks_basic.xml b/templates/blocks_basic.xml index 36a57710..c709e3cc 100644 --- a/templates/blocks_basic.xml +++ b/templates/blocks_basic.xml @@ -3,6 +3,9 @@ + + + @@ -20,4 +23,13 @@ + + + + 3 + + + + + diff --git a/templates/blocks_basic_move.xml b/templates/blocks_basic_move.xml index 6b3ffc8d..0937011d 100644 --- a/templates/blocks_basic_move.xml +++ b/templates/blocks_basic_move.xml @@ -3,4 +3,15 @@ + + + + + + 3 + + + + + diff --git a/templates/blocks_std.xml b/templates/blocks_std.xml index 2f9b07a2..b0dfbe50 100644 --- a/templates/blocks_std.xml +++ b/templates/blocks_std.xml @@ -58,6 +58,10 @@ + + + + @@ -72,5 +76,23 @@ - + + + + + + 3 + + + + + + + + + + + + + diff --git a/templates/config.html b/templates/config.html index 923dd7df..25aab5e8 100644 --- a/templates/config.html +++ b/templates/config.html @@ -51,7 +51,11 @@

CoderBot

- + + + + +
{% trans %}Program editor details{% endtrans %} @@ -99,6 +103,13 @@

CoderBot

          + +
@@ -109,6 +120,10 @@

CoderBot

+ + + +
@@ -160,6 +175,8 @@

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

+ +
@@ -197,4 +214,15 @@

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

+ 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%}"; diff --git a/templates/control.html b/templates/control.html index 58d985f4..59596b68 100644 --- a/templates/control.html +++ b/templates/control.html @@ -40,12 +40,13 @@

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..0a261481 100644 --- a/viz/camera.py +++ b/viz/camera.py @@ -19,8 +19,9 @@ 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.out_rgb = picamera.array.PiRGBArray(self.camera, size=(640,480))#160,120 self.h264_encoder = None self.recording = None self.video_filename = None @@ -59,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() @@ -102,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) diff --git a/wifi.py b/wifi.py index aa9c2610..d880eeee 100755 --- a/wifi.py +++ b/wifi.py @@ -9,12 +9,17 @@ import fcntl import struct import json +import ConfigParser +import StringIO +from time import sleep class WiFi(): CONFIG_FILE = "/etc/coderbot_wifi.conf" - adapters = ["RT5370", "RTL8188CUS"] - hostapds = {"RT5370": "hostapd.RT5370", "RTL8188CUS": "hostapd.RTL8188"} + 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" wifi_client_conf_file = "/etc/wpa_supplicant/wpa_supplicant.conf" _config = {} @@ -45,24 +50,84 @@ 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") - + os.system("sudo service hostapd restart") except subprocess.CalledProcessError as e: print e.output @classmethod def stop_hostapd(cls): try: - out = subprocess.check_output(["pkill", "-9", "hostapd"]) - print out + os.system("sudo service 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 + + 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) @@ -86,50 +151,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("}") - - @classmethod - def set_start_as_client(cls): - shutil.copy("/etc/network/interfaces_cli", "/etc/network/interfaces") - cls._config["wifi_mode"] = "client" - cls.save_config() - - @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 - - @classmethod - def set_start_as_ap(cls): - shutil.copy("/etc/network/interfaces_ap", "/etc/network/interfaces") - 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() @@ -143,25 +164,56 @@ def start_service(cls): except: print "Unable to register ip, revert to ap mode" cls.start_as_ap() + 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(): w = WiFi() - if len(sys.argv) > 2 and sys.argv[1] == "updatecfg": - if len(sys.argv) > 2 and sys.argv[2] == "ap": - w.set_start_as_ap() - w.start_as_ap() - elif len(sys.argv) > 2 and sys.argv[2] == "client": - 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() - else: - w.start_service() + if len(sys.argv) < 2: + print 'Testing Client Connection...' + 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') + #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") + + 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()