first commit
This commit is contained in:
commit
a5a0434432
1126 changed files with 439481 additions and 0 deletions
400
Software/Scratch/GrovePiScratch.py
Executable file
400
Software/Scratch/GrovePiScratch.py
Executable file
|
|
@ -0,0 +1,400 @@
|
|||
#!/usr/bin/python
|
||||
###############################################################################################################
|
||||
# This library is for using the GrovePi with Scratch
|
||||
# http://www.dexterindustries.com/GrovePi/
|
||||
# History
|
||||
# ------------------------------------------------
|
||||
# Author Date Comments
|
||||
# Karan 29 June 15 Initial Authoring
|
||||
# John 22 Feb 16 Adding GrovePi Barometer
|
||||
# Nicole Nov 16 Added Folder support for take_picture
|
||||
# Nicole Nov 16 Added eSpeak Support
|
||||
# Nicole 18 Nov 16 Adding PivotPi support
|
||||
# SimonW 22 Mar 18 Bug fix in error handling line 383
|
||||
'''
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
GrovePi for the Raspberry Pi: an open source platform for connecting Grove Sensors to the Raspberry Pi.
|
||||
Copyright (C) 2016 Dexter Industries
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
'''
|
||||
'''
|
||||
#
|
||||
# Based on the BrickPi Scratch Library written by Jaikrishna
|
||||
#
|
||||
# The Python program acts as the Bridge between Scratch & GrovePi and must be running for the Scratch program to run.
|
||||
##############################################################################################################
|
||||
'''
|
||||
import scratch,sys,threading,math
|
||||
import grovepi
|
||||
import time
|
||||
import os # to handle folder paths
|
||||
|
||||
try:
|
||||
sys.path.insert(0, '/home/pi/Dexter/PivotPi/Software/Scratch/')
|
||||
import PivotPiScratch
|
||||
pivotpi_available=True
|
||||
except:
|
||||
pivotpi_available=False
|
||||
|
||||
# Folders where pictures get saved
|
||||
defaultCameraFolder="/home/pi/Desktop/"
|
||||
cameraFolder = defaultCameraFolder
|
||||
|
||||
|
||||
# Pi user ID Number - used for setting permissions
|
||||
pi_user=1000
|
||||
pi_group=1000
|
||||
|
||||
# used is GrovePi is actually connected - allows for testing without a GrovePi
|
||||
en_grovepi=1
|
||||
# print debugging statements
|
||||
en_debug=1
|
||||
|
||||
try:
|
||||
s = scratch.Scratch()
|
||||
if s.connected:
|
||||
print "GrovePi Scratch: Connected to Scratch successfully"
|
||||
#else:
|
||||
#sys.exit(0)
|
||||
except scratch.ScratchError:
|
||||
print "GrovePi Scratch: Scratch is either not opened or remote sensor connections aren't enabled"
|
||||
#sys.exit(0)
|
||||
|
||||
class myThread (threading.Thread):
|
||||
def __init__(self, threadID, name, counter):
|
||||
threading.Thread.__init__(self)
|
||||
self.threadID = threadID
|
||||
self.name = name
|
||||
self.counter = counter
|
||||
def run(self):
|
||||
while running:
|
||||
time.sleep(.2) # sleep for 200 ms
|
||||
|
||||
thread1 = myThread(1, "Thread-1", 1) #Setup and start the thread
|
||||
thread1.setDaemon(True)
|
||||
|
||||
analog_sensors=['analogRead','rotary','sound','light','moisture']
|
||||
digitalInp=['button']
|
||||
digitalOp=['led','relay']
|
||||
pwm=['LEDPower','buzzer','analogWrite']
|
||||
|
||||
def match_sensors(msg,lst):
|
||||
for i,e in enumerate(lst):
|
||||
if msg[:len(e)].lower()==e.lower():
|
||||
return i
|
||||
return -1
|
||||
|
||||
try:
|
||||
s.broadcast('READY')
|
||||
except NameError:
|
||||
print "GrovePi Scratch: Unable to Broadcast"
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
m = s.receive()
|
||||
|
||||
while m==None or m[0] == 'sensor-update':
|
||||
m = s.receive()
|
||||
|
||||
originalmsg = m[1]
|
||||
|
||||
# change input to all lowercase.
|
||||
# Users can enter any which way they want
|
||||
msg = originalmsg.lower()
|
||||
if en_debug:
|
||||
print "Rx:",originalmsg
|
||||
|
||||
if msg == 'SETUP'.lower() :
|
||||
print "Setting up sensors done"
|
||||
elif msg == 'START'.lower() :
|
||||
running = True
|
||||
if thread1.is_alive() == False:
|
||||
thread1.start()
|
||||
print "Service Started"
|
||||
|
||||
# ANALOG SENSORS
|
||||
elif match_sensors(msg,analog_sensors) >=0:
|
||||
if en_grovepi:
|
||||
s_no=match_sensors(msg,analog_sensors)
|
||||
sens=analog_sensors[s_no]
|
||||
port=int(msg[len(sens):])
|
||||
a_read=grovepi.analogRead(port)
|
||||
s.sensorupdate({sens:a_read})
|
||||
|
||||
if en_debug:
|
||||
print msg
|
||||
print sens +'op:'+ str(a_read)
|
||||
|
||||
elif msg[:8].lower()=="setInput".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[8:])
|
||||
grovepi.pinMode(port,"INPUT")
|
||||
if en_debug:
|
||||
print msg
|
||||
|
||||
elif msg[:9].lower()=="setOutput".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[9:])
|
||||
grovepi.pinMode(port,"OUTPUT")
|
||||
if en_debug:
|
||||
print msg
|
||||
|
||||
elif msg[:11].lower()=="digitalRead".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[11:])
|
||||
grovepi.pinMode(port,"INPUT")
|
||||
d_read=grovepi.digitalRead(port)
|
||||
s.sensorupdate({'digitalRead':d_read})
|
||||
if en_debug:
|
||||
print msg
|
||||
print "Digital Reading: " + str(d_read)
|
||||
|
||||
elif match_sensors(msg,digitalInp) >=0:
|
||||
if en_grovepi:
|
||||
s_no=match_sensors(msg,digitalInp)
|
||||
sens=digitalInp[s_no]
|
||||
port=int(msg[len(sens):])
|
||||
sens += str(port)
|
||||
grovepi.pinMode(port,"INPUT")
|
||||
d_read=grovepi.digitalRead(port)
|
||||
s.sensorupdate({sens:d_read})
|
||||
if en_debug:
|
||||
print msg,
|
||||
print sens +' output:'+ str(d_read)
|
||||
|
||||
elif msg[:16].lower()=="digitalWriteHigh".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[16:])
|
||||
grovepi.pinMode(port,"OUTPUT")
|
||||
grovepi.digitalWrite(port,1)
|
||||
if en_debug:
|
||||
print msg
|
||||
|
||||
elif msg[:15].lower()=="digitalWriteLow".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[15:])
|
||||
grovepi.pinMode(port,"OUTPUT")
|
||||
grovepi.digitalWrite(port,0)
|
||||
if en_debug:
|
||||
print msg
|
||||
|
||||
elif match_sensors(msg,pwm) >=0:
|
||||
if en_grovepi:
|
||||
s_no=match_sensors(msg,pwm)
|
||||
sens=pwm[s_no]
|
||||
l=len(sens)
|
||||
port=int(msg[l:l+1])
|
||||
power=int(msg[l+1:])
|
||||
grovepi.pinMode(port,"OUTPUT")
|
||||
grovepi.analogWrite(port,power)
|
||||
if en_debug:
|
||||
print msg
|
||||
|
||||
elif match_sensors(msg,digitalOp) >=0:
|
||||
if en_grovepi:
|
||||
s_no=match_sensors(msg,digitalOp)
|
||||
sens=digitalOp[s_no]
|
||||
l=len(sens)
|
||||
port=int(msg[l:l+1])
|
||||
state=msg[l+1:]
|
||||
grovepi.pinMode(port,"OUTPUT")
|
||||
if state=='on':
|
||||
grovepi.digitalWrite(port,1)
|
||||
else:
|
||||
grovepi.digitalWrite(port,0)
|
||||
if en_debug:
|
||||
print msg
|
||||
|
||||
elif msg[:4].lower()=="temp".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[4:])
|
||||
[temp,humidity] = grovepi.dht(port,0)
|
||||
s.sensorupdate({'temp':temp})
|
||||
if en_debug:
|
||||
print msg
|
||||
print "temp: ",temp
|
||||
|
||||
elif msg[:8].lower()=="humidity".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[8:])
|
||||
[temp,humidity] = grovepi.dht(port,0)
|
||||
s.sensorupdate({'humidity':humidity})
|
||||
if en_debug:
|
||||
print msg
|
||||
print "humidity:",humidity
|
||||
|
||||
elif msg[:8].lower()=="distance".lower():
|
||||
if en_grovepi:
|
||||
port=int(msg[8:])
|
||||
dist=grovepi.ultrasonicRead(port)
|
||||
s.sensorupdate({'distance':dist})
|
||||
if en_debug:
|
||||
print msg
|
||||
print "distance=",dist
|
||||
|
||||
elif msg[:3].lower()=="lcd".lower():
|
||||
if en_grovepi:
|
||||
if en_debug:
|
||||
print msg[:3], msg[3:6], originalmsg[6:]
|
||||
|
||||
sys.path.insert(0, '/home/pi/Dexter/GrovePi/Software/Python/grove_rgb_lcd')
|
||||
import grove_rgb_lcd
|
||||
if msg[3:6].lower() == "col".lower(): #lower() added just for consistency. Not really needed
|
||||
rgb = []
|
||||
for i in range(0,6,2):
|
||||
rgb.append(int(msg[6:][i:i+2],16)) # convert from one hex string to three ints
|
||||
if en_debug:
|
||||
print "colours are:",rgb[0],rgb[1],rgb[2]
|
||||
grove_rgb_lcd.setRGB(rgb[0],rgb[1],rgb[2])
|
||||
elif msg[3:6].lower() == "txt".lower():
|
||||
txt = originalmsg[6:]
|
||||
grove_rgb_lcd.setText_norefresh(txt)
|
||||
else:
|
||||
pass
|
||||
if en_debug:
|
||||
print msg
|
||||
|
||||
# elif msg[:10].lower()=="setOutput".lower():
|
||||
# if en_grovepi:
|
||||
# port=int(msg[10:])
|
||||
# a_read=grovepi.analogRead(port)
|
||||
# s.sensorupdate({'analogRead':a_read})
|
||||
# if en_debug:
|
||||
# print msg
|
||||
# print "Analog Reading: " + str(a_read)
|
||||
elif msg.lower()=="READ_IR".lower() or msg.lower()=="IR".lower():
|
||||
print "READ_IR!"
|
||||
if en_ir_sensor==0:
|
||||
import lirc
|
||||
sockid = lirc.init("keyes", blocking = False)
|
||||
en_ir_sensor=1
|
||||
try:
|
||||
read_ir= lirc.nextcode() # press 1
|
||||
if len(read_ir) !=0:
|
||||
print read_ir[0]
|
||||
except:
|
||||
if en_debug:
|
||||
e = sys.exc_info()[1]
|
||||
print "Error reading IR sensor: " + str(read_ir)
|
||||
if en_debug:
|
||||
print "IR Recv Reading: " + str(read_ir)
|
||||
if en_gpg:
|
||||
if len(read_ir) !=0:
|
||||
s.sensorupdate({'read_ir':read_ir[0]})
|
||||
else:
|
||||
s.sensorupdate({'read_ir':""})
|
||||
# CREATE FOLDER TO SAVE PHOTOS IN
|
||||
|
||||
elif msg[:6].lower()=="FOLDER".lower():
|
||||
print "Camera folder"
|
||||
try:
|
||||
cameraFolder=defaultCameraFolder+str(msg[6:])
|
||||
if not os.path.exists(cameraFolder):
|
||||
os.makedirs(cameraFolder)
|
||||
os.chown(cameraFolder,pi_user,pi_group)
|
||||
s.sensorupdate({"folder":"created"})
|
||||
else:
|
||||
s.sensorupdate({"folder":"set"})
|
||||
except:
|
||||
print "error with folder name"
|
||||
|
||||
elif msg.lower()=="TAKE_PICTURE".lower():
|
||||
print "TAKE_PICTURE!"
|
||||
try:
|
||||
from subprocess import call
|
||||
import datetime
|
||||
newimage = "{}/img_{}.jpg".format(cameraFolder,str(datetime.datetime.now()).replace(" ","_",10).replace(":","_",10))
|
||||
photo_cmd="raspistill -o {} -w 640 -h 480 -t 1".format(newimage)
|
||||
print photo_cmd
|
||||
call ([photo_cmd], shell=True)
|
||||
os.chown(newimage,pi_user,pi_group)
|
||||
print "Picture Taken"
|
||||
except:
|
||||
if en_debug:
|
||||
e = sys.exc_info()[1]
|
||||
print "Error taking picture",e
|
||||
s.sensorupdate({'camera':"Error"})
|
||||
s.sensorupdate({'camera':"Picture Taken"})
|
||||
|
||||
# Barometer code, pressure
|
||||
elif msg[:9].lower()=="pressure".lower():
|
||||
if en_grovepi:
|
||||
# We import here to prevent errors thrown. If the import fails, you just get an error message instead of the communicator crashing.
|
||||
# If user is using multiple sensors and using their own image which does not have the pythonpath set correctly then
|
||||
# they'll just not get the output for 1 sensor, and the others will still keep working
|
||||
from grove_i2c_barometic_sensor_BMP180 import BMP085 # Barometric pressure sensor.
|
||||
bmp = BMP085(0x77, 1) #Initialize the pressure sensor (barometer)
|
||||
press = bmp.readPressure()/100.0
|
||||
s.sensorupdate({'pressure':press})
|
||||
if en_debug:
|
||||
print "Pressure: " + str(press)
|
||||
if en_debug: # If Debug is enabled, print the value of msg.
|
||||
print msg
|
||||
|
||||
elif (msg[:5].lower()=="SPEAK".lower() or msg[:3].lower()=="SAY".lower() ):
|
||||
try:
|
||||
if en_grovepi:
|
||||
from subprocess import call
|
||||
cmd_beg = "espeak-ng -ven+f1 "
|
||||
in_text = msg[len("SPEAK"):]
|
||||
cmd_end = " 2>/dev/null"
|
||||
|
||||
call([cmd_beg+"\""+in_text+"\""+cmd_end], shell=True)
|
||||
if en_debug:
|
||||
print(msg)
|
||||
except:
|
||||
print("Issue with espeak")
|
||||
|
||||
# PIVOTPI
|
||||
elif pivotpi_available==True and PivotPiScratch.isPivotPiMsg(msg):
|
||||
pivotsensors = PivotPiScratch.handlePivotPi(msg)
|
||||
# print "Back from PivotPi",pivotsensors
|
||||
s.sensorupdate(pivotsensors)
|
||||
|
||||
else:
|
||||
if en_debug:
|
||||
print "Ignoring: ",msg
|
||||
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
running= False
|
||||
print "GrovePi Scratch: Disconnected from Scratch"
|
||||
break
|
||||
except (scratch.ScratchConnectionError,NameError) as e: #bug fix simonw 22Mar18
|
||||
while True:
|
||||
#thread1.join(0)
|
||||
print "GrovePi Scratch: Scratch connection error, Retrying"
|
||||
# print e
|
||||
time.sleep(5)
|
||||
try:
|
||||
s = scratch.Scratch()
|
||||
s.broadcast('READY')
|
||||
print "GrovePi Scratch: Connected to Scratch successfully"
|
||||
break;
|
||||
except scratch.ScratchError:
|
||||
print "GrovePi Scratch: Scratch is either not opened or remote sensor connections aren't enabled\n..............................\n"
|
||||
except:
|
||||
e = sys.exc_info()[0]
|
||||
print "GrovePi Scratch: Error %s" % e
|
||||
5
Software/Scratch/GrovePi_Scratch_Scripts/GrovePiScratch_debug.sh
Executable file
5
Software/Scratch/GrovePi_Scratch_Scripts/GrovePiScratch_debug.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#! /bin/bash
|
||||
|
||||
id=$(ps aux | grep "python GrovePiScratch.py" | grep -v grep | awk '{print $2}')
|
||||
kill -15 $id 2> /dev/null
|
||||
python /home/pi/Desktop/GrovePi/Software/Scratch/GrovePiScratch.py
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Exec=/home/pi/Desktop/GrovePi/Software/Scratch/GrovePi_Scratch_Scripts/GrovePi_Scratch_Start.sh
|
||||
Icon=/usr/share/scratch/Media/Costumes/Animals/cat1-b.gif
|
||||
Terminal=false
|
||||
Name=GrovePi_Scratch_Start
|
||||
Comment= Programming system and content development tool
|
||||
Categories=Application;Education;Development;
|
||||
MimeType=application/x-scratch-project
|
||||
2
Software/Scratch/GrovePi_Scratch_Scripts/GrovePi_Scratch_Start.sh
Executable file
2
Software/Scratch/GrovePi_Scratch_Scripts/GrovePi_Scratch_Start.sh
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#! /bin/bash
|
||||
lxterminal --command "sudo /home/pi/Desktop/GrovePi/Software/Scratch/GrovePi_Scratch_Scripts/GrovePiScratch_debug.sh"
|
||||
38
Software/Scratch/GrovePi_Scratch_Scripts/README.md
Normal file
38
Software/Scratch/GrovePi_Scratch_Scripts/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
NOTE: THIS IS FOR DEVELOPERS ONLY. Normal user need not do this.
|
||||
See more at http://www.dexterindustries.com/grovepi/
|
||||
|
||||
1. Make both **GrovePiScratch_debug.sh** and **GrovePi_Scratch_Start.sh** executable:
|
||||
|
||||
> sudo chmod +x GrovePiScratch_debug.sh
|
||||
|
||||
> sudo chmod +x GrovePi_Scratch_Start.sh
|
||||
|
||||
2. Copy **GrovePi_Scratch_Start.desktop** to **/usr/share/applications/**
|
||||
3. Make a desktop Shortcut from **Desktop->Education->GrovePi_Scratch_Start**
|
||||
3. Double click on **GrovePi_Scratch_Start** to start running the script
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
GrovePi for the Raspberry Pi: an open source platform for connecting Grove Sensors to the Raspberry Pi.
|
||||
Copyright (C) 2016 Dexter Industries
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
28
Software/Scratch/GrovePi_Scratch_Scripts/Scratch_setup.txt
Normal file
28
Software/Scratch/GrovePi_Scratch_Scripts/Scratch_setup.txt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
These are instructions for installing GrovePi Scratch on any Raspberry Pi image not from Dexter Industries.
|
||||
To skip this step, use the Dexter Industries Raspberry Pi image, found here: http://www.dexterindustries.com/BrickPi/getting-started/pi-prep/
|
||||
|
||||
The following is a short list of changes needed to setup GrovePi for Scratch.
|
||||
|
||||
You need the ScratchPy library to be installed. Install it by the following procedure.
|
||||
Run the command:
|
||||
sudo wget https://bitbucket.org/pypa/setuptools/raw/0.7.4/ez_setup.py -O - | sudo python
|
||||
Clone scratchpy:
|
||||
cd Desktop/GrovePi/Software/Scratch
|
||||
git clone https://github.com/pilliq/scratchpy.git
|
||||
Navigate to extracted directory and run:
|
||||
sudo make install
|
||||
|
||||
Start Scratch
|
||||
Select "Sensing"
|
||||
Right Click Enable Remote sensor connections
|
||||
You will see "Remote Sensor Connections Enabled"
|
||||
|
||||
back to terminal
|
||||
cd Desktop/GrovePi/Software/Scratch
|
||||
sudo python GrovePiScratch.py
|
||||
|
||||
Now open up an example:
|
||||
Might see "Remote sensor connections enabled" again. That's fine, hit ok.
|
||||
And now go!
|
||||
|
||||
|
||||
BIN
Software/Scratch/Grove_Examples/Barometer_Example.sb
Normal file
BIN
Software/Scratch/Grove_Examples/Barometer_Example.sb
Normal file
Binary file not shown.
BIN
Software/Scratch/Grove_Examples/LCD.sb
Normal file
BIN
Software/Scratch/Grove_Examples/LCD.sb
Normal file
Binary file not shown.
46
Software/Scratch/Grove_Examples/README.md
Normal file
46
Software/Scratch/Grove_Examples/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
Scratch examples for GrovePi
|
||||
============================
|
||||
|
||||
These are simple examples meant to illustrate how you can access GrovePi features through Scratch.
|
||||
|
||||
## 1. Simple LED: led_on_port_7 :
|
||||
|
||||
Plug any GrovePi LED onto Port 7 and run this Scratch script.
|
||||
|
||||
Click on the LED (on the screen) to control the physical LED
|
||||
|
||||
Click on Dex to turn the LED on for 2 seeconds and then it will turn off automatically
|
||||
|
||||
Click on the letter B on your keyboard to generate a blinking LED
|
||||
|
||||
Both the physical LED and the Scratch LED should react together
|
||||
|
||||
## 2. two_tone_buzzer :
|
||||
|
||||
Plug the buzzer in port 5
|
||||
|
||||
Move the ball around. When it hits the leg a low buzz will be emitted. When it hits the head, a low buzz will be heard
|
||||
|
||||
## 3. two_buttons_in_parallel :
|
||||
|
||||
Put a button in port 2 and another one in port 3.
|
||||
|
||||
Press the first button (in port 2). Dex will wave
|
||||
|
||||
Press the second button (in port 3). Dex will rotate.
|
||||
|
||||
## 4. LCD screen :
|
||||
|
||||
Put LCD screen in any of the I2C ports
|
||||
|
||||
Clicking on any of the six coloured circles will change the background colour of the LCD screen (and change the colour of the sunglasses)
|
||||
|
||||
Clicking on any of the four text buttons in the corners will display that text (and have Dex say the selected text)
|
||||
|
||||
Clicking on Dex will have Dex ask you your name, and then display it on the LCD screen
|
||||
|
||||
## 5. Rotary sensor:
|
||||
|
||||
Plug the rotary sensor into port A0
|
||||
|
||||
Click on the Green flag and use the rotary sensor to move Dex left to right
|
||||
BIN
Software/Scratch/Grove_Examples/Rotary.sb
Normal file
BIN
Software/Scratch/Grove_Examples/Rotary.sb
Normal file
Binary file not shown.
BIN
Software/Scratch/Grove_Examples/Save_Variables_To_Text.sb
Normal file
BIN
Software/Scratch/Grove_Examples/Save_Variables_To_Text.sb
Normal file
Binary file not shown.
BIN
Software/Scratch/Grove_Examples/Take_Picture_Example.sb
Normal file
BIN
Software/Scratch/Grove_Examples/Take_Picture_Example.sb
Normal file
Binary file not shown.
BIN
Software/Scratch/Grove_Examples/led_on_port_D7.sb
Normal file
BIN
Software/Scratch/Grove_Examples/led_on_port_D7.sb
Normal file
Binary file not shown.
BIN
Software/Scratch/Grove_Examples/two_buttons_in_parallel.sb
Normal file
BIN
Software/Scratch/Grove_Examples/two_buttons_in_parallel.sb
Normal file
Binary file not shown.
BIN
Software/Scratch/Grove_Examples/two_tone_buzzer.sb
Normal file
BIN
Software/Scratch/Grove_Examples/two_tone_buzzer.sb
Normal file
Binary file not shown.
12
Software/Scratch/Local_Scratch2_Start.desktop
Normal file
12
Software/Scratch/Local_Scratch2_Start.desktop
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Exec=xhost +
|
||||
Icon=/usr/share/scratch/Media/Costumes/Animals/cat1-a.gif
|
||||
Exec=sudo bash /home/pi/Dexter/GrovePi/Software/Scratch/launch_scratch2.sh
|
||||
Terminal=false
|
||||
Name=GrovePi+ Scratch2
|
||||
Comment= Programming system and content development tool
|
||||
Categories=Application;Education;Development;
|
||||
MimeType=application/x-scratch-project
|
||||
12
Software/Scratch/Local_Scratch_Start.desktop
Normal file
12
Software/Scratch/Local_Scratch_Start.desktop
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Exec=xhost +
|
||||
Icon=/usr/share/scratch/Media/Costumes/Animals/cat1-b.gif
|
||||
Exec=sudo bash /home/pi/Dexter/GrovePi/Software/Scratch/start_local_scratch.sh
|
||||
Terminal=false
|
||||
Name=GrovePi+ Scratch 1.4
|
||||
Comment= Programming system and content development tool
|
||||
Categories=Application;Education;Development;
|
||||
MimeType=application/x-scratch-project
|
||||
6
Software/Scratch/launch_scratch2.sh
Executable file
6
Software/Scratch/launch_scratch2.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
sudo pkill -f GrovePiScratch.py
|
||||
sudo pkill -f wstosgh.py
|
||||
python /home/pi/Dexter/GrovePi/Software/Scratch/GrovePiScratch.py &
|
||||
python /home/pi/Dexter/GrovePi/Software/Scratch/wstosgh.py &
|
||||
/usr/bin/scratch2
|
||||
|
||||
BIN
Software/Scratch/new.sb
Normal file
BIN
Software/Scratch/new.sb
Normal file
Binary file not shown.
36
Software/Scratch/readme.md
Normal file
36
Software/Scratch/readme.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Program the GrovePi in Scratch
|
||||
|
||||
You can program the GrovePi in Scratch. This repo contains support programs and example programs to help you program the [GrovePi](http://www.dexterindustries.com/GrovePi) in Scratch.
|
||||
|
||||
## Getting Started Examples
|
||||
Please see our Examples folder for examples on how to get started with programming the GrovePi in Scratch!
|
||||
|
||||
Here are a list of example commands.
|
||||

|
||||
|
||||
## See Also
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
GrovePi for the Raspberry Pi: an open source platform for connecting Grove Sensors to the Raspberry Pi.
|
||||
Copyright (C) 2017 Dexter Industries
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
5
Software/Scratch/s2pifiles/extensions.json
Executable file
5
Software/Scratch/s2pifiles/extensions.json
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
{ "name":"Pi GPIO", "type":"extension", "file":"piGPIOExtension.js", "md5":"pigpio.png", "url":"file:///usr/lib/scratch2/scratch_extensions/gpio.html", "tags":["hardware"] },
|
||||
{ "name":"Pi SenseHAT", "type":"extension", "file":"piSenseHATExtension.js", "md5":"pisensehat.png", "url":"file:///usr/lib/scratch2/scratch_extensions/sensehat.html", "tags":["hardware"] },
|
||||
{ "name":"GrovePi", "type":"extension", "file":"piGrovePiExtension.js", "md5":"grovepi.png", "url":"file:///usr/lib/scratch2/scratch_extensions/grovepi.html", "tags":["hardware"] }
|
||||
]
|
||||
118
Software/Scratch/s2pifiles/grovepi.html
Executable file
118
Software/Scratch/s2pifiles/grovepi.html
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
<!doctype html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Worksheet - Physical Computing With Scratch | Raspberry Pi Learning Resources</title>
|
||||
<meta name="description" content="The worksheet for the Physical Computing With Scratch Learning Resource for Raspberry Pi" />
|
||||
<link rel="icon" type="image/png" href="/wp-content/themes/mind-control/images/favicon.png" />
|
||||
<link rel="publisher" href="https://plus.google.com/+RaspberryPi" />
|
||||
<link rel="stylesheet" href="/wp-content/themes/mind-control/css/prism.css" />
|
||||
<link rel="stylesheet" href="./style.css?1482225765" />
|
||||
<link rel="stylesheet" href="./robotoslab.css?family=Roboto+Slab:100,300,400,700">
|
||||
<link rel="stylesheet" href="./robotothin.css?family=Roboto:100italic,100,300italic,300,400italic,400,500italic,500,700italic,700,900italic,900">
|
||||
|
||||
<script>
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-46270871-1']);
|
||||
_gaq.push(['_gat._forceSSL']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function () {
|
||||
var ga = document.createElement('script');
|
||||
ga.type = 'text/javascript';
|
||||
ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : '//www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
<script src="/wp-content/themes/mind-control/js/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="learning worksheet learn worksheet ">
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<a href="/resources/" class="heading" title="Back to raspberrypi.org">Raspberry Pi Learning Resources</a>
|
||||
</header>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<article class="content">
|
||||
|
||||
<h1>Physical computing with Scratch</h1>
|
||||
<h2>GPIO pins</h2>
|
||||
<p>One powerful feature of the Raspberry Pi is the row of GPIO pins along the top edge of the board. GPIO stands for General-Purpose Input/Output. These pins are a physical interface between the Raspberry Pi and the outside world. At the simplest level, you can think of them as switches that you can turn on or off (input) or that the Pi can turn on or off (output).</p>
|
||||
<p>The GPIO pins allow the Raspberry Pi to control and monitor the outside world by being connected to electronic circuits. The Pi is able to control LEDs, turning them on or off, run motors, and many other things. It's also able to detect whether a switch has been pressed, the temperature, and light. We refer to this as physical computing.</p>
|
||||
<p>There are 40 pins on the Raspberry Pi (26 pins on early models), and they provide various different functions.</p>
|
||||
<p>If you have a RasPiO pin label, it can help to identify what each pin is used for. Make sure your pin label is placed with the keyring hole facing the USB ports, pointed outwards.</p>
|
||||
<p><img src="./images/raspio-ports.jpg" alt="" /></p>
|
||||
<p>If you don't have a pin label, then this guide can help you to identify the pin numbers:</p>
|
||||
<p><img src="./images/GPIO.png" alt="" /></p>
|
||||
<p>You'll see pins labelled as 3V3, 5V, GND and GP2, GP3, etc:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>3V3</td>
|
||||
<td>3.3 volts</td>
|
||||
<td>Anything connected to these pins will always get 3.3V of power</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5V</td>
|
||||
<td>5 volts</td>
|
||||
<td>Anything connected to these pins will always get 5V of power</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GND</td>
|
||||
<td>ground</td>
|
||||
<td>Zero volts, used to complete a circuit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GP2</td>
|
||||
<td>GPIO pin 2</td>
|
||||
<td>These pins are for general-purpose use and can be configured as input or output pins</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ID_SC/ID_SD/DNC</td>
|
||||
<td>Special purpose pins</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><strong>WARNING</strong>: If you follow the instructions, then playing about with the GPIO pins is safe and fun. Randomly plugging wires and power sources into your Pi, however, may destroy it, especially if using the 5V pins. Bad things can also happen if you try to connect things to your Pi that use a lot of power; LEDs are fine, motors are not. If you're worried about this, then you might want to consider using an add-on board such as the <a href="https://shop.pimoroni.com/products/explorer-hat">Explorer HAT</a> until you're confident enough to use the GPIO directly.</p>
|
||||
<h2>Lighting an LED</h2>
|
||||
<p>You can test whether your GPIO pins and LEDs are working by building the circuit below. You can use any resistor over about 50Ω.</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p>The LED is connected directly to the <strong>GND</strong> pin and the <strong>3V3</strong> pin via the 330 Ohm resistor, and should light up.</p>
|
||||
</li>
|
||||
<li>Be sure to connect your LED the correct way round; the longer leg should be connected to the 3V3 pin:</li>
|
||||
</ol>
|
||||
<p><img src="./images/led-3v3.png" alt="Test Circuit" /></p>
|
||||
<h2>Using a switchable pin</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<p>To control the LED, you'll need to adapt your circuit to use a switchable pin.</p>
|
||||
</li>
|
||||
<li>In the diagram below <strong>pin 17</strong> has been used, but you can use any numbered pin you wish.</li>
|
||||
</ol>
|
||||
<p><img src="./images/led-gpio17.png" alt="Test Circuit" /></p>
|
||||
|
||||
</article>
|
||||
|
||||
<div style="clear:both;"></div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
Software/Scratch/s2pifiles/grovepi.png
Executable file
BIN
Software/Scratch/s2pifiles/grovepi.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
170
Software/Scratch/s2pifiles/piGrovePiExtension.js
Executable file
170
Software/Scratch/s2pifiles/piGrovePiExtension.js
Executable file
|
|
@ -0,0 +1,170 @@
|
|||
// Modified piGPIOExtension.js for GrovePi
|
||||
// Ver 2.25Feb18
|
||||
new (function() {
|
||||
var ext = this;
|
||||
var fs = require('fs');
|
||||
var websocket;
|
||||
var sensorSplit;
|
||||
var sensorDict = {};
|
||||
var variableDict = {};
|
||||
|
||||
|
||||
function doConnect()
|
||||
{
|
||||
websocket = new WebSocket('ws://localhost:8000/')
|
||||
websocket.onopen = function(evt) { onOpen(evt) }
|
||||
websocket.onclose = function(evt) { onClose(evt) }
|
||||
websocket.onmessage = function(evt) { onMessage(evt) }
|
||||
websocket.onerror = function(evt) { onError(evt) }
|
||||
console.log('websocket connected from piGrovePiExtension')
|
||||
}
|
||||
|
||||
function onOpen(evt) {
|
||||
console.log('websocket opened')
|
||||
}
|
||||
|
||||
function onClose(evt) {
|
||||
console.log('websocket closed')
|
||||
}
|
||||
|
||||
function onMessage(evt) {
|
||||
var data = evt.data
|
||||
console.log('msg from sgh:' + data)
|
||||
sensorSplit = data.split(":");
|
||||
sensorDict[sensorSplit[0]] = sensorSplit[1];
|
||||
// console.log('sensorDict=' + JSON.stringify(sensorDict))
|
||||
}
|
||||
|
||||
function onError(evt) {
|
||||
var error = evt.data
|
||||
console.log('websocket error', error);
|
||||
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
function sendMessage(message) {
|
||||
websocket.send(message);
|
||||
console.log('msg to sgh:' + message)
|
||||
}
|
||||
|
||||
function doDisconnect() {
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
doConnect();
|
||||
|
||||
// Cleanup function when the extension is unloaded
|
||||
ext._shutdown = function ()
|
||||
{
|
||||
for (pin = 2; pin < 28; pin++)
|
||||
{
|
||||
if (fs.existsSync("/sys/class/gpio/gpio" + pin))
|
||||
fs.writeFileSync("/sys/class/gpio/unexport", pin, "utf8");
|
||||
}
|
||||
};
|
||||
|
||||
// Status reporting code
|
||||
// Use this to report missing hardware, plugin or unsupported browser
|
||||
ext._getStatus = function ()
|
||||
{
|
||||
return {status: 2, msg: 'Ready'};
|
||||
};
|
||||
|
||||
ext.set_gpio = function (pin, val)
|
||||
{
|
||||
if (pin === '' || pin < 0 || pin > 27) return;
|
||||
|
||||
var dir = 0, lev;
|
||||
if (val == 'output high') lev = 1;
|
||||
else if (val == 'output low') lev = 0;
|
||||
else dir = 1;
|
||||
|
||||
// check the pin is exported
|
||||
if (!fs.existsSync("/sys/class/gpio/gpio" + pin))
|
||||
|
||||
fs.writeFileSync("/sys/class/gpio/export", pin, "utf8");
|
||||
|
||||
// the ownership of direction takes time to establish, so try this until it succeeds
|
||||
while (true)
|
||||
{
|
||||
try {
|
||||
fs.writeFileSync("/sys/class/gpio/gpio" + pin + "/direction", dir == 0 ? "out" : "in", "utf8");
|
||||
break;
|
||||
}
|
||||
catch (error) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// set the output value
|
||||
if (dir == 0)
|
||||
sendMessage('pin ' + pin + ' = ' + (lev == 1 ? "1" : "0"));
|
||||
fs.writeFileSync("/sys/class/gpio/gpio" + pin + "/value", lev == 1 ? "1" : "0", "utf8");
|
||||
};
|
||||
|
||||
ext.get_gpio = function (pin)
|
||||
{
|
||||
if (pin === '' || pin < 0 || pin > 27) return;
|
||||
|
||||
// check the pin is exported
|
||||
if (!fs.existsSync("/sys/class/gpio/gpio" + pin))
|
||||
fs.writeFileSync("/sys/class/gpio/export", pin);
|
||||
|
||||
// read the pin value
|
||||
var data = fs.readFileSync ("/sys/class/gpio/gpio" + pin + "/value", 'utf8');
|
||||
|
||||
if (data.slice(0,1) == "1") return true;
|
||||
else return false;
|
||||
};
|
||||
|
||||
//my code
|
||||
|
||||
ext.send_broadcast1 = function (bmsg1 ,bmsg2)
|
||||
{
|
||||
sendMessage('broadcast "' + bmsg1 + bmsg2 + '"');
|
||||
};
|
||||
|
||||
ext.send_broadcast2 = function (bmsg1 ,bmsg2,bmsg3)
|
||||
{
|
||||
sendMessage('broadcast "' + bmsg1 + bmsg2 + bmsg3 + '"');
|
||||
};
|
||||
|
||||
ext.send_broadcast0 = function (bmsg1)
|
||||
{
|
||||
sendMessage('broadcast "' + bmsg1 + '"');
|
||||
};
|
||||
|
||||
ext.get_sensorMsgs = function (sensorName)
|
||||
{
|
||||
console.log(sensorName.toLowerCase() + ':' + sensorDict[sensorName.toLowerCase()])
|
||||
//fs.writeFileSync("/home/pi/GrovePiSensors.json", JSON.stringify(sensorDict), "utf8");
|
||||
return sensorDict[sensorName.toLowerCase()];
|
||||
};
|
||||
|
||||
|
||||
// Block and block menu descriptions
|
||||
var descriptor = {
|
||||
blocks: [
|
||||
|
||||
[' ', 'broadcast %m.broadcastType %s', 'send_broadcast1', 'digitalRead','0'],
|
||||
[' ', 'broadcast %m.broadcastType2 %m.ports %s', 'send_broadcast2', 'analogWrite','0','0'],
|
||||
[' ', 'broadcast %m.broadcastType0', 'send_broadcast0', 'take_picture'],
|
||||
['r', '%m.sensorvals value', 'get_sensorMsgs', 'digitalread'],
|
||||
|
||||
|
||||
],
|
||||
menus: {
|
||||
sensorvals: ['digitalread','analogread','moisture','light','sound','rotary','distance','temp','humidty','folder','camera'],
|
||||
broadcastType: ['digitalRead','analogRead','digitalWriteHigh','moisture','light','sound','rotary','distance','button','relay','temp','humidty','folder','take_picture','speak','lcdcol','lcdtxt'],
|
||||
broadcastType2: ['analogWrite','buzzer','led'],
|
||||
broadcastType0: ['take_picture'],
|
||||
ports: ['0','1','2','3','4','5','6','7']
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Register the extension
|
||||
ScratchExtensions.register('GrovePi', descriptor, ext);
|
||||
})();
|
||||
9
Software/Scratch/s2pifiles/setup_scratch2.sh
Executable file
9
Software/Scratch/s2pifiles/setup_scratch2.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
sudo cp extensions.json /usr/lib/scratch2/scratch_extensions/extensions.json
|
||||
sudo cp piGrovePiExtension.js /usr/lib/scratch2/scratch_extensions/piGrovePiExtension.js
|
||||
sudo cp grovepi.html /usr/lib/scratch2/scratch_extensions/grovepi.html
|
||||
sudo cp -u grovepi.png /usr/lib/scratch2/medialibrarythumbnails/grovepi.png
|
||||
pip install scratchpy
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
Software/Scratch/scratch_functions.png
Normal file
BIN
Software/Scratch/scratch_functions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
11
Software/Scratch/start_local_scratch.sh
Normal file
11
Software/Scratch/start_local_scratch.sh
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
echo "ensuring only one instance of GoPiGo3 Scratch Communicator"
|
||||
sudo pkill -f GrovePiScratch.py
|
||||
sudo python /home/pi/Dexter/GrovePi/Software/Scratch/GrovePiScratch.py &
|
||||
|
||||
echo "starting Scratch"
|
||||
scratch /home/pi/Dexter/lib/Dexter/Scratch_GUI/new.sb
|
||||
|
||||
echo "killing background process"
|
||||
sudo pkill -f GrovePiScratch.py
|
||||
echo "background process killed"
|
||||
347
Software/Scratch/websocket_server.py
Normal file
347
Software/Scratch/websocket_server.py
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
# Author: Johan Hanssen Seferidis
|
||||
# License: MIT
|
||||
|
||||
import re
|
||||
import sys
|
||||
import struct
|
||||
from base64 import b64encode
|
||||
from hashlib import sha1
|
||||
import logging
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
from SocketServer import ThreadingMixIn, TCPServer, StreamRequestHandler
|
||||
else:
|
||||
from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig()
|
||||
|
||||
'''
|
||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
||||
| |1|2|3| |K| | |
|
||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
| Extended payload length continued, if payload len == 127 |
|
||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
||||
| Payload Data continued ... |
|
||||
+---------------------------------------------------------------+
|
||||
'''
|
||||
|
||||
FIN = 0x80
|
||||
OPCODE = 0x0f
|
||||
MASKED = 0x80
|
||||
PAYLOAD_LEN = 0x7f
|
||||
PAYLOAD_LEN_EXT16 = 0x7e
|
||||
PAYLOAD_LEN_EXT64 = 0x7f
|
||||
|
||||
OPCODE_CONTINUATION = 0x0
|
||||
OPCODE_TEXT = 0x1
|
||||
OPCODE_BINARY = 0x2
|
||||
OPCODE_CLOSE_CONN = 0x8
|
||||
OPCODE_PING = 0x9
|
||||
OPCODE_PONG = 0xA
|
||||
|
||||
|
||||
# -------------------------------- API ---------------------------------
|
||||
|
||||
class API():
|
||||
|
||||
def run_forever(self):
|
||||
try:
|
||||
logger.info("Listening on port %d for clients.." % self.port)
|
||||
self.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
self.server_close()
|
||||
logger.info("Server terminated.")
|
||||
except Exception as e:
|
||||
logger.error(str(e), exc_info=True)
|
||||
exit(1)
|
||||
|
||||
def new_client(self, client, server):
|
||||
pass
|
||||
|
||||
def client_left(self, client, server):
|
||||
pass
|
||||
|
||||
def message_received(self, client, server, message):
|
||||
pass
|
||||
|
||||
def set_fn_new_client(self, fn):
|
||||
self.new_client = fn
|
||||
|
||||
def set_fn_client_left(self, fn):
|
||||
self.client_left = fn
|
||||
|
||||
def set_fn_message_received(self, fn):
|
||||
self.message_received = fn
|
||||
|
||||
def send_message(self, client, msg):
|
||||
self._unicast_(client, msg)
|
||||
|
||||
def send_message_to_all(self, msg):
|
||||
self._multicast_(msg)
|
||||
|
||||
|
||||
# ------------------------- Implementation -----------------------------
|
||||
|
||||
class WebsocketServer(ThreadingMixIn, TCPServer, API):
|
||||
"""
|
||||
A websocket server waiting for clients to connect.
|
||||
|
||||
Args:
|
||||
port(int): Port to bind to
|
||||
host(str): Hostname or IP to listen for connections. By default 127.0.0.1
|
||||
is being used. To accept connections from any client, you should use
|
||||
0.0.0.0.
|
||||
loglevel: Logging level from logging module to use for logging. By default
|
||||
warnings and errors are being logged.
|
||||
|
||||
Properties:
|
||||
clients(list): A list of connected clients. A client is a dictionary
|
||||
like below.
|
||||
{
|
||||
'id' : id,
|
||||
'handler' : handler,
|
||||
'address' : (addr, port)
|
||||
}
|
||||
"""
|
||||
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True # comment to keep threads alive until finished
|
||||
|
||||
clients = []
|
||||
id_counter = 0
|
||||
|
||||
def __init__(self, port, host='127.0.0.1', loglevel=logging.WARNING):
|
||||
logger.setLevel(loglevel)
|
||||
self.port = port
|
||||
TCPServer.__init__(self, (host, port), WebSocketHandler)
|
||||
|
||||
def _message_received_(self, handler, msg):
|
||||
self.message_received(self.handler_to_client(handler), self, msg)
|
||||
|
||||
def _ping_received_(self, handler, msg):
|
||||
handler.send_pong(msg)
|
||||
|
||||
def _pong_received_(self, handler, msg):
|
||||
pass
|
||||
|
||||
def _new_client_(self, handler):
|
||||
self.id_counter += 1
|
||||
client = {
|
||||
'id': self.id_counter,
|
||||
'handler': handler,
|
||||
'address': handler.client_address
|
||||
}
|
||||
self.clients.append(client)
|
||||
self.new_client(client, self)
|
||||
|
||||
def _client_left_(self, handler):
|
||||
client = self.handler_to_client(handler)
|
||||
self.client_left(client, self)
|
||||
if client in self.clients:
|
||||
self.clients.remove(client)
|
||||
|
||||
def _unicast_(self, to_client, msg):
|
||||
to_client['handler'].send_message(msg)
|
||||
|
||||
def _multicast_(self, msg):
|
||||
for client in self.clients:
|
||||
self._unicast_(client, msg)
|
||||
|
||||
def handler_to_client(self, handler):
|
||||
for client in self.clients:
|
||||
if client['handler'] == handler:
|
||||
return client
|
||||
|
||||
|
||||
class WebSocketHandler(StreamRequestHandler):
|
||||
|
||||
def __init__(self, socket, addr, server):
|
||||
self.server = server
|
||||
StreamRequestHandler.__init__(self, socket, addr, server)
|
||||
|
||||
def setup(self):
|
||||
StreamRequestHandler.setup(self)
|
||||
self.keep_alive = True
|
||||
self.handshake_done = False
|
||||
self.valid_client = False
|
||||
|
||||
def handle(self):
|
||||
while self.keep_alive:
|
||||
if not self.handshake_done:
|
||||
self.handshake()
|
||||
elif self.valid_client:
|
||||
self.read_next_message()
|
||||
|
||||
def read_bytes(self, num):
|
||||
# python3 gives ordinal of byte directly
|
||||
bytes = self.rfile.read(num)
|
||||
if sys.version_info[0] < 3:
|
||||
return map(ord, bytes)
|
||||
else:
|
||||
return bytes
|
||||
|
||||
def read_next_message(self):
|
||||
try:
|
||||
b1, b2 = self.read_bytes(2)
|
||||
except ValueError as e:
|
||||
b1, b2 = 0, 0
|
||||
|
||||
fin = b1 & FIN
|
||||
opcode = b1 & OPCODE
|
||||
masked = b2 & MASKED
|
||||
payload_length = b2 & PAYLOAD_LEN
|
||||
|
||||
if not b1:
|
||||
logger.info("Client closed connection.")
|
||||
self.keep_alive = 0
|
||||
return
|
||||
if opcode == OPCODE_CLOSE_CONN:
|
||||
logger.info("Client asked to close connection.")
|
||||
self.keep_alive = 0
|
||||
return
|
||||
if not masked:
|
||||
logger.warn("Client must always be masked.")
|
||||
self.keep_alive = 0
|
||||
return
|
||||
if opcode == OPCODE_CONTINUATION:
|
||||
logger.warn("Continuation frames are not supported.")
|
||||
return
|
||||
elif opcode == OPCODE_BINARY:
|
||||
logger.warn("Binary frames are not supported.")
|
||||
return
|
||||
elif opcode == OPCODE_TEXT:
|
||||
opcode_handler = self.server._message_received_
|
||||
elif opcode == OPCODE_PING:
|
||||
opcode_handler = self.server._ping_received_
|
||||
elif opcode == OPCODE_PONG:
|
||||
opcode_handler = self.server._pong_received_
|
||||
else:
|
||||
logger.warn("Unknown opcode %#x." + opcode)
|
||||
self.keep_alive = 0
|
||||
return
|
||||
|
||||
if payload_length == 126:
|
||||
payload_length = struct.unpack(">H", self.rfile.read(2))[0]
|
||||
elif payload_length == 127:
|
||||
payload_length = struct.unpack(">Q", self.rfile.read(8))[0]
|
||||
|
||||
masks = self.read_bytes(4)
|
||||
decoded = ""
|
||||
for char in self.read_bytes(payload_length):
|
||||
char ^= masks[len(decoded) % 4]
|
||||
decoded += chr(char)
|
||||
opcode_handler(self, decoded)
|
||||
|
||||
def send_message(self, message):
|
||||
self.send_text(message)
|
||||
|
||||
def send_pong(self, message):
|
||||
self.send_text(message, OPCODE_PONG)
|
||||
|
||||
def send_text(self, message, opcode=OPCODE_TEXT):
|
||||
"""
|
||||
Important: Fragmented(=continuation) messages are not supported since
|
||||
their usage cases are limited - when we don't know the payload length.
|
||||
"""
|
||||
|
||||
# Validate message
|
||||
if isinstance(message, bytes):
|
||||
message = try_decode_UTF8(message) # this is slower but ensures we have UTF-8
|
||||
if not message:
|
||||
logger.warning("Can\'t send message, message is not valid UTF-8")
|
||||
return False
|
||||
elif isinstance(message, str) or isinstance(message, unicode):
|
||||
pass
|
||||
else:
|
||||
logger.warning('Can\'t send message, message has to be a string or bytes. Given type is %s' % type(message))
|
||||
return False
|
||||
|
||||
header = bytearray()
|
||||
payload = encode_to_UTF8(message)
|
||||
payload_length = len(payload)
|
||||
|
||||
# Normal payload
|
||||
if payload_length <= 125:
|
||||
header.append(FIN | opcode)
|
||||
header.append(payload_length)
|
||||
|
||||
# Extended payload
|
||||
elif payload_length >= 126 and payload_length <= 65535:
|
||||
header.append(FIN | opcode)
|
||||
header.append(PAYLOAD_LEN_EXT16)
|
||||
header.extend(struct.pack(">H", payload_length))
|
||||
|
||||
# Huge extended payload
|
||||
elif payload_length < 18446744073709551616:
|
||||
header.append(FIN | opcode)
|
||||
header.append(PAYLOAD_LEN_EXT64)
|
||||
header.extend(struct.pack(">Q", payload_length))
|
||||
|
||||
else:
|
||||
raise Exception("Message is too big. Consider breaking it into chunks.")
|
||||
return
|
||||
|
||||
self.request.send(header + payload)
|
||||
|
||||
def handshake(self):
|
||||
message = self.request.recv(1024).decode().strip()
|
||||
upgrade = re.search('\nupgrade[\s]*:[\s]*websocket', message.lower())
|
||||
if not upgrade:
|
||||
self.keep_alive = False
|
||||
return
|
||||
key = re.search('\n[sS]ec-[wW]eb[sS]ocket-[kK]ey[\s]*:[\s]*(.*)\r\n', message)
|
||||
if key:
|
||||
key = key.group(1)
|
||||
else:
|
||||
logger.warning("Client tried to connect but was missing a key")
|
||||
self.keep_alive = False
|
||||
return
|
||||
response = self.make_handshake_response(key)
|
||||
self.handshake_done = self.request.send(response.encode())
|
||||
self.valid_client = True
|
||||
self.server._new_client_(self)
|
||||
|
||||
def make_handshake_response(self, key):
|
||||
return \
|
||||
'HTTP/1.1 101 Switching Protocols\r\n'\
|
||||
'Upgrade: websocket\r\n' \
|
||||
'Connection: Upgrade\r\n' \
|
||||
'Sec-WebSocket-Accept: %s\r\n' \
|
||||
'\r\n' % self.calculate_response_key(key)
|
||||
|
||||
def calculate_response_key(self, key):
|
||||
GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
||||
hash = sha1(key.encode() + GUID.encode())
|
||||
response_key = b64encode(hash.digest()).strip()
|
||||
return response_key.decode('ASCII')
|
||||
|
||||
def finish(self):
|
||||
self.server._client_left_(self)
|
||||
|
||||
|
||||
def encode_to_UTF8(data):
|
||||
try:
|
||||
return data.encode('UTF-8')
|
||||
except UnicodeEncodeError as e:
|
||||
logger.error("Could not encode data to UTF-8 -- %s" % e)
|
||||
return False
|
||||
except Exception as e:
|
||||
raise(e)
|
||||
return False
|
||||
|
||||
|
||||
def try_decode_UTF8(data):
|
||||
try:
|
||||
return data.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
except Exception as e:
|
||||
raise(e)
|
||||
|
||||
123
Software/Scratch/wstosgh.py
Executable file
123
Software/Scratch/wstosgh.py
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
import array
|
||||
import itertools
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from websocket_server import WebsocketServer
|
||||
|
||||
|
||||
def rcv_from_sgh():
|
||||
global s,c,server
|
||||
dataPrevious = ""
|
||||
while True:
|
||||
print "listening for data from sgh"
|
||||
# time.sleep(1)
|
||||
data = dataPrevious + c.recv(8192)
|
||||
print "data length" , len(data)
|
||||
if data != "":
|
||||
print "Data received from sgh", data
|
||||
#print ("datalen: %s", len(data))
|
||||
|
||||
if len(data) > 0: # Connection still valid so process the data received
|
||||
|
||||
dataIn = data
|
||||
|
||||
datawithCAPS = data
|
||||
# dataOut = ""
|
||||
dataList = [] # used to hold series of broadcasts or sensor updates
|
||||
dataPrefix = "" # data to be re-added onto front of incoming data
|
||||
while len(dataIn) > 0: # loop thru data
|
||||
if len(dataIn) < 4: # If whole length not received then break out of loop
|
||||
# print "<4 chrs received"
|
||||
dataPrevious = dataIn # store data and tag it onto next data read
|
||||
break
|
||||
sizeInfo = dataIn[0:4]
|
||||
size = struct.unpack(">L", sizeInfo)[0] # get size of Scratch msg
|
||||
# print "size:", size
|
||||
if size > 0:
|
||||
# print dataIn[4:size + 4]
|
||||
dataMsg = dataIn[4:size + 4].lower() # turn msg into lower case
|
||||
|
||||
if len(dataMsg) < size: # if msg recieved is too small
|
||||
# print "half msg found"
|
||||
# print size, len(dataMsg)
|
||||
dataPrevious = dataIn # store data and tag it onto next data read
|
||||
break
|
||||
#print "msg:",dataMsg
|
||||
dataList.append(dataMsg)
|
||||
dataIn = dataIn[size + 4:] # cut data down that's been processed
|
||||
|
||||
# print "previous:", dataPrevious
|
||||
print "datalist:",dataList
|
||||
for msg in dataList:
|
||||
#print "msg:",msg[0:13]
|
||||
if msg[0:13] == 'sensor-update':
|
||||
msgsplit = msg[14:].replace('"','').split(' ')
|
||||
print "split",msgsplit
|
||||
#for loop in range(int(len(msgsplit) / 2)):
|
||||
# server.send_message_to_all(msgsplit[loop * 2] + ':' + msgsplit[(loop * 2) + 1])
|
||||
server.send_message_to_all(msgsplit[0] + ':' + msgsplit[1])
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
|
||||
|
||||
# Called for every client connecting (after handshake)
|
||||
def new_client(client, server):
|
||||
print("New client connected and was given id %d" % client['id'])
|
||||
#server.send_message_to_all("Hey all, a new client has joined us")
|
||||
|
||||
|
||||
# Called for every client disconnecting
|
||||
def client_left(client, server):
|
||||
print("Client(%d) disconnected" % client['id'])
|
||||
|
||||
|
||||
# Called when a client sends a message
|
||||
def message_received(client, server, message):
|
||||
if len(message) > 200:
|
||||
message = message[:200]+'..'
|
||||
print("Client(%d) said: %s" % (client['id'], message))
|
||||
dataOut = message
|
||||
|
||||
n = len(dataOut)
|
||||
b = (chr((n >> 24) & 0xFF)) + (chr((n >> 16) & 0xFF)) + (chr((n >> 8) & 0xFF)) + (
|
||||
chr(n & 0xFF))
|
||||
c.send(b + dataOut)
|
||||
print "Data sent to sgh", dataOut
|
||||
|
||||
|
||||
|
||||
|
||||
# For Scratch 3 handle long as int
|
||||
if sys.version > '3':
|
||||
long = int
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
s = socket.socket() #Create a socket object
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
#host = socket.gethostname() #Get the local machine name
|
||||
port = 42001 # Reserve a port for your service
|
||||
s.bind(('127.0.0.1',port)) #Bind to the port
|
||||
|
||||
s.listen(5) #Wait for the client connection
|
||||
print "wstosgh listening to scratchGPIO_handler"
|
||||
c,addr = s.accept() #Establish a connection with the client
|
||||
print "Got connection from ScratchGPIOHandler", addr
|
||||
|
||||
|
||||
PORT=8000
|
||||
server = WebsocketServer(PORT)
|
||||
server.set_fn_new_client(new_client)
|
||||
server.set_fn_client_left(client_left)
|
||||
server.set_fn_message_received(message_received)
|
||||
d = threading.Thread(name='rcv_from_sgh', target=rcv_from_sgh)
|
||||
d.setDaemon(True)
|
||||
d.start()
|
||||
server.run_forever()
|
||||
Loading…
Add table
Add a link
Reference in a new issue