Added entirety of CAD files, Software and the translated documentation
This commit is contained in:
178
02_Software/02_RaspberryPi/color_detection_v4.py
Normal file
178
02_Software/02_RaspberryPi/color_detection_v4.py
Normal file
@ -0,0 +1,178 @@
|
||||
# import the necessary package
|
||||
from picamera.array import PiRGBArray
|
||||
from picamera import PiCamera
|
||||
from image_process.colorlabler import ColorLabler
|
||||
from image_process.coordtransform import CoordinateTransform
|
||||
from time import sleep
|
||||
import argparse
|
||||
import imutils
|
||||
import numpy as np
|
||||
import cv2
|
||||
import math
|
||||
import time
|
||||
|
||||
|
||||
class DetectColor:
|
||||
#values for crop_image
|
||||
y = 130
|
||||
h = 210
|
||||
x = 140
|
||||
w = 340
|
||||
|
||||
radius = 86.4 #distance between robot center and camera center in mm
|
||||
z = 239 #distance between camera and tray in mm
|
||||
#camera information
|
||||
focallength = 3.04 #mm
|
||||
pixelsize = 0.00112 #mm
|
||||
|
||||
def __init__(self):
|
||||
# define the list of boundaries
|
||||
# blue (dark), red, yellow, cyan, green
|
||||
self.boundaries = [
|
||||
([17, 15, 100], [50, 56, 200]),
|
||||
([86, 31, 4], [220, 88, 50]),
|
||||
([204, 204, 50], [255, 255, 102]),
|
||||
([5, 200, 200], [125, 255, 255]),
|
||||
([100, 192, 0], [150, 255, 150])]
|
||||
#create Matrix to save position and color
|
||||
index = np.array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58])
|
||||
colorindex = np.full(59,-1, dtype=int)
|
||||
self.Matrix = np.column_stack((index,colorindex))
|
||||
xcoordinates = np.array([24.68,45.72,54.91,56.98,17.48,11.97,48.78,93.34,82.2,54.32,24.68,24.68,32.56,40.44,77.03,111.44,105.08,100.96,11.97,39.8,70.35,105.08,123.76,131.86,108.29,92.25,58.33,23.34,7.66,0,-24.68,-45.72,-54.91,-56.98,-17.48,-11.97,-48.78,-93.34,-82.2,-54.32,-24.68,-24.68,-32.56,-40.44,-77.03,-111.44,-105.08,-100.96,-11.97,-39.8,-70.35,-105.08,-123.76,-131.86,-108.29,-92.25,-58.33,-23.34,-7.66])
|
||||
ycoordinates = np.array([-61.88,-49.5,-24.16,19.79,59.11,-86.1,-79.83,-60.72,70.62,72.05,85.12,-106.88,-102.18,-97.47,-84.32,-39.84,-6.32,28.98,-130.68,-121.61,-110.27,-79.83,-12.15,11.8,52.11,90.76,109.74,109.74,130.48,0,61.88,49.5,24.16,-19.79,-59.11,86.1,79.83,60.72,-70.62,-72.05,-85.12,106.88,102.18,97.47,84.32,39.84,6.32,-28.98,130.68,121.61,110.27,79.83,12.15,-11.8,-52.11,-90.76,-109.74,-109.74,-130.48])
|
||||
self.Matrix = np.column_stack((self.Matrix,xcoordinates))
|
||||
self.Matrix = np.column_stack((self.Matrix,ycoordinates))
|
||||
|
||||
#camera settings
|
||||
self.camera = PiCamera()
|
||||
self.camera.resolution = (640,480)
|
||||
self.rawCapture = PiRGBArray(self.camera, size=(640,480))
|
||||
#self.rawCapture = PiRGBArray(self.camera)
|
||||
time.sleep(0.1)
|
||||
|
||||
def startDetection(self, angle):
|
||||
#take picture
|
||||
self.camera.capture(self.rawCapture, format="bgr")
|
||||
image = self.rawCapture.array
|
||||
#
|
||||
#
|
||||
#angle = 90
|
||||
#if angle > 360:
|
||||
# angle = angle - 360
|
||||
#
|
||||
#
|
||||
i = 1
|
||||
#crop the image
|
||||
crop_image = image[self.y : self.y + self.h, self.x : self.x + self.w]
|
||||
#crop_image = cv2.imread("/home/pi/Pictures/90.jpg")
|
||||
cv2.imwrite("original.png", crop_image)
|
||||
#
|
||||
#
|
||||
crop_imagehsv = cv2.cvtColor(crop_image,cv2.COLOR_BGR2HSV)
|
||||
lower_red1 = np.array([0,150,50])
|
||||
upper_red1 = np.array([10,255,255])
|
||||
lower_red2 = np.array([178,75,75])
|
||||
upper_red2 = np.array([179,255,255])
|
||||
lower_blue = np.array([110,100,100])
|
||||
upper_blue = np.array([130,255,255])
|
||||
lower_green = np.array([40,100,100])
|
||||
upper_green = np.array([70,255,255])
|
||||
lower_yellow = np.array([25,75,75])
|
||||
upper_yellow = np.array([30,255,255])
|
||||
lower_cyan = np.array([87,75,75])
|
||||
upper_cyan = np.array([91,255,255])
|
||||
#red
|
||||
mas1 = cv2.inRange(crop_imagehsv, lower_red1, upper_red1)
|
||||
cv2.imwrite("mas1.png", mas1)
|
||||
mas2 = cv2.inRange(crop_imagehsv, lower_red2, upper_red2)
|
||||
cv2.imwrite("mas2.png", mas2)
|
||||
#blue
|
||||
mas3 = cv2.inRange(crop_imagehsv, lower_blue, upper_blue)
|
||||
cv2.imwrite("mas3.png", mas3)
|
||||
#green
|
||||
mas4 = cv2.inRange(crop_imagehsv, lower_green, upper_green)
|
||||
cv2.imwrite("mas4.png", mas4)
|
||||
#yellow
|
||||
mas5 = cv2.inRange(crop_imagehsv, lower_yellow, upper_yellow)
|
||||
cv2.imwrite("mas5.png", mas5)
|
||||
#cyan
|
||||
mas6 = cv2.inRange(crop_imagehsv, lower_cyan, upper_cyan)
|
||||
cv2.imwrite("mas6.png", mas6)
|
||||
#
|
||||
mas = mas1 | mas2 | mas3 | mas4 | mas5 | mas6
|
||||
cv2.imwrite("HSV.png", mas)
|
||||
#
|
||||
|
||||
# loop over the boundarie
|
||||
for (lower, upper) in self.boundaries:
|
||||
# create NumPy arrays from the boundaries
|
||||
lower = np.array(lower, dtype = "uint8")
|
||||
upper = np.array(upper, dtype = "uint8")
|
||||
|
||||
# find the colors within the specified boundaries and apply
|
||||
# the mask
|
||||
mask = cv2.inRange(crop_image, lower, upper)
|
||||
if(i == 1):
|
||||
mask2 = mask | mas;
|
||||
i = 0;
|
||||
else:
|
||||
mask2 = mask2 | mask;
|
||||
outputall = cv2.bitwise_and(crop_image,crop_image,mask=mask2)
|
||||
|
||||
# show the images -> Debug
|
||||
#cv2.imshow("images", outputall)
|
||||
#cv2.waitKey(0)
|
||||
|
||||
|
||||
# load the image, convert it to grayscale, blur it slightly,
|
||||
# and threshold it
|
||||
cv2.imwrite("MaskAll.png", outputall)
|
||||
blurred = cv2.GaussianBlur(outputall, (5, 5), 0)
|
||||
gray = cv2.cvtColor(blurred, cv2.COLOR_BGR2GRAY)
|
||||
#cv2.imwrite("Graustufenbild.png", gray)
|
||||
|
||||
lab = cv2.cvtColor(blurred, cv2.COLOR_BGR2LAB)
|
||||
cv2.imwrite("lab.png", lab)
|
||||
thresh = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY)[1]
|
||||
|
||||
cv2.imwrite("thresh.png", thresh)
|
||||
#cv2.waitKey(0)
|
||||
|
||||
# find contours in the thresholded image
|
||||
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
|
||||
cv2.CHAIN_APPROX_SIMPLE)
|
||||
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
|
||||
#initialize color labeler
|
||||
cl = ColorLabler();
|
||||
tr = CoordinateTransform(self.h,self.w, self.radius, self.focallength, self.pixelsize, self.z);
|
||||
|
||||
# loop over the contours
|
||||
for c in cnts:
|
||||
print("---------------------------------------")
|
||||
temp = 10000
|
||||
saveindex = -1
|
||||
# compute the center of the contour
|
||||
M = cv2.moments(c)
|
||||
cX = int((M["m10"] / M["m00"]))
|
||||
cY = int((M["m01"] / M["m00"]))
|
||||
#label color
|
||||
color_name, colorId = cl.label(lab,c)
|
||||
#transform coordinate
|
||||
xnew, ynew = tr.transform(angle,cX,cY)
|
||||
print(cX, cY)
|
||||
print(xnew, ynew,math.sqrt((xnew-self.Matrix[0][2])**2+(ynew-self.Matrix[0][3])**2))
|
||||
for j in range(59):
|
||||
if (math.sqrt((xnew-self.Matrix[j][2])**2+(ynew-self.Matrix[j][3])**2)) < temp:
|
||||
temp = math.sqrt((xnew-self.Matrix[j][2])**2+(ynew-self.Matrix[j][3])**2)
|
||||
saveindex = self.Matrix[j][0]
|
||||
print(saveindex)
|
||||
#save colorid for selected placeId
|
||||
self.Matrix[int(saveindex)][1] = colorId
|
||||
print colorId
|
||||
|
||||
self.rawCapture.truncate(0)
|
||||
print self.Matrix
|
||||
|
||||
if __name__ == "__main__":
|
||||
ballColors = DetectColor()
|
||||
ballColors.startDetection(10)
|
80
02_Software/02_RaspberryPi/comtest.py
Normal file
80
02_Software/02_RaspberryPi/comtest.py
Normal file
@ -0,0 +1,80 @@
|
||||
# Creates instructions for the robot to move 360 degrees around, make photos of the ball-plate and write the ball_colors to the database
|
||||
|
||||
import MySQLdb
|
||||
import random
|
||||
import serial
|
||||
import time
|
||||
from struct import pack
|
||||
|
||||
def sendBin( dec ):
|
||||
x = pack('i', dec)
|
||||
ser.write(x[1])
|
||||
ser.write(x[0])
|
||||
|
||||
ser = serial.Serial('/dev/ttyACM0', 9600) # Change name if necessary. Find out name with ls /dev/tty* (1x without, 1x with USB connected)
|
||||
time.sleep(2) # Arduino resets, so wait for some time
|
||||
|
||||
# Create instructions to turn around
|
||||
signature = chr(0b01010010) # from raspberry
|
||||
priority0 = chr(0b00000000) # add to end of queue
|
||||
priority1 = chr(0b00000001)
|
||||
moveJ = chr(0b00000001) # binary 1
|
||||
moveA = chr(0b00000100) # binary 4
|
||||
pause = chr(0b00000111) # binary 7
|
||||
dontChange = chr(0b01111111) + chr(0b11111111)
|
||||
eot = chr(0b11111111) + chr(0b11111111) # end of transmission
|
||||
magOff = chr(0b00000000)
|
||||
magOn = chr(0b00000001)
|
||||
|
||||
# a, r, h
|
||||
if 1 == 1:
|
||||
msg = signature + priority0 + moveJ
|
||||
ser.write(msg)
|
||||
sendBin(120)
|
||||
sendBin(100)
|
||||
sendBin(10)
|
||||
ser.write(eot)
|
||||
|
||||
msg = signature + priority0 + pause
|
||||
ser.write(msg)
|
||||
sendBin(5000)
|
||||
ser.write(eot)
|
||||
|
||||
msg = signature + priority0 + moveJ
|
||||
ser.write(msg)
|
||||
sendBin(80)
|
||||
sendBin(110)
|
||||
sendBin(20)
|
||||
ser.write(eot)
|
||||
|
||||
if 1 == 0:
|
||||
msg = signature + priority0 + moveA
|
||||
ser.write(msg)
|
||||
sendBin(50)
|
||||
sendBin(128)
|
||||
sendBin(90)
|
||||
msg = magOff + eot
|
||||
ser.write(msg)
|
||||
|
||||
msg = signature + priority0 + moveA
|
||||
ser.write(msg)
|
||||
sendBin(30)
|
||||
sendBin(180)
|
||||
sendBin(100)
|
||||
msg = magOff + eot
|
||||
ser.write(msg)
|
||||
|
||||
msg = signature + priority1 + pause
|
||||
ser.write(msg)
|
||||
sendBin(2000)
|
||||
ser.write(eot)
|
||||
|
||||
while True:
|
||||
print(ser.readline())
|
||||
|
||||
ser.close() # close serial connection
|
||||
|
||||
# For debugging
|
||||
#file = open("testfile.txt", "w")
|
||||
#file.write("update")
|
||||
#file.close
|
180
02_Software/02_RaspberryPi/create_instructions.py
Normal file
180
02_Software/02_RaspberryPi/create_instructions.py
Normal file
@ -0,0 +1,180 @@
|
||||
import math
|
||||
import sys
|
||||
import serial
|
||||
import time
|
||||
from struct import pack
|
||||
|
||||
# Transforming x, y, z to r, a, h
|
||||
def xyz2arh( x, y, z ):
|
||||
r = int(round(math.sqrt(pow(x, 2) + pow(y, 2))))
|
||||
a = int(round(math.degrees(math.atan2(y, x))))
|
||||
# atan2 gives negative results for -y --> subtract it from 360
|
||||
if a < 0:
|
||||
a = 360 + a
|
||||
print a
|
||||
h = z
|
||||
return [a, r, h]
|
||||
|
||||
def xyz2a( x, y ):
|
||||
a = int(round(math.degrees(math.atan2(y, x))))
|
||||
# atan2 gives negative results for -y --> subtract it from 360
|
||||
if a < 0:
|
||||
a = 360 + a
|
||||
return a
|
||||
|
||||
def xyz2r( x, y ):
|
||||
r = int(round(math.sqrt(pow(x, 2) + pow(y, 2))))
|
||||
return r
|
||||
|
||||
def xyz2h( z ):
|
||||
h = z
|
||||
return h
|
||||
|
||||
def sendBin( dec , ser):
|
||||
print ("SENT POSITION: " + str(dec))
|
||||
x = pack('i', dec)
|
||||
ser.write(x[1])
|
||||
ser.write(x[0])
|
||||
|
||||
def sendPositions( pos , ser):
|
||||
sendBin(pos[0], ser)
|
||||
sendBin(pos[1], ser)
|
||||
sendBin(pos[2], ser)
|
||||
|
||||
# Calculate the shelf-number (1. digit of track)
|
||||
def shelfNr( track ):
|
||||
return int(track/10)
|
||||
|
||||
# Calculate the track-number (2. digit of track)
|
||||
def trackNr( track ):
|
||||
return track % 10
|
||||
|
||||
# Create and send instructions to move a ball from given x, y to track
|
||||
def create_instr( x, y, track):
|
||||
ser = serial.Serial(port='/dev/ttyACM0', # Change name if necessary. Find out name with ls /dev/tty* (1x without, 1x with USB connected)
|
||||
baudrate=9600,
|
||||
parity=serial.PARITY_NONE,
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
timeout=1)
|
||||
time.sleep(1) # TODO DELETE Arduino resets, so wait for some time
|
||||
|
||||
signature = chr(0b01010010) # from raspberry
|
||||
priority = chr(0b00000000) # add to end of queue
|
||||
moveJ = chr(0b00000001) # binary 1
|
||||
moveL = chr(0b00000010) # binary 2
|
||||
moveL = moveJ
|
||||
moveA = chr(0b00000100) # binary 4
|
||||
pause = chr(0b00000111) # binary 7
|
||||
dontChange = 32767
|
||||
eot = chr(0b11111110) + chr(0b11111110) # end of transmission
|
||||
magOn = chr(0b00000001)
|
||||
magOff = chr(0b00000000)
|
||||
|
||||
heights = (80, 258, 187, 56) # heights of shelfs in mm (1. = Vorposition-Hoehe; 2. = Unterstes Brett Hoehe)
|
||||
angles = (90, 63, 55, 47, 40, 32, 0) # angles of tracks in degrees + 0/6 = prePos
|
||||
trackPosR = (170, 160, 173, 170) # radius of final pos in track in mm (2. = Oberste)
|
||||
trackPrePosR = (trackPosR[0]-10, trackPosR[1]-10, trackPosR[2]-10, trackPosR[3]-10) # radius of pre pos of track
|
||||
trackPreShelfR = trackPrePosR # radius of pre pos of shelf
|
||||
shelfPrePosR = 155 # save bottom-position
|
||||
shelfPrePosHOffset = 5
|
||||
ballPosPreZOffset = 40 # height of pre pos of ballpos in mm (above the ball)
|
||||
ballPosFinH = 10
|
||||
magnetPauseTime = 1000
|
||||
speedSlow = 20
|
||||
speedFast = 50
|
||||
|
||||
higherOffsetBalls = [[-17.48, -59.11], # 34
|
||||
[-54.32, -72.05], # 39
|
||||
[-24.68, -85.12], # 40
|
||||
[-58.33, -109.74]] # 56
|
||||
higherhigherOffsetBalls = [[-131.86, -11.8]] # 53
|
||||
|
||||
# Create header
|
||||
header = signature + priority
|
||||
|
||||
# Transform the given arguments so the program can work with them
|
||||
x, y = y, x # change x and y to suit robot coordinate system
|
||||
x = float(x)
|
||||
y = float(y)
|
||||
track = int(track)
|
||||
|
||||
#for i in range(len(higherOffsetBalls)):
|
||||
# if x == higherOffsetBalls[i][1] and y == higherOffsetBalls[i][0]:
|
||||
# ballPosFinH = 13
|
||||
# break
|
||||
|
||||
#for i in range(len(higherhigherOffsetBalls)):
|
||||
# if x == higherhigherOffsetBalls[i][1] and y == higherhigherOffsetBalls[i][0]:
|
||||
# ballPosFinH = 15
|
||||
# break
|
||||
|
||||
actShelfNr = shelfNr(track)
|
||||
instr =[[moveJ, xyz2a(x, y), xyz2r(x, y), xyz2h(ballPosPreZOffset), speedFast], # To Ball-Pre-Position
|
||||
[moveL, xyz2a(x, y), xyz2r(x, y), xyz2h(ballPosFinH), speedSlow], # To Ball-Position
|
||||
[moveA, dontChange, dontChange, dontChange, magOn], # Switch on Magnet
|
||||
[pause, magnetPauseTime],
|
||||
[moveL, xyz2a(x, y), xyz2r(x, y), xyz2h(ballPosPreZOffset), speedSlow], # To Ball-Pre-Position again
|
||||
[moveJ, angles[0], shelfPrePosR, heights[0], speedFast], # To Shelf-Pre-Pre-Position
|
||||
# TODO Left or right pre-position?
|
||||
[moveJ, angles[0], trackPreShelfR[actShelfNr], heights[actShelfNr]+shelfPrePosHOffset, speedFast], # To Shelf-Pre-Position
|
||||
[moveJ, angles[trackNr(track)], trackPrePosR[actShelfNr], heights[actShelfNr]+shelfPrePosHOffset, speedSlow], # To Track-Pre-Position
|
||||
[moveL, angles[trackNr(track)], trackPosR[actShelfNr], heights[actShelfNr], speedSlow], # To Track-Goal-Position
|
||||
[moveA, dontChange, dontChange, dontChange, magOff], # Switch off Magnet
|
||||
[pause, magnetPauseTime],
|
||||
[moveL, angles[trackNr(track)], trackPrePosR[actShelfNr], heights[actShelfNr]+shelfPrePosHOffset, speedSlow], # To Track-Pre-Position
|
||||
[moveJ, angles[0], trackPreShelfR[actShelfNr], heights[actShelfNr]+shelfPrePosHOffset, speedSlow], # To Shelf-Pre-Position
|
||||
[moveJ, angles[0], shelfPrePosR, heights[0], speedFast] # To Shelf-Pre-Pre-Position
|
||||
]
|
||||
|
||||
# MoveJ, MoveL: A, R, H
|
||||
# MoveA: R1, P, R2
|
||||
|
||||
# TODO: Is robot in safe mode?
|
||||
|
||||
# Send instructions
|
||||
#time.sleep(5)
|
||||
|
||||
for i in range(len(instr)):
|
||||
ser.write(header)
|
||||
ser.write(instr[i][0]) # send move-type
|
||||
|
||||
if instr[i][0] == pause:
|
||||
sendBin(instr[i][1], ser)
|
||||
if instr[i][0] == moveJ or instr[i][0] == moveL:
|
||||
sendBin(instr[i][1], ser)
|
||||
sendBin(instr[i][2], ser)
|
||||
sendBin(instr[i][3], ser)
|
||||
#sendBin(instr[i][4], ser) # send speed
|
||||
if instr[i][0] == moveA:
|
||||
sendBin(instr[i][1], ser)
|
||||
sendBin(instr[i][2], ser)
|
||||
sendBin(instr[i][3], ser)
|
||||
ser.write(instr[i][4]) # send magOn / magOff
|
||||
|
||||
ser.write(eot)
|
||||
|
||||
# DEBUG
|
||||
print("SENT INSTR" + str(i))
|
||||
time.sleep(1.0)
|
||||
|
||||
# Debug: Send pause
|
||||
# ser.write(header)
|
||||
# ser.write(pause)
|
||||
# sendBin(1500,ser)
|
||||
# ser.write(eot)
|
||||
|
||||
while ser.inWaiting():
|
||||
print(ser.readline())
|
||||
|
||||
while 1 == 1:
|
||||
print(ser.readline())
|
||||
|
||||
ser.close() # close serial connection
|
||||
# Only for debugging
|
||||
#file = open("testfile.txt", "w")
|
||||
#file.write(str(x) + str(y) + str(track));
|
||||
|
||||
# Get the information from php with arguments -> if the file create_instructions is called directly with arguments
|
||||
if __name__ == "__main__":
|
||||
create_instr(sys.argv[1], sys.argv[2], sys.argv[3])
|
17
02_Software/02_RaspberryPi/db.inc.php
Normal file
17
02_Software/02_RaspberryPi/db.inc.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
$host = "localhost";
|
||||
$username = "root";
|
||||
|
||||
$password = "root";
|
||||
|
||||
$dbname = "rpr_robot";
|
||||
$result_array = array();
|
||||
|
||||
/* Create connection */
|
||||
$conn = new mysqli ($host, $username, $password, $dbname);
|
||||
if ($conn->connect_error) {
|
||||
die("Connection failed: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
?>
|
194
02_Software/02_RaspberryPi/handleBalls.js
Normal file
194
02_Software/02_RaspberryPi/handleBalls.js
Normal file
@ -0,0 +1,194 @@
|
||||
var actPrio = 0;
|
||||
var numBallsPlaced = new Array();
|
||||
for (var i = 0; i < 55; i++) {numBallsPlaced[i] = 0;}
|
||||
|
||||
$( function() {
|
||||
// Let the plate items be draggable
|
||||
$( "li", "#ballBox" ).draggable({
|
||||
revert: "invalid",
|
||||
containment: "document",
|
||||
helper: "clone",
|
||||
cursor: "move"
|
||||
});
|
||||
|
||||
refreshPlate();
|
||||
refreshShelf();
|
||||
});
|
||||
|
||||
// Send request for scanning of new balls and writing the data to the database
|
||||
function updateBallPos() {
|
||||
if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();
|
||||
} else {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
|
||||
xmlhttp.open("GET","updateBallDatabase.php?q=5",true);
|
||||
xmlhttp.send();
|
||||
|
||||
document.getElementById('unclickableCircle').style.visibility = "visible";
|
||||
document.getElementById('stareBalls').style.visibility = "visible";
|
||||
|
||||
setTimeout(function(){
|
||||
document.getElementById('stareBalls').style.visibility = "hidden";
|
||||
document.getElementById('unclickableCircle').style.visibility = "hidden";
|
||||
refreshPlate();
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
// Loads all balls from the database and displays them on the plate
|
||||
function refreshPlate() {
|
||||
var myObj, x, txt = "", offsetX, offsetY, scale;
|
||||
|
||||
document.getElementById("ballBox").innerHTML = ""; // Clear plate first
|
||||
|
||||
if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();
|
||||
} else {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
myObj = JSON.parse(this.responseText);
|
||||
offsetX = 300+51;
|
||||
offsetY = 300+51;
|
||||
scale = 2.62;
|
||||
for (x in myObj) {
|
||||
// Calculate ball-position on plate in website
|
||||
myObj[x].pos_x = offsetX + myObj[x].pos_x * scale;
|
||||
myObj[x].pos_y = offsetY + myObj[x].pos_y * scale;
|
||||
$("<div id='" + myObj[x].pos_id + "' class='ball' style='top: " + myObj[x].pos_y + "px; left: " + myObj[x].pos_x +"px; background-color: " + myObj[x].ball_color + ";'></div>")
|
||||
.appendTo('#ballBox').attr('ballID', myObj[x].pos_id).draggable({
|
||||
revert: "invalid",
|
||||
containment: "document",
|
||||
});
|
||||
document.getElementById(myObj[x].pos_id).style.cursor = "grab";
|
||||
}
|
||||
}
|
||||
};
|
||||
xmlhttp.open("GET","updateBallDatabase.php?q=0",true);
|
||||
xmlhttp.send();
|
||||
}
|
||||
|
||||
// Loads all balls from the database and displays them in the shelf
|
||||
function refreshShelf() {
|
||||
var myObj, x;
|
||||
var id = 0;
|
||||
|
||||
document.getElementById("shelfBox").innerHTML = ""; // Clear shelf first
|
||||
for (var i = 0; i < 35; i++) {numBallsPlaced[i] = 0;} // Init array on refresh
|
||||
for (var i = 10; i < 35; i++) {
|
||||
if(i%5 == 0 && i%10 != 0) { // Display 5 traces per shelf
|
||||
i = i + 5;
|
||||
$('<br>').appendTo('#shelfBox');
|
||||
}
|
||||
id = i + 1;
|
||||
$('<div class="shelf" id="shelf' + id + '" ></div>').data('number', i).attr('section', id).appendTo('#shelfBox').droppable({
|
||||
accept: "#ballBox > div",
|
||||
classes: { "ui-droppable-active": "ui-state-highlight" },
|
||||
drop: handleBallDrop
|
||||
});
|
||||
}
|
||||
|
||||
if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();
|
||||
} else {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
myObj = JSON.parse(this.responseText);
|
||||
for (x in myObj) {
|
||||
$("<div id='" + myObj[x].pos_id + "' class='ball' style='position: relative; left: 13px; background-color: " + myObj[x].ball_color + ";'></div>")
|
||||
.appendTo('#shelf'+ myObj[x].ball_targetPos + '').attr('ballID', myObj[x].pos_id);
|
||||
numBallsPlaced[myObj[x].ball_targetPos]++;
|
||||
if(numBallsPlaced[myObj[x].ball_targetPos] >= 5) {
|
||||
$("#shelf" + myObj[x].ball_targetPos).droppable({accept: "",}); //Prevent balls from being dropped if shelf is already full
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xmlhttp.open("GET","updateBallDatabase.php?q=3",true);
|
||||
xmlhttp.send();
|
||||
}
|
||||
|
||||
// Puts all balls from the shelf back on the plate (in the database)
|
||||
function emptyShelf(str) {
|
||||
if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();
|
||||
} else {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
|
||||
xmlhttp.open("GET","updateBallDatabase.php?q=2",true);
|
||||
xmlhttp.send();
|
||||
|
||||
var i = 0;
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
i++;
|
||||
if (i == 3) {
|
||||
refreshShelf();
|
||||
refreshPlate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If a ball is dropped, it is replaced and that is wirtten to the database
|
||||
function handleBallDrop(event, ui) {
|
||||
$item = ui.draggable;
|
||||
var ballID = $item.attr('ballID');
|
||||
var dropID = $(this).attr('section');
|
||||
var live = 0;
|
||||
|
||||
//if (dropID > 10 && dropID < 20) dropID + 20;
|
||||
//if (dropID > 30 && dropID < 40) dropID - 20;
|
||||
|
||||
// Drop ball and change behaviour and style
|
||||
$item.appendTo(this).fadeIn();
|
||||
ui.draggable.draggable('disable');
|
||||
document.getElementById(ballID).style.cursor = "context-menu";
|
||||
document.getElementById(ballID).style.position = "relative";
|
||||
document.getElementById(ballID).style.top = "0px";
|
||||
document.getElementById(ballID).style.left = "13px";
|
||||
|
||||
numBallsPlaced[dropID] = numBallsPlaced[dropID] + 1;
|
||||
|
||||
if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();
|
||||
} else {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
|
||||
|
||||
if (document.getElementById("myonoffswitch").checked == true) live = 1;
|
||||
|
||||
xmlhttp.open("GET","updateBallDatabase.php?q=1&ballID=" + ballID + "&shelfID=" + dropID + "&prio=" + actPrio + "&live=" + live,true);
|
||||
actPrio++;
|
||||
if(numBallsPlaced[dropID] >= 5) {
|
||||
$("#shelf" + dropID).droppable({accept: "",}); //Prevent balls from being dropped if shelf is already full
|
||||
}
|
||||
xmlhttp.send();
|
||||
document.getElementById('unclickableCircle').style.visibility = "visible";
|
||||
document.getElementById('playBalls').style.visibility = "visible";
|
||||
setTimeout(function(){
|
||||
document.getElementById('unclickableCircle').style.visibility = "hidden";
|
||||
document.getElementById('playBalls').style.visibility = "hidden";
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
function autoSort(){
|
||||
emptyShelf();
|
||||
|
||||
setTimeout(function(){
|
||||
if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();
|
||||
} else {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
|
||||
xmlhttp.open("GET","updateBallDatabase.php?q=4",true);
|
||||
xmlhttp.send();
|
||||
|
||||
document.getElementById('unclickableCircle').style.visibility = "visible";
|
||||
document.getElementById('playBalls').style.visibility = "visible";
|
||||
|
||||
var i = 0;
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
i++;
|
||||
if (i == 3){
|
||||
refreshPlate();
|
||||
refreshShelf();
|
||||
document.getElementById('unclickableCircle').style.visibility = "hidden";
|
||||
document.getElementById('playBalls').style.visibility = "hidden";
|
||||
//location.reload();
|
||||
//console.log("A");
|
||||
}
|
||||
};
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function startSorting(){
|
||||
if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();
|
||||
} else {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
|
||||
xmlhttp.open("GET","updateBallDatabase.php?q=6",true);
|
||||
xmlhttp.send();
|
||||
alert("Your data has been sent successfully. The robot will start sorting soon.");
|
||||
}
|
73
02_Software/02_RaspberryPi/image_process/colorlabler.py
Normal file
73
02_Software/02_RaspberryPi/image_process/colorlabler.py
Normal file
@ -0,0 +1,73 @@
|
||||
from scipy.spatial import distance as dist
|
||||
from collections import OrderedDict
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
class ColorLabler:
|
||||
def __init__(self):
|
||||
# initialize the colors dictionary, containing the color
|
||||
# name as the key and the RGB tuple as the value
|
||||
# colors = ({
|
||||
# "Blue": (33.5, 35.5, 150),
|
||||
# "Red": (153, 59.5, 27),
|
||||
# "Yellow": (206, 217.5, 50),
|
||||
# "Cyan": (43, 255, 255),
|
||||
# "Green": (61, 165, 85)})
|
||||
colors = ({
|
||||
"Blue": (28, 70, 152),
|
||||
"Red": (126, 34, 47),
|
||||
"Yellow": (86, 83, 42),
|
||||
"Cyan": (109, 131, 180),
|
||||
"Green": (2, 77, 54)})
|
||||
|
||||
# allocate memory for the L*a*b* image, then initialize
|
||||
# the color names list
|
||||
self.lab = np.zeros((len(colors), 1, 3), dtype = "uint8")
|
||||
self.colorNames = []
|
||||
|
||||
# loop over the colors dictionary
|
||||
for (i, (name,rgb)) in enumerate(colors.items()):
|
||||
# update the L*a*b* array and the color names list
|
||||
self.lab[i] = rgb
|
||||
self.colorNames.append(name)
|
||||
# convert the L*a*b* array from the RGB color space
|
||||
# to L*a*b*
|
||||
self.lab = cv2.cvtColor(self.lab, cv2.COLOR_RGB2LAB)
|
||||
|
||||
def label(self, image, c):
|
||||
# construct a mask for the contour, then compute the
|
||||
# average L*a*b* value for the masked region
|
||||
mask = np.zeros(image.shape[:2], dtype = "uint8")
|
||||
cv2.drawContours(mask, [c], -1, 255, -1)
|
||||
#cv2.imwrite("mask1.png",mask)
|
||||
mask = cv2.erode(mask, None, iterations = 2)
|
||||
#cv2.imwrite("mask2.png", mask)
|
||||
mean = cv2.mean(image, mask = mask)[:3]
|
||||
|
||||
# initialize the minimum distance found thus far
|
||||
minDist = (np.inf, None)
|
||||
|
||||
# loop over the known L*a*b* color values
|
||||
for (i, row) in enumerate(self.lab):
|
||||
# compute the distance between the current L*a*b*
|
||||
# color value and the mean of the image
|
||||
d = dist.euclidean(row[0], mean)
|
||||
# if the distance is smaller than the current distance,
|
||||
# then update the bookkeeping variable
|
||||
if d < minDist[0]:
|
||||
minDist = (d, i)
|
||||
|
||||
if self.colorNames[minDist[1]] == "Blue":
|
||||
color_index = 0
|
||||
elif self.colorNames[minDist[1]] == "Red":
|
||||
color_index = 1
|
||||
elif self.colorNames[minDist[1]] == "Yellow":
|
||||
color_index = 2
|
||||
elif self.colorNames[minDist[1]] == "Cyan":
|
||||
color_index = 3
|
||||
elif self.colorNames[minDist[1]] == "Green":
|
||||
color_index = 4
|
||||
|
||||
# return the name of the color with the smallest distance
|
||||
return self.colorNames[minDist[1]], color_index
|
109
02_Software/02_RaspberryPi/image_process/coordtransform.py
Normal file
109
02_Software/02_RaspberryPi/image_process/coordtransform.py
Normal file
@ -0,0 +1,109 @@
|
||||
import numpy as np
|
||||
import cv2
|
||||
import math
|
||||
|
||||
|
||||
class CoordinateTransform:
|
||||
def __init__(self,h,w,Radius,foc,pixsize,z):
|
||||
self.w = w
|
||||
self.h = h
|
||||
#a = np.array([[w],[h],[1]])
|
||||
#self.high = z;
|
||||
self.R = Radius #mm
|
||||
#focallengthinpixel=float(foc)/pixsize
|
||||
#self.inverseCalib_matrix = np.array([[float(1)/focallengthinpixel,0,0],[0,float(1)/focallengthinpixel,0],[0,0,1]])
|
||||
#print(a)
|
||||
#b = np.dot(self.inverseCalib_matrix,a)
|
||||
#print(b)
|
||||
#b = b*z
|
||||
#inverse calibration matrix without cx and cy
|
||||
self.xresolution = 156
|
||||
self.yresolution = 96
|
||||
#self.xresolution = b[0][0]
|
||||
#self.yresolution = b[1][0]
|
||||
#print(b)
|
||||
#print(self.xresolution)
|
||||
#print(self.yresolution)
|
||||
|
||||
def transform(self, angle, x, y):
|
||||
angledeg = angle
|
||||
anglerad = float(angle*math.pi)/180
|
||||
beta = (math.pi/2) - anglerad
|
||||
|
||||
Rx = self.R*math.cos(anglerad)
|
||||
Ry = self.R*math.sin(anglerad)
|
||||
|
||||
OldPositioninPixel = np.array([[x],[y],[1]])
|
||||
#print(self.xresolution, self.w,x)
|
||||
m = float(self.xresolution*x)/self.w
|
||||
n = float(self.yresolution*y)/self.h
|
||||
OldPosition = np.array([[m],[n],[1.0]])
|
||||
#print(OldPositioninPixel)
|
||||
#OldPosition = np.dot(self.inverseCalib_matrix,OldPositioninPixel)
|
||||
#print(OldPosition)
|
||||
#OldPosition = OldPosition * self.high
|
||||
#print(OldPosition)
|
||||
|
||||
|
||||
Rotate = np.array([[math.cos(beta),-math.sin(beta),0],[math.sin(beta),math.cos(beta),0],[0,0,1]]) #rotate coordinate
|
||||
Translation1 = np.array([[1,0,Rx],[0,1,-Ry],[0,0,1]]) #transform to camera center
|
||||
|
||||
if angledeg < 90 and angledeg > 0:
|
||||
gamma = (math.pi/2) - anglerad
|
||||
k = (float(self.xresolution)/2) - (float(self.yresolution)/2)*math.tan(gamma)
|
||||
l = (float(self.yresolution)/2)/math.cos(gamma)
|
||||
TranslationQ1 = np.array([[1,0,-(math.cos(gamma)*k)],[0,1,-(math.sin(gamma)*k+l)],[0,0,1]])
|
||||
M1 = np.dot(TranslationQ1,Translation1)
|
||||
#print(TranslationQuadrant1)
|
||||
#print(Translation1)
|
||||
#print(M1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
#print(M2)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
print(M3)
|
||||
if angledeg > 90 and angledeg < 180:
|
||||
gamma = (math.pi) - anglerad
|
||||
k = (float(self.xresolution)/2) - (float(self.yresolution)/2)*math.tan(gamma)
|
||||
l = (float(self.yresolution)/2)/math.cos(gamma)
|
||||
TranslationQ2 = np.array([[1,0,-(math.cos(gamma)*k+l)],[0,1,-(math.sin(gamma)*k)],[0,0,1]])
|
||||
M1 = np.dot(TranslationQ2,Translation1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
if angledeg > 180 and angledeg < 270:
|
||||
gamma = ((3*math.pi)/2) - anglerad
|
||||
k = (float(self.xresolution)/2) - (float(self.yresolution)/2)*math.tan(gamma)
|
||||
l = (float(self.yresolution)/2)/math.cos(gamma)
|
||||
TranslationQ3 = np.array([[1,0,math.cos(gamma)*k],[0,1,math.sin(gamma)*k+l],[0,0,1]])
|
||||
M1 = np.dot(TranslationQ3,Translation1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
if angledeg > 270 and angledeg < 360:
|
||||
gamma = (2*math.pi) - anglerad
|
||||
k = (float(self.xresolution)/2) - (float(self.yresolution)/2)*math.tan(gamma)
|
||||
l = (float(self.yresolution)/2)/math.cos(gamma)
|
||||
TranslationQ4 = np.array([[1,0,math.sin(gamma)*k+l],[0,1,-(math.cos(gamma)*k)],[0,0,1]])
|
||||
M1 = np.dot(TranslationQ4,Translation1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
if angledeg == 0:
|
||||
TranslationA0 = np.array([[1,0,-(float(self.xresolution)/2)],[0,1,-(float(self.yresolution)/2)],[0,0,1]])
|
||||
M1 = np.dot(TranslationA0,Translation1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
if angledeg == 90:
|
||||
TranslationA90= np.array([[1,0,-(float(self.xresolution)/2)],[0,1,-(float(self.yresolution)/2)],[0,0,1]])
|
||||
M1 = np.dot(TranslationA90,Translation1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
if angledeg == 180:
|
||||
TranslationA180= np.array([[1,0,-(float(self.yresolution)/2)],[0,1,(float(self.xresolution)/2)],[0,0,1]])
|
||||
M1 = np.dot(TranslationA180,Translation1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
if angledeg == 270:
|
||||
TranslationA270= np.array([[1,0,(float(self.xresolution)/2)],[0,1,(float(self.yresolution)/2)],[0,0,1]])
|
||||
M1 = np.dot(TranslationA270,Translation1)
|
||||
M2 = np.dot(M1,Rotate)
|
||||
M3 = np.dot(M2,OldPosition)
|
||||
# return position with base orientation
|
||||
return M3[0][0], M3[1][0]
|
BIN
02_Software/02_RaspberryPi/img/Aufnahmeplatte.png
Normal file
BIN
02_Software/02_RaspberryPi/img/Aufnahmeplatte.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
BIN
02_Software/02_RaspberryPi/img/Aufnahmeplatte_Positons.png
Normal file
BIN
02_Software/02_RaspberryPi/img/Aufnahmeplatte_Positons.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 92 KiB |
BIN
02_Software/02_RaspberryPi/img/Shelf.png
Normal file
BIN
02_Software/02_RaspberryPi/img/Shelf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
35
02_Software/02_RaspberryPi/readBalls.py
Normal file
35
02_Software/02_RaspberryPi/readBalls.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Reads the positions of all sorted balls from the database and calls create_instr so the robot sorts them
|
||||
|
||||
import MySQLdb
|
||||
from create_instructions import create_instr
|
||||
|
||||
db = MySQLdb.connect("localhost","root","root","rpr_robot" ) # Open database connection
|
||||
cursor = db.cursor() # Prepare a cursor object using cursor() method
|
||||
|
||||
# Write the sql-query for later proceeding
|
||||
# Get the initial and final position as well as the id of all balls which are placed and not sorted by the robot yet, ordered by the priority ascending
|
||||
sql = "SELECT pos_x, pos_y, ball_targetPos, pos_id FROM ball_positions WHERE ball_targetPos!=0 AND sorted_by_robot=0 ORDER BY ball_prio ASC"
|
||||
|
||||
try:
|
||||
cursor.execute(sql) # Execute the SQL command
|
||||
results = cursor.fetchall() # Fetch all the rows
|
||||
for row in results:
|
||||
posX = row[0]
|
||||
posY = row[1]
|
||||
ball_targetPos = row[2]
|
||||
id = row[3]
|
||||
print ball_targetPos
|
||||
create_instr(posX, posY, ball_targetPos) # Create movement-instructions for every sorted ball
|
||||
sql = "UPDATE ball_positions SET sorted_by_robot=1 WHERE pos_id = %i" % (id) # Write to database, that the robot moved this ball in real life
|
||||
cursor.execute(sql) # Execute the SQL command
|
||||
db.commit() # Commit your changes in the database
|
||||
except:
|
||||
print "Error: unable to fetch data"
|
||||
db.rollback() # Rollback in case there is any error
|
||||
|
||||
db.close() # disconnect from server
|
||||
|
||||
# For debugging
|
||||
file = open("testfile.txt", "w")
|
||||
file.write("readBalls")
|
||||
file.close
|
63
02_Software/02_RaspberryPi/shelfRobot.html
Normal file
63
02_Software/02_RaspberryPi/shelfRobot.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="stylesheet" type="text/css" href="style_button.css">
|
||||
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
|
||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||
<script src="handleBalls.js"></script>
|
||||
<script>
|
||||
function checkBoxAutoSort() {
|
||||
/*if (document.getElementById("myonoffswitch").checked == false){
|
||||
document.getElementById("autosortButton").disabled = false;
|
||||
document.getElementById("startSortingButton").disabled = false;
|
||||
} else {
|
||||
document.getElementById("autosortButton").disabled = true;
|
||||
document.getElementById("startSortingButton").disabled = true;
|
||||
}*/
|
||||
alert("You have to buy a premium-account to switch Live-Mode off")
|
||||
//$("#myonoffswitch").prop("checked", true);
|
||||
//$("#myonoffswitch").attr("checked", true);
|
||||
document.getElementById("myonoffswitch").checked = true;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg" id="playBalls" style="visibility: hidden;">
|
||||
Please wait<br>while I'm playing<br>with your balls!
|
||||
</div>
|
||||
<div class="msg" id="stareBalls" style="visibility: hidden;">
|
||||
Please wait while I'm having an intense look at your balls!
|
||||
</div>
|
||||
<div id="nav">
|
||||
<form>
|
||||
<div id="navL">
|
||||
<button type="button" name="empty" class="button" onclick="emptyShelf(this.value)">Empty Shelf</button> <br>
|
||||
<button type="button" name="refresh" class="button" onclick="updateBallPos()">Refresh Positions</button> <br>
|
||||
</div>
|
||||
<div id="navM">
|
||||
<span style="margin-left: 8px; font-size: 16px;"> Live-Sort </span>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch" onclick="checkBoxAutoSort()" checked>
|
||||
<label class="onoffswitch-label" for="myonoffswitch">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="navR">
|
||||
<button type="button" name="autosort" class="button" onclick="autoSort()" id="autosortButton" disabled="">Auto Sort</button>
|
||||
<button type="button" name="start" class="button" onclick="startSorting()" id="startSortingButton" disabled="">Start Sorting</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="unclickableCircle" style="visibility: hidden;"></div>
|
||||
<div id="ballBox">
|
||||
<!-- Balls on Plate are created dynamically -->
|
||||
</div>
|
||||
<div id="shelfBox" class="ui-widget-content ui-state-default">
|
||||
<!-- Balls in Shelf are created dynamically -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
114
02_Software/02_RaspberryPi/style.css
Normal file
114
02_Software/02_RaspberryPi/style.css
Normal file
@ -0,0 +1,114 @@
|
||||
body {
|
||||
background-color: lightblue;
|
||||
font-family: verdana;
|
||||
}
|
||||
|
||||
div.ball{
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
position: absolute;
|
||||
/*opacity: 0.5;*/
|
||||
z-index: 999;
|
||||
/*cursor: grab;*/
|
||||
}
|
||||
|
||||
#ballBox{
|
||||
background-image: url("img/Aufnahmeplatte.png");
|
||||
background-repeat: no-repeat;
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.msg{
|
||||
position: absolute;
|
||||
font-weight: bold;
|
||||
font-size: 20pt;
|
||||
top: 150px;
|
||||
left: 175px;
|
||||
width: 400px;
|
||||
height: 150px;
|
||||
float: left;
|
||||
z-index: 9999999;
|
||||
padding-top: 60px;
|
||||
text-align: center;
|
||||
background-color: #000;
|
||||
border-radius: 10px;
|
||||
opacity: .6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#unclickableCircle{
|
||||
background-color: black;
|
||||
position: absolute;
|
||||
opacity: 0.4;
|
||||
width: 732px;
|
||||
height: 732px;
|
||||
margin-top: 2px;
|
||||
margin-left: 2px;
|
||||
border-radius: 366px;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
#backgroundMsg{
|
||||
background-color: red;
|
||||
position: absolute;
|
||||
opacity: 0.2;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
#nav{
|
||||
/*background-color: red;*/
|
||||
height: 80px;
|
||||
}
|
||||
#navL{
|
||||
/*background-color: yellow;*/
|
||||
width: 200px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#navM{
|
||||
/*background-color: green;*/
|
||||
width: 90px;
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
#navR{
|
||||
/*background-color: green;*/
|
||||
width: 200px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#shelfBox{
|
||||
/*background-color: red;*/
|
||||
background-image: url("img/Shelf.png");
|
||||
background-repeat: repeat-y;
|
||||
background-size: contain;
|
||||
float: right;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 750px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.shelf{
|
||||
/*background-color: yellow;
|
||||
opacity: 0.5;*/
|
||||
width: 60px;
|
||||
height: 150px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 5px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
116
02_Software/02_RaspberryPi/style_button.css
Normal file
116
02_Software/02_RaspberryPi/style_button.css
Normal file
@ -0,0 +1,116 @@
|
||||
/*.button {
|
||||
background-color:#ffebba;
|
||||
-moz-border-radius:5px;
|
||||
-webkit-border-radius:5px;
|
||||
border-radius:5px;
|
||||
border:1px solid #f5b427;
|
||||
display:inline-block;
|
||||
cursor:pointer;
|
||||
color:#030303;
|
||||
font-family:Arial;
|
||||
font-size: 16px;
|
||||
padding: 2px 32px;
|
||||
margin-top: 5px;
|
||||
text-decoration:none;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color:#f5b427;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
top:1px;
|
||||
}
|
||||
|
||||
.button:disabled{
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
}*/
|
||||
|
||||
|
||||
.button {
|
||||
background-color: #34A7C1;
|
||||
border: 1px solid #999999;
|
||||
border-radius:5px;
|
||||
-moz-border-radius:5px;
|
||||
-webkit-border-radius:5px;
|
||||
color: #EEEEEE;
|
||||
padding: 2px 32px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #EEEEEE;
|
||||
color: #34A7C1;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
background-color: #34A7C1;
|
||||
color: #EEEEEE;
|
||||
}
|
||||
|
||||
.button:disabled{
|
||||
background-color: #EEEEEE;
|
||||
color: #34A7C1;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.onoffswitch {
|
||||
position: relative;
|
||||
padding-top: 5px;
|
||||
width: 90px;
|
||||
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
|
||||
}
|
||||
.onoffswitch-checkbox {
|
||||
display: none;
|
||||
}
|
||||
.onoffswitch-label {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 2px solid #999999;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.onoffswitch-inner {
|
||||
display: block;
|
||||
width: 200%;
|
||||
margin-left: -100%;
|
||||
transition: margin 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-inner:before, .onoffswitch-inner:after {
|
||||
display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
|
||||
font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.onoffswitch-inner:before {
|
||||
content: "ON";
|
||||
padding-left: 10px;
|
||||
background-color: #34A7C1; color: #FFFFFF;
|
||||
}
|
||||
.onoffswitch-inner:after {
|
||||
content: "OFF";
|
||||
padding-right: 10px;
|
||||
background-color: #EEEEEE; color: #999999;
|
||||
text-align: right;
|
||||
}
|
||||
.onoffswitch-switch {
|
||||
display: block; width: 18px; margin: 6px;
|
||||
background: #FFFFFF;
|
||||
position: absolute; top: 5px; bottom: 0;
|
||||
right: 56px;
|
||||
border: 2px solid #999999; border-radius: 20px;
|
||||
transition: all 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
|
||||
right: 0px;
|
||||
}
|
1
02_Software/02_RaspberryPi/testfile.txt
Normal file
1
02_Software/02_RaspberryPi/testfile.txt
Normal file
@ -0,0 +1 @@
|
||||
140.05.044
|
103
02_Software/02_RaspberryPi/updateBallDatabase.php
Normal file
103
02_Software/02_RaspberryPi/updateBallDatabase.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
include('db.inc.php');
|
||||
$q = intval($_GET['q']);
|
||||
|
||||
// Load Plate with balls and pass the result to html-file
|
||||
if($q == 0) {
|
||||
$result = $conn->query("SELECT pos_id, pos_x, pos_y, ball_color FROM ball_positions WHERE pos_isEmpty = 0 AND ball_targetPos = 0");
|
||||
$outp = array();
|
||||
$outp = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
echo json_encode($outp);
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
// Load Shelf with balls and pass the result to html-file
|
||||
if($q == 3) {
|
||||
$result = $conn->query("SELECT pos_id, ball_color, ball_targetPos FROM ball_positions WHERE ball_targetPos != 0 ORDER BY ball_prio");
|
||||
$outp = array();
|
||||
$outp = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
echo json_encode($outp);
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
// Write to database, if a ball has been moved to the shelf
|
||||
if($q == 1) {
|
||||
$ballID = intval($_GET['ballID']);
|
||||
$shelfID = intval($_GET['shelfID']);
|
||||
$prio = intval($_GET['prio']);
|
||||
$live = intval($_GET['live']);
|
||||
|
||||
// Create instructions only if webside is in live-mode
|
||||
if ($live == 1) {
|
||||
$conn->query("UPDATE ball_positions SET ball_targetPos = $shelfID, ball_prio = $prio, sorted_by_robot = 1 WHERE pos_id = $ballID");
|
||||
|
||||
//Get infos for python-script
|
||||
$result = $conn->query("SELECT pos_x, pos_y, ball_targetPos FROM ball_positions WHERE pos_id = $ballID");
|
||||
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
|
||||
$posX = $row['pos_x'];
|
||||
$posY = $row['pos_y'];
|
||||
$target = $row['ball_targetPos'];
|
||||
|
||||
passthru('python create_instructions.py '.$posX.' '.$posY.' '.$target.'');
|
||||
} else {
|
||||
$conn->query("UPDATE ball_positions SET ball_targetPos = $shelfID, ball_prio = $prio, sorted_by_robot = 0 WHERE pos_id = $ballID");
|
||||
}
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
// Empty the shelf -> Reset target-positions and priorities
|
||||
if($q == 2) {
|
||||
$conn->query("UPDATE ball_positions SET pos_isEmpty = 0, ball_targetPos = 0, ball_prio = -1, sorted_by_robot = 0");
|
||||
$conn->query("UPDATE ball_positions SET pos_isEmpty = 1 WHERE pos_id = 29");
|
||||
$conn->close();
|
||||
echo json_encode("done");
|
||||
}
|
||||
|
||||
// Autosort balls in Database
|
||||
if($q == 4) {
|
||||
$result = $conn->query("SELECT pos_id, ball_color FROM ball_positions WHERE pos_isEmpty = 0 AND ball_targetPos = 0 ORDER BY ball_color, pos_id");
|
||||
|
||||
$numColor = array(0, 0, 0, 0); // Store Blue, Red, Green, Yellow
|
||||
$actColor = "undefined";
|
||||
$i = 0;
|
||||
$prio = 0;
|
||||
$shelfID = 10;
|
||||
|
||||
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
|
||||
$actID = $row['pos_id'];
|
||||
$actColor = $row['ball_color'];
|
||||
|
||||
if ($i == 0) $lastColor = $actColor; // Don't go to next shelf at first run
|
||||
if (($i % 5 == 0 && $i != 0) || $actColor != $lastColor) { // If the shelf is full or color changes
|
||||
$shelfID++;
|
||||
$i = 0;
|
||||
}
|
||||
if ($shelfID % 5 == 0 && $shelfID % 10 != 0 && $shelfID != 10) {// If the last shelf in a row is reached
|
||||
$shelfID = $shelfID + 5;
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
$conn->query("UPDATE ball_positions SET ball_targetPos = $shelfID, ball_prio = $prio WHERE pos_id = $actID");
|
||||
|
||||
$lastColor = $actColor;
|
||||
$i++;
|
||||
$prio++;
|
||||
}
|
||||
$conn->close();
|
||||
echo json_encode("done");
|
||||
}
|
||||
|
||||
// Call python-script for refreshing the plate (scanning the positions)
|
||||
if($q == 5) {
|
||||
passthru('python update_VarAngle.py');
|
||||
}
|
||||
|
||||
// Call python-script for doing the sorting of the balls which are in the shelf in Not-Live-Mode
|
||||
if($q == 6) {
|
||||
passthru('python readBalls.py');
|
||||
}
|
||||
|
||||
?>
|
133
02_Software/02_RaspberryPi/update_VarAngle.py
Normal file
133
02_Software/02_RaspberryPi/update_VarAngle.py
Normal file
@ -0,0 +1,133 @@
|
||||
# Creates instructions for the robot to move 360 degrees around, make photos of the ball-plate and write the ball_colors to the database
|
||||
|
||||
import MySQLdb
|
||||
import random
|
||||
import serial
|
||||
import time
|
||||
from color_detection_v4 import DetectColor
|
||||
from struct import pack
|
||||
|
||||
def sendBin( dec ):
|
||||
x = pack('i', dec)
|
||||
ser.write(x[1])
|
||||
ser.write(x[0])
|
||||
|
||||
#ser = serial.Serial('/dev/ttyACM0', 9600) # Change name if necessary. Find out name with ls /dev/tty* (1x without, 1x with USB connected)
|
||||
ser = serial.Serial(port='/dev/ttyACM0', # Change name if necessary. Find out name with ls /dev/tty* (1x without, 1x with USB connected)
|
||||
baudrate=9600,
|
||||
parity=serial.PARITY_NONE,
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
timeout=1)
|
||||
time.sleep(2) # Arduino resets, so wait for some time
|
||||
|
||||
db = MySQLdb.connect("localhost","root","root","rpr_robot" ) # Open database connection
|
||||
cursor = db.cursor() # prepare a cursor object using cursor() method
|
||||
|
||||
# Get the number of rows in the database for the loop
|
||||
cursor.execute('select * from ball_positions')
|
||||
cursor.fetchall()
|
||||
num_entries = cursor.rowcount - 1
|
||||
|
||||
# Define the possible colors of the balls
|
||||
colors = ["blue", "red", "yellow", "cyan", "green"]
|
||||
is_empty = 0;
|
||||
|
||||
# Create instructions to turn around
|
||||
signature = chr(0b01010010) # from raspberry
|
||||
priority0 = chr(0b00000000) # add to end of queue
|
||||
priority1 = chr(0b00000001)
|
||||
moveJ = chr(0b00000001) # binary 1
|
||||
moveA = chr(0b00000100) # binary 4
|
||||
pause = chr(0b00000111) # binary 7
|
||||
dontChange = chr(0b01111111) + chr(0b11111111)
|
||||
eot = chr(0b11111110) + chr(0b11111110) # end of transmission
|
||||
magOff = chr(0b00000000)
|
||||
magOn = chr(0b00000001)
|
||||
angle = (0, 45, 90, 135, 180, 225, 270, 315)
|
||||
|
||||
# Predefine messages
|
||||
msgPause = signature + priority0 + pause
|
||||
msgTurn = signature + priority0 + moveA
|
||||
|
||||
# Initialize color detection
|
||||
ballColors = DetectColor()
|
||||
|
||||
# TODO: Check if robot is in save mode or move it to!
|
||||
|
||||
|
||||
# Set startposition
|
||||
ser.write(msgTurn)
|
||||
sendBin(0) # Keep R1 as it is
|
||||
sendBin(110) # Put P to this height
|
||||
sendBin(90) # Put R2 to this angle
|
||||
ser.write(eot)
|
||||
print("TO START")
|
||||
|
||||
time.sleep(10)
|
||||
|
||||
for i in angle:
|
||||
ser.write(msgTurn)
|
||||
sendBin(i) # Turn R1 to actual angle
|
||||
ser.write(dontChange) # Keep P as it is
|
||||
ser.write(dontChange) # Keep R2 as it is
|
||||
ser.write(eot)
|
||||
print("TO "+str(i))
|
||||
|
||||
ser.write(msgPause)
|
||||
sendBin(2000) # Send Wait for 2 seconds
|
||||
ser.write(eot)
|
||||
print("pause at "+str(i))
|
||||
|
||||
# TODO Wait time or wait for "I'm there" from robot
|
||||
time.sleep(3)
|
||||
i = i + 90
|
||||
if i >= 360:
|
||||
i = i - 360
|
||||
ballColors.startDetection(i)
|
||||
# TODO Wait time
|
||||
time.sleep(3)
|
||||
|
||||
ser.write(msgTurn)
|
||||
sendBin(0) # Keep R1 as it is
|
||||
sendBin(110) # Put P to this height
|
||||
sendBin(90) # Put R2 to this angle
|
||||
ser.write(eot)
|
||||
print("TO START")
|
||||
|
||||
# Go through array and write detected positions to database
|
||||
for i in range(0, num_entries + 1):
|
||||
# Ball-Position 29 must be empty at any time!
|
||||
if i == 29 or ballColors.Matrix[i][1] == -1:
|
||||
is_empty = 1
|
||||
color = "invalid"
|
||||
else:
|
||||
is_empty = 0
|
||||
# Set random color -> Just for offline-programming!
|
||||
#color = random.choice(colors);
|
||||
color = colors[int(ballColors.Matrix[i][1])];
|
||||
|
||||
|
||||
# Prepare SQL query to UPDATE required records
|
||||
sql = "UPDATE ball_positions SET pos_isEmpty = %i, ball_color = '%s', ball_targetPos = 0, ball_prio = -1 WHERE pos_id = %i" % (is_empty, color, i)
|
||||
try:
|
||||
cursor.execute(sql) # Execute the SQL command
|
||||
db.commit() # Commit your changes in the database
|
||||
except:
|
||||
print "Error: unable to update records"
|
||||
db.rollback() # Rollback in case there is any error
|
||||
|
||||
db.close() # disconnect from server
|
||||
|
||||
|
||||
# Only for debugging
|
||||
while True:
|
||||
print(ser.readline())
|
||||
|
||||
file = open("testfile.txt", "w")
|
||||
file.write(ballColors.Matrix);
|
||||
file.close
|
||||
#print ballColors.Matrix
|
||||
|
||||
|
||||
ser.close() # close serial connection
|
39
02_Software/02_RaspberryPi/update_random.py
Normal file
39
02_Software/02_RaspberryPi/update_random.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Creates instructions for the robot to move 360 degrees around, make photos of the ball-plate and write the ball_colors to the database
|
||||
|
||||
import MySQLdb
|
||||
import random
|
||||
|
||||
db = MySQLdb.connect("localhost","root","root","rpr_robot" ) # Open database connection
|
||||
cursor = db.cursor() # prepare a cursor object using cursor() method
|
||||
|
||||
# Get the number of rows in the database for the loop
|
||||
cursor.execute('select * from ball_positions')
|
||||
cursor.fetchall()
|
||||
num_entries = cursor.rowcount - 1
|
||||
|
||||
# Define the possible colors of the balls
|
||||
colors = ["blue", "red", "yellow", "cyan", "green"]
|
||||
is_empty = 0;
|
||||
|
||||
|
||||
# Go through array and write detected positions to database
|
||||
for i in range(0, num_entries + 1):
|
||||
# Ball-Position 29 must be empty at any time!
|
||||
if i == 29:
|
||||
is_empty = 1
|
||||
color = "invalid"
|
||||
else:
|
||||
is_empty = 0
|
||||
# Set random color -> Just for offline-programming!
|
||||
color = random.choice(colors);
|
||||
|
||||
# Prepare SQL query to UPDATE required records
|
||||
sql = "UPDATE ball_positions SET pos_isEmpty = %i, ball_color = '%s', ball_targetPos = 0, ball_prio = -1 WHERE pos_id = %i" % (is_empty, color, i)
|
||||
try:
|
||||
cursor.execute(sql) # Execute the SQL command
|
||||
db.commit() # Commit your changes in the database
|
||||
except:
|
||||
print "Error: unable to update records"
|
||||
db.rollback() # Rollback in case there is any error
|
||||
|
||||
db.close() # disconnect from server
|
Reference in New Issue
Block a user