Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Examples/Examples.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Examples

This folder contains a series of minimum functional code examples that should allow for a quick start to development. While it is likely possible to start at the final one and understand the code, it may be easier to progress through them in order. The intended order is `Fundamentals` > `Skeleton Code` > `With Motors`
This folder contains a series of minimum functional code examples that should allow for a quick background for further development. The intended order is `Fundamentals` > `Skeleton Code` > `With Motors`. For more complete examples, see the `Library` folder.

Sample 3d Models are collected on the [CADExamples](CADExamples.md) page.

Expand Down
2 changes: 1 addition & 1 deletion Examples/WithMotors/WithMotors.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This version adds the ability to control the motors via the Adafruit motor shiel
To download these files, a simple script has been provided. Starting in the `var/www/html` directory, make sure that it is empty using `rm -rf var/www/html/*`. Note, this command will **DELETE EVERYTHING** in that directory. Next, download the script via

```
wget https://raw.githubusercontent.com/Gautreaux/PiSetup/master/Examples/WithMotors/downloadScript.sh
wget https://raw.githubusercontent.com/Aggie-Robotics/PiSetup/master/Examples/WithMotors/downloadScript.sh
```

then, run the script to download the remaining files:
Expand Down
10 changes: 5 additions & 5 deletions Examples/WithMotors/downloadScript.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
wget https://raw.githubusercontent.com/Gautreaux/PiSetup/master/Examples/WithMotors/index.html
wget https://raw.githubusercontent.com/Gautreaux/PiSetup/master/Examples/WithMotors/motorInterface.py
wget https://raw.githubusercontent.com/Gautreaux/PiSetup/master/Examples/WithMotors/script.js
wget https://raw.githubusercontent.com/Gautreaux/PiSetup/master/Examples/WithMotors/server.py
wget https://raw.githubusercontent.com/Gautreaux/PiSetup/master/Examples/WithMotors/styles.css
wget https://raw.githubusercontent.com/Aggie-Robotics/PiSetup/master/Examples/WithMotors/index.html
wget https://raw.githubusercontent.com/Aggie-Robotics/PiSetup/master/Examples/WithMotors/motorInterface.py
wget https://raw.githubusercontent.com/Aggie-Robotics/PiSetup/master/Examples/WithMotors/script.js
wget https://raw.githubusercontent.com/Aggie-Robotics/PiSetup/master/Examples/WithMotors/server.py
wget https://raw.githubusercontent.com/Aggie-Robotics/PiSetup/master/Examples/WithMotors/styles.css

chmod u+r+x index.html
chmod u+r+x motorInterface.py
Expand Down
21 changes: 21 additions & 0 deletions Library/About.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Library

This folder contains sample/started code to assist with the development of your system. Download scripts are provided for grouped components.

# Scripts

Use the scripts to collect code by:

1. Navigate to folder
1. (Optional) empty the folder `rm -rf *`
1. WARNING: this will delete everything
1. Download the script: `wget https://raw.githubusercontent.com/Aggie-Robotics/PiSetup/master/Examples/WithMotors/<scriptName>.sh`
1. Allow the script permission to run: `chmod u+r+x <scriptName>.sh`

1. Run the script: `./<scriptName>.sh`

## Single Joystick

Name: `singleJoy.sh`

A simple controller with a single joystick. Script contains a full python, javascript, and html (index.html) ready to deploy. Also implements the virtual motor interface.
37 changes: 37 additions & 0 deletions Library/abstractMotorInterface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from abc import ABC, abstractmethod

#abstract class for a motor interface
class MotorInterface(ABC):
#constructor
def __init__(self):
super().__init__()

#destructor
@abstractmethod
def __del__(self):
raise NotImplementedError

#return the value associated with the key
#i.e. return the throttle of the motor with that index
@abstractmethod
def __getitem__(self, key):
raise NotImplementedError

#set the value associated with the key
#i.e. set the value of the throttle for the motor at index i
# the throttle value should be in the range [-1,1] inclusive or None
@abstractmethod
def __setitem__(self, key, value):
raise NotImplementedError

#stop all the motors
@abstractmethod
def stop(self):
pass

#what is the string representation of the motors
def __str__(self):
return "abstract MotorInterface"



49 changes: 49 additions & 0 deletions Library/adaMotorInterface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from abstractMotorInterface import MotorInterface

# interface for the adafruit motor library via motor interface
class AdaMotorInterface(MotorInterface):
kit = None #shared across all instances of AdaMotorInterface

def __init__(self):
try:
kit == None
except UnboundLocalError:
#do not load the interface until needed
from adafruit_motorkit import MotorKit
kit = MotorKit()

self.motors = [kit.motor1, kit.motor2, kit.motor3, kit.motor4]
self.stop()

def __del__(self):
self.stop()

def __getitem__(self, key):
return self.motors[key].throttle

def __setitem__(self, key, value):
if(value != None and (value < -1 or value > 1)):
raise ValueError(f"Illegal throttle Value. Should be float in inclusive range [-1,1] got {value}")
self.motors[key].throttle = value

def stop(self):
#stop all the motors
for i in range(len(self.motors)):
self.motors[i].throttle = None

def __str__(self):
s = "["
for i in range(len(self.motors)):
try:
t = self.motors[i].throttle
if(t > 0):
s += f" {t:4.2f}" #4.2f formats how the number is printed
elif(t < 0):
s += f"{t:4.2f}"
else:
s += " 0"
except TypeError:
s += " None"
if(i != len(self.motors)-1):
s += ","
return s + "]"
55 changes: 55 additions & 0 deletions Library/connectionBase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class ConnectionBase {
constructor(host, port) {
this.host = host
this.port = port
}

connect() {
let s = "ws://" + this.host + ":" + this.port;
console.log("Client started. Tying connection on: " + s);

//create the socket object
this.socket = new WebSocket(s);

//add the callbacks as necessary
this.socket.onopen = this.onSocketOpen; //called when connection established
this.socket.onerror = this.onSocketError; //called when error
this.socket.onmessage = this.onSocketReceive; //called each message received
this.socket.onclose = this.onSocketClose; //called each close
}

//called when the socket establishes a connection to the server
onSocketOpen(event) {
console.log("Socket Opened");
}

//called on a socket error
// most commonly, this is due to connection timeout
//most errors also cause the socket to close,
// calling onclose callback after the onerror callback
onSocketError(event) {
console.log("Socket Error");
}

//called when the socket is closed
onSocketClose(event) {
console.log("Socket Closed");
//now that the socket is invalid, remove it
this.socket = null;
}

//called when the socket receives data
onSocketReceive(event) {
console.log("Message Received '" + event.data + "'");
}

//sends the input parameter into the socket
send(message) {
if (this.socket == null) {
console.log("Cannot send message, socket is not connected.");
}
else {
this.socket.send(message);
}
}
}
5 changes: 5 additions & 0 deletions Library/joystick.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#joystick{
height: 50px;
width: 50%;
background-color: red;
}
25 changes: 25 additions & 0 deletions Library/joystickBase.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>

<head>
<script src="connectionBase.js"></script>
<script src="statusBar.js"></script>
<script src="statusConnection.js"></script>

<script src="joysticksController.js"></script>

<!-- This file is generated by the python server when it launches -->
<script src="hostPort.js"></script>

<!-- TODO - joysticks and status bar styles-->
<link rel="stylesheet" type="text/css" href="joystick.css">

<!-- Finally load the main file-->
<script src="joysticksGeneral.js"></script>
</head>
<body onload="initFunction()">
<div id="statusBar"></div>
<div id="joystick"></div>
Open the console:
<br>
ctrl+shift+j on chrome
</body>
6 changes: 6 additions & 0 deletions Library/joysticksController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Joystick{
constructor(element) {
this.element = element
return this
}
}
9 changes: 9 additions & 0 deletions Library/joysticksGeneral.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function initFunction(){
let j = document.getElementById("joystick")
let joystick = new Joystick(j)

let s = document.getElementById("statusBar");
let statusBar = new StatusBar(s)
connection = new StatusConnection(myHost, myPort, statusBar);

}
130 changes: 130 additions & 0 deletions Library/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import asyncio
import websockets
import sys
from signal import SIGINT, SIGTERM
from virtualMotorInterface import VirtualMotorInterface
from adaMotorInterface import AdaMotorInterface
from time import sleep

DEFAULT_PORT = "8765"
DEFAULT_HOST = "192.168.4.1"


#function builder utilizing lambda capture
# allows the inner function access to the args of the outer function
def buildConnectionHandler(interface):
#called once per connection
async def connectionHandler(websocket, path):
try:
print("New connection received.")
async for message in websocket:
try:
assert(message[0] == "M")
i = int(message[1])
assert(i in [0,1,2,3]) #should preempt the IndexError
if(message[2:] == "None"):
v = None
else:
v = float(message[2:])
interface[i] = v
# print(interface) #print the updated interface values
except (ValueError, AssertionError, IndexError):
#fall back onto the echo functionality
print(f"Received new message '{message}'")
await websocket.send(f"ECHO:{message}")

#this part is executed after the websocket is closed by the client
#if the socket is closed by the server (i.e. ctrl-c) this is not executed
print("Connection lost.")
interface.stop() #stop the motors (for safety)
print(interface)
except websockets.exceptions.ConnectionClosedError:
#when the websocket is closed in some abnormal fashion
print("Connection closed abnormally.")
interface.stop()
return connectionHandler


#if the python module has been launched as the main one
if __name__ == "__main__":
#first, we allow for command line arguments formatted such:
# server.py <hostname> <port> <motor-interface-value>
# where hostname and port are optional
try:
port = sys.argv[2]
except IndexError:
#The user did not provide an port argument
port = DEFAULT_PORT

try:
host = sys.argv[1]
except IndexError:
host = DEFAULT_HOST

try:
motorInterfaceType = int(sys.argv[3])
except (IndexError, ValueError):
motorInterfaceType = 0

#next, write the host:port combo to a file that js can read.
try:
with open("hostPort.js", 'w') as fileOut:
fileOut.write(f"myHost = '{host}';")
fileOut.write(f"myPort = '{port}';")
#with open() as syntax automatically closes the file on exit
except (FileNotFoundError, FileExistsError, OSError) as e:
print("Something went wrong trying to write the hostport.js file. Type:" + str(type(e)))

print(f"Starting Server at {host}:{port}")

if(motorInterfaceType == 1):
print("Using the AdaMotorInterface")
interface = AdaMotorInterface()
else:
print("Using the VirtualMotorInterface")
interface = VirtualMotorInterface()

#This outer while loop "solves" the race condition.
#More on this at the end of the file.
failCtr = 5
while(True):
#start the server
server = websockets.serve(buildConnectionHandler(interface), host, port)

try:
#since the connection is asynchronous, we need to hold the program until its finished
# under normal circumstances, this means we wait forever
asyncio.get_event_loop().run_until_complete(server)
print("server started successfully.")
failCtr = -1
asyncio.get_event_loop().run_forever()
#any code down here would not be reachable until the server closes its socket
# under normal circumstances, this means this code is unreachable

except KeyboardInterrupt:
#the interrupt was fired (ctrl-c), time to exit
#note, the interrupt wont happen till the next async event happens
print("Exiting via KeyboardInterrupt")
exit(-1)
except OSError as e:
failCtr+=1
if(failCtr > 5):
print("The server startup failed too many times, allowing exception to fire.")
print("\tThis may be a result of too many packages slowing down the loading of the network interface.")
raise e #allow the error to fire.
else:
sleep(5)

#About the race condition:
#The pi has to load packages that give it the ability to accept incoming connections
#These packages take time to load (both on the pi and within the actual wifi chip)
#In other threads, the pi continues loading other packages
#One of these other packages is the rc.local script
#Which launches this python program in the background
#It is a frequent occurrence that rc.local happens before the wifi fully loads
#Which causes a bind failure and an exception

#In the long term, it would be better to register the server as a service that is dependent on wifi
#Thus, the kernel will not launch it until the wifi is ready, preventing error
#Futhermore, in the case of a crash, the kernel should be able to relaunch the program

1 change: 1 addition & 0 deletions Library/singleJoy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ECHO Not Implemented
Loading