*Note items in the list may have been re-listed later and funded. Some projects may have been ahead of their time as the Pi wasn't popular enough.
Monday, September 28, 2015
List of 18 Crowdfunded Raspberry Pi Boards That Didn't Make It
*Note items in the list may have been re-listed later and funded. Some projects may have been ahead of their time as the Pi wasn't popular enough.
Sunday, September 27, 2015
Raspberry Pi Relay Boards Compared
A Comparison of some add-on/hat relay boards that are fully compatible with the raspberry pi. This list is only for boards that connect directly to the Raspberry Pi's GPIO.
If you have one of these and you recommend it. +1 it :-) to help others. Comment below if you don't see one on here and I will add it. Please include a link to the seller. All prices are priced as of 09-28-15 and do NOT include shipping. Some Items may not ship to and from the US as it includes foreign shops.
Board | Relays | Extra Inputs |
Extra Outputs |
Stack-able | Size | Pi Ver. | Price (usd) |
+1 |
---|---|---|---|---|---|---|---|---|
PiFace | 2 | 8 | 8 | YES(8) | A/B | A/B | $35 | |
PiFace Rev 2 | 2 | 8 | 8 | - | B+ | B+/2 | $44 | |
PiFace Relay+ | 4 | - | - | YES(8) | A+ | A+/B+/2 | $46 | |
SeeedStudio Hat | 4 | - | - | YES(?) | * | * | $32 | |
Plate Shield | 4 | - | - | - | B | A/B | $35 | |
LN Digital Interface | 2 | 8 | 8 | - | A+ | A+/B+/2 | $35 | |
MiniPiio | 2 | - | - | - | 1/2 | A/B | $18 | |
OpenElectronics I/O Exp Board |
8 | 8 | - | YES(8) | Large | A/B | $30 | |
BCRobotics Relay Hat | 4 | - | - | - | A+ | A+/B+/2 | $19 | |
Temperature Controller Plate | 2 | TypeK Therm. |
4 | YES | B | A/B | $50 | |
2803 4 Relay | 4 | - | 2 Stepper Motor |
YES | B | A/B | $34 | |
2803 2 Relay | 2 | - | 2 Stepper Motor |
YES | 1/2 | A/B | $25 | |
Ps-6 Relay Hat | 6 | - | - | - | A+ | A+/B+/2 | $50 | |
Slice of Relay | 2 | - | - | - | 1/2 | A/B | $17 | |
Rpi+ 2Relay Hat | 2 | 1-Wire | - | - | A+ | A+/B+/2 | $23 | |
OssoPi I/O Board | 8 | 8 | - | - | X-Large | A+/B+/2 | $103 | |
ER-OPTO Relay4 Hat | 4 | - | - | YES | A+ | A+/B+/2 | $22 |
Sunday, September 20, 2015
5 Ways to Secure Your Raspberry Pi's Websocket Server.
Websocket's are a great way to transmit real time data. I use them quite often from transfering picam to the web, controlling lights, controlling a raspberry pi picture frame, and controlling my sprinklers(pending post). ALL with the Raspberry Pi. But if it's done without security someone somewhere can tap in and take control. Things could end up pretty bad if it's used to stream video and control lighting.
1. WSS (WebSockets over SSL/TLS).
First I strongly strongly recommend SSL/TLS encryption. Just like https it encrypts the traffic between the client and server. Nothing should be transmitted in plain text to a client. Anyone smart enough to be listening on the connection can probably find out how your websocket protocol is working and take control. For examples on how to create a wss capable server refer to:- NodeJS
- Autobahn Python (Twisted):
2. Query String Authentication
Creating a connection connection to your websocket server by your connection parameters (sometimes a username/password) and appending it as a Query String to the end of the connection:var websocket = new Websocket("wss://rpi?user=Eben&password=Upton");
I don't recommend doing it that way especially if you don't have a secure SSL/TLS connection.
3. CHAP Authentication (Challenge Response Authentication)
I was not liking the query string method and knowing that just securing the channel using SSL/TLS wasn't enough I tried implementing a CHAP authentication routine for my Autobahn WS server on my Pi.
CHAP explained on wikipedia. CHAP is a 3 way handshake:
- Client Connects to the websocket server, server then sends a challenge string (random characters of random or set length) to client.
- Client responds with the hash of the challenge+'shared secret'
- Server calculates the challenge it sent and the 'shared secret' it has locally and compares the client's hash to it's own and either authenticates (adds it to approved clients) or drops the client.
The javascript library I used for SHA256 is found here
4. Basic/Digest/Forms Authentication
Basic/Digest is a common way the web authenticates it's clients. Uses a username and password authentication and there several APIs that support it. Explanation of Digest Auth
This can be done before a connection to the websocket server is made using a post to the server. The server can then add the client to the list of accepted connections.
5. Auth Header
*This only works if the client is NOT a webpage. Just like basic/digest authentication some websocket servers can read custom headers. Through the current web api for websockets you are not able to modify the headers in any way. But your client may able to change his auth header through nodejs, autobahn, and socketrocket
Bonus. Using A Third Party Authentication
One way to authenticate it use a third party authentication service.
- Autobahn's github has an example of using Mozilla Persona,
- Node js' authentication strategies (not with websockets but may be possible to implement)
Please share
Sunday, September 6, 2015
Raspberry Pi: How to create your own WebSocket API - Part 2
Part 1: Setup xcode project
Now that I've set up the server to handle request from the client we can set up the iOS client. Either you can start with a blank project or fork/clone my github example.
- Now install the SocketRocket websocket client library. There are a couple ways to do it. I used cocoapods, follow the directions for installation here. I setup a basic uitableview within my viewcontroller.
- Link your tableview to your viewcontroller and call it tableView.
- Setup your viewDidLoad
static NSString *cellIdent = @"cellIdent"; NSMutableArray *outputs; //global array of our outputs for the piface NSMutableArray *inputs; //global array of our inputs - (void)viewDidLoad { [super viewDidLoad]; _webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"wss://192.168.5.21:9000" ]]; _webSocket.delegate = self; outputs = [@[@0,@0,@0,@0,@0,@0,@0,@0] mutableCopy];//pre populate our array with zeros inputs = [@[@0,@0,@0,@0,@0,@0,@0,@0] mutableCopy];// " self.tableView.dataSource = self; //don't forget to set your delegate for your datasource //we have a custom xib for our tableview cell. register it here [self.tableView registerNib:[UINib nibWithNibName:@"OutputTableViewCell" bundle:nil] forCellReuseIdentifier:cellIdent]; [self.webSocket open]; //open our websocket }
We need to set our socket rocket delegate so we can handle messages from the server:
-(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{ [self performSelector:@selector(connectWS) withObject:nil afterDelay:5]; } -(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{ [self performSelector:@selector(connectWS) withObject:nil afterDelay:5]; } -(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{ NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; [self parseJSON:json]; } -(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload{ } -(void)webSocketDidOpen:(SRWebSocket *)webSocket{ } -(void)parseJSON:(NSDictionary*)jsonObj{ if([jsonObj isKindOfClass:[NSArray class]]){ for (NSDictionary *dict in jsonObj) { if([dict objectForKey:@"Outputs"]) [self parseOutputs:[dict objectForKey:@"Outputs"]]; if([dict objectForKey:@"Inputs"]) [self parseInputs:[dict objectForKey:@"Inputs"]]; } } else{ if([jsonObj objectForKey:@"Outputs"]) [self parseOutputs:[jsonObj objectForKey:@"Outputs"]]; if([jsonObj objectForKey:@"Inputs"]) [self parseInputs:[jsonObj objectForKey:@"Inputs"]]; } } -(void)parseOutputs:(NSString*)outputStr{ for(int index=0; index < outputStr.length; index++){ NSString *val = [outputStr substringWithRange:NSMakeRange(index, 1)]; [outputs setObject:@([val boolValue]) atIndexedSubscript:index]; } [self.tableView reloadData]; } -(void)parseInputs:(NSString*)inputStr{ for(int index=0; index < inputStr.length; index++){ NSString *val = [inputStr substringWithRange:NSMakeRange(index, 1)]; [inputs setObject:@([val boolValue]) atIndexedSubscript:index]; } [self.tableView reloadData]; }
Note what we just did here:
- Set up the delegates
- webSocketDidOpen
- didRecieveMessage
- didCloseWithCode
- didFailWithError
- Created code to handle the JSON message
- Convert our JSON msg into NSData and then into a NSDictionary
- Parsing Inputs and Outputs and modifying our global arrays for each
Now we set up the table a bit more:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ OutputTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdent forIndexPath:indexPath]; if(indexPath.section==0){ int output_num = [@(indexPath.item) intValue]; [cell setLabelText:[NSString stringWithFormat:@"Output: %d",output_num]]; [cell setOutputStatus:[outputs[indexPath.item] boolValue]]; [cell setOutput:output_num]; [cell setCmd:^(int output) { [self.webSocket send:[NSString stringWithFormat:@"{\"Output\":\"%d\"}",output]]; }]; } else{ int input_num = [@(indexPath.item) intValue]; [cell setLabelText:[NSString stringWithFormat:@"Input: %d",input_num]]; [cell setOutputStatus:[inputs[indexPath.item] boolValue]]; [cell setCmd:nil]; } return cell; } -(void)connectWS{ _webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"wss://10.10.55.21:9000" ]]; _webSocket.delegate = self; [self.webSocket open]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ if(section==0) return outputs.count; if(section ==1) return inputs.count; else return 0; } -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 2; }
When we set the table up we use our custom cell class:
- Cell count/ Section count. We have two types of cells. One we don't have a reaction to button presses (inputs we are reading). And the other we have the outputs we want to control and see status.
- return 2 in our numberOfSectionsInTableView
- return our output/input row count depending on the section number in numberOfRowsInSection
- Connect our cells up to the status stored in the global input/output array
- If it is an output we set up the callback to send a custom message to our WebSocket Server. If you recall we set our server to respond to JSON messages formatted as {"Output",pinNumber}. So we use the IndexPath.Item to determine our position in the inputs/outputs.
- If it is an input we do not set the "setCmd" callback from our button press.
- We also set up a connectWS method to have it attempt to re-connect after 5 if the websocket connection drops. (usually happens when you lose connection to wifi/cellular or if you restart the websocket server.
In this iOS app I won't connect my custom plug code since you haven't set up the server to respond to anything but output commands. We could set it up to be {"Plug":plugNum} in the server if we needed.
Now we only color our output and input buttons ONLY when we know the status. Don't set the color just because you tapped the button, let the server reply with it's status.
For simplistic purposes I didn't decorate it anymore than this in this example. You can be creative and set up your UI however it seems easiest. Note that iOS does not keep this connection open when you close your app. I don't handle it elegantly in my example code. If you wish to have your phone get updates on your sensors while off you will need to set it up as a background service and have it poll a REST-like service you can set up on your Pi with-in the same server app.
Custom API and modules for WebSockets
WebSocket communication is one of the best ways to get realtime data to your client device. Creating custom class modules for each sensor type connected to device can make your code and organized and and your project expandable.
Raspberry Pi: How to create your own WebSocket API - Part 1
A while I posted my very first raspberry pi home automation project (LightSwitchPi) and since then I've learned a lot more python and circuitry. I, being one of many, achieving exactly what the Raspberry Pi Foundation wanted, Education.
Here's a quick tutorial on how to create and form your own home automation api for expansion. I am going to use Autobahn Python for the WebSocket backend and in Part 2 going to build a simple iOS app to communicate with the Pi via WebSockets (SocketRocket).
Part 1: Custom Sensor/Control Modules:
Install the following if you don't have it:- For WebSockets, Autobahn Python :
- sudo apt-get install python-twisted python-pip
- sudo pip install autobahn
- github example
In the github example there are several files to note:
- websocket.py
- The autobahn python based websocket broadcast server with ssl encryption.
- ButtonListener.py
- The lightswitch (module) using the first gen PiFace Relay Board.
- PlugPoller.py
- A wifi plug I built (link here, forgive the sloppy design :-P ) with a uart-to-wifi interface.
- web folder
- simple webpage showing the broadcasts from the websocket server for testing.
First we need to set up our modules. I'm using the term modules to clarify that you can at anytime add to your websocket server "things" your Raspberry pi can do (I.E temperature sensing, motion, etc...). In each module you set it up as a separate thread.
For example ButtonListener is as Follows:
import threading import os import pifacedigitalio from sys import exit from time import sleep import time class Buttons(threading.Thread): cmd = None loop = True button1Callback = None button2Callback = None #set up a callback for each button button2LongPressCallback = None #set up custom stuff if needed
We start by importing our needed libraries and creating a class of the "threading.Thread) type. I am creating several callback variables so that when we instantiate our class in the websocket server we can assign those callbacks to broadcast to our clients the status of the button presses. We continue with the init and the run. Take note that when input[x] is pressed it sets a variable to true and then doesn't react till you release the button. A way to programmatically set a rising/falling edge detection for button presses.
def __init__(self): threading.Thread.__init__(self) self.cmd = pifacedigitalio.PiFaceDigital() self.loop = True def run(self): time_pushed = 0 pushed_1 = False pushed_2 = False inputs = None # toggle button turn relay 1 on while self.loop: sleep(0.05) inputs = self.input_status() outputs = self.output_status() # print inputs, ' ', outputs if inputs[0] == '1' and pushed_1 is not True: pushed_1 = True if self.button1Callback: # json callback for button press self.button1Callback('{"Inputs":"' + self.input_status() + '"}') if inputs[0] == '0' and pushed_1: pushed_1 = False self.cmd.relays[0].toggle() if self.button1Callback: # json callback for button press self.button1Callback('{"Outputs":"' + self.output_status() + '"}') self.button1Callback('{"Inputs":"' + self.input_status() + '"}') if inputs[1] == '1' and pushed_2 is not True: pushed_2 = True time_pushed = time.time() if self.button2Callback: self.button2Callback('{"Inputs":"' + self.input_status() + '"}') if inputs[1] == '0' and pushed_2: time_taken = time.time() - time_pushed pushed_2 = False if(self.button2Callback): self.button2Callback('{"Inputs":"' + self.input_status() + '"}') if time_taken < .5: self.cmd.relays[1].toggle() if self.button2Callback: self.button2Callback('{"Outputs":"' + self.output_status() + '"}') time_pushed = 0 if (time_taken > .5): try: if self.button2LongPressCallback: self.button2LongPressCallback() except: pass time_pushed = 0
We also need to create a stop function so we can exit cleanly from the thread if needed. "def output_status" and "def input_status" are reading the inputs and outputs of the PiFace board and outputting them to a string for our JSON broadcast. "def output_cmd" is a quick method for changing outputs along with outputting the result to the callbacks.
def stop(self): self.loop = False def output_status(self): list1 = self.cmd.output_port.value status = '{0:08b}'.format(list1)[::-1] return status def output_cmd(self, pin, value, local=True): self.cmd.leds[pin].value=value if self.button2Callback and not local: self.button2Callback('{"Outputs":"' + self.output_status() + '"}') return self.output_status() def input_status(self): list1 = self.cmd.input_port.value status = '{0:08b}'.format(list1)[::-1] return status
Part 2: WebSocket Broadcast Server Broadcasting
*(For the complete github code see here)
In a broadcast server you need to keep track of the following:
- Registering clients
- UnRegistering clients
- Broadcasting to clients
- When to broadcast
In my previous post I usually have a polling timer in my websocket server having the server check status of each item and sending it out on change. In this example the "modules" are doing that anyway so we will only need to assign what the callback methods do.
We will put the light switch button module in the init part of the broadcast portion of the server:
We will put the light switch button module in the init part of the broadcast portion of the server:
class BroadcastServerFactory(WebSocketServerFactory): def __init__(self, url, debug=False, debugCodePaths=False): WebSocketServerFactory.__init__(self, url, debug=debug, debugCodePaths=debugCodePaths) self.clients = [] self.lighting = Buttons() self.lighting.button1Callback = self.broadcast self.lighting.button2Callback = self.broadcast self.lighting.start() self.plugs = PlugPoller(plugIp, 8080) self.plugs.statusChangeCallback = self.broadcast self.lighting.button2LongPressCallback = self.plugs.toggleAllNote what we did:
- We instantiate our Button class and assign the two regular button press callbacks. Since self.broadcast only takes one argument self.broadcast(msg) it works great for how we set it up in the button class.
- Set up the plugs module along with it's callbacks.
- We also set up the long button press callback to toggle some plugs.
With those we will broadcast to our clients status changes for inputs and outputs in a JSON format. {"Outputs":"00000000"},{"Inputs":"00000000"}
Since we are not polling periodically we need to let the clients know the status of all the modules when they are registered as a client:
def register(self, client): if client not in self.clients: client.sendMessage(json.dumps([{"Outputs":self.lighting.output_status()}, {"Inputs":self.lighting.input_status()}, {"Lamps":self.plugs.getstatus()}])) print("registered client {}".format(client.peer)) self.clients.append(client)
Part 3: WebSocket Commands from clients:
When our server receives a message from a client we need to handle it if is supposed to control something. In mine I decided since it's an output I'm toggling I just send a JSON string {"Output",pinNum}. If it was an analog device you'd need to send a bit more. With python you can convert json to a dictionary using json.loads(json_string).
def onMessage(self, payload, isBinary): if not isBinary: data = json.loads(payload) if(data["Output"]): output = data["Output"] #get current status to toggle the output. current = factory.lighting.output_status()[int(output)] newval = not int(current) #print(output,' ',current,' ',newval) factory.lighting.output_cmd(int(output), newval, False)Remember we put a status change callback whenever we call the output_cmd. That way all clients get the new status change.
For the iOS portion continue here
Subscribe to:
Posts (Atom)