I have been really fatigued and in pain the last couple of days. On top of that, the weather did not play nice, so I was mostly desk/bed ridden. Still I managed to build some scripts and perhaps it is time to talk about what I use and why.
The front end is a HTML(5)/javascript interface running from the main RPi on a LAMP stack. I have the option to use PHP, but the necessity has not arisen yet. I would PHP in the back-end for talking to databases.
The main backbone of the system is Python. I wrote a socket server that talks to everything be it internal apps, the front-end or the Arduino sensor board. The reason for a socket server is twofold. It offers an entrypoint which is accessible from every level and you are not horsing around with app user sudo lists and permissions and such. Everything is run under the user who started the server.
The code is a bit spaghetti, but for the most part it works. It works around the quirks of socket servers with a hack. Perhaps there are snippets that people find useful :
#!/usr/bin/env python3
import serial
import socket
import sys
import os
import glob, os
import time
from datetime import datetime
import mimetools
from StringIO import StringIO
# Define socket host and port
SERVER_HOST = '192.168.1.41'
SERVER_PORT = 7654
# Create socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#server_socket.Server(cors_allowed_origins='*')
server_socket.bind((SERVER_HOST, SERVER_PORT))
server_socket.listen(1)
print('Listening on port %s ...' % SERVER_PORT)
# http://192.168.1.41:7654/?key:8e76@r1on&command&leftBlinkOn
ser = serial.Serial('/dev/ttyAMA0', 19200, timeout=1)
ser.reset_input_buffer()
while True:
failureMode = False
# Wait for client connections
client_connection, client_address = server_socket.accept()
# Get the client request
request = str(client_connection.recv(1024).decode())
#print '----'
#print request
reqLength = len(str(request).strip());
#print reqLength
#print '----'
if reqLength > 10:
request, he = request.split('\r\n', 1)
# Get the headers
m = mimetools.Message(StringIO(he))
# Add request information
m.dict['method'], m.dict['path'], m.dict['http-version'] = request.split()
# print m['path']
arguments = m['path'].split('?')
# print "args"
# print argCount
argCount = len(arguments)
# print "args"
# print argCount
if argCount > 1:
# print(arguments[1])
options = arguments[1].split('&')
if options[0] != "key:8e76@r1on":
failureMode = True
# failureMSG = "&Err[3]=Invalid Key Error."
if failureMode == False:
key = options[0]
order = options[1]
command = options[2]
if order == "say":
#os.system("espeak ") + ('"') + str(command) + ('"')
#espeak -v nl+f5 -s150 "dit is een test" # dutch female
status = os.popen('cmus-remote -Q | head -n 1 | awk \'{print $2}\'').read()
status = status.replace('\n', ' ').replace('\r', '')[:-1]
if (status == "playing"):
os.popen('cmus-remote -v 35%')
print("'" + status + "'")
os.system('espeak -v nl+f5 -s150 ' + ('"') + str(command) + ('"'))
response = 'HTTP/1.0 200 OK'
response += "Content-Type: text/html; charset=utf-8\n"
response += "Access-Control-Allow-Origin: *\n"
response += '\n\n<data><say>\"' + str(command) + '\"</say></data>'
if (status == "playing"):
os.popen('cmus-remote -v 80%')
elif order == "audio":
response = 'HTTP/1.0 200 OK'
response += "Content-Type: text/html; charset=utf-8\n"
response += "Access-Control-Allow-Origin: *\n"
if command == "play":
d = 2
elif command == "pause":
d = 2
elif command == "stop":
d = 2
elif command == "next":
d = 2
elif command == "prev":
d = 2
elif command == "status":
status = os.popen('cmus-remote -Q | head -n 1 | awk \'{print $2}\'').read()
status = status.replace('\n', ' ').replace('\r', '')[:-1]
if status == "":
status = "Cmus not running"
duration = os.popen('cmus-remote -Q | sed -n 3,3p | awk \'{print $2}\'').read()
duration = duration.replace('\n', ' ').replace('\r', '')[:-1]
position = os.popen('cmus-remote -Q | sed -n 4,4p | awk \'{print $2}\'').read()
position = position.replace('\n', ' ').replace('\r', '')[:-1]
mfile = os.popen('cmus-remote -Q | sed -n 2,2p | cut -c 6-').read()
mfile = mfile.replace('\n', ' ').replace('\r', '')[:-1]
volumeL = os.popen('cmus-remote -Q | sed -n 16,16p | awk \'{print $3}\'').read()
volumeL = volumeL.replace('\n', ' ').replace('\r', '')[:-1]
volumeR = os.popen('cmus-remote -Q | sed -n 17,17p | awk \'{print $3}\'').read()
volumeR = volumeR.replace('\n', ' ').replace('\r', '')[:-1]
response += '\n\n<data>'
response += '<status>' + status + '</status>'
response += '<file>' + mfile + '</file>'
response += '<duration>' + duration + '</duration>'
response += '<position>' + position + '</position>'
response += '<volumeLeft>' + volumeL + '</volumeLeft>'
response += '<volumeRight>' + volumeR + '</volumeRight>'
response += '</data>'
else:
strcommand = str("<" + order + "=" + command + ">")
print(strcommand)
ser.write(strcommand + "\n")
response = 'HTTP/1.0 200 OK'
response += "Content-Type: text/html; charset=utf-8\n"
response += "Access-Control-Allow-Origin: *\n"
response += "\n\n<data><module>Device Control...</module>\n\n"
response += '<recvData>'
response += '<order>' + str(order) + '</order>'
response += '<command>' + str(command) + '</command>'
response += '</recvData></data>'
else:
response = 'HTTP/1.0 200 OK'
response += "Content-Type: text/html; charset=utf-8\n"
response += "Access-Control-Allow-Origin: *\n"
response = '\n\n<data><module>Device Control...</module>\n\n'
response += '<recvData><error>mode1</error></recvData></data>'
# print chkHost
else:
response = 'HTTP/1.0 200 OK'
response += "Content-Type: text/html; charset=utf-8\n"
response += "Access-Control-Allow-Origin: *\n"
response += '\n\n<recvData><error>Packet zero bytes</error></recvData></data>'
client_connection.sendall(response.encode())
client_connection.close()
time.sleep(2)
# Close socket
server_socket.close()
This script always returns and XML file to the caller, regardless of the command was local, for an app or from the serial device.
The serial devise is an Arduino mega. It has its own little console, but mainly gets its commands from the socket server:
#include <dht.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <secTimer.h>
#include <TinyGPSPlus.h>
dht DHT;
#define DHT11_PIN 6
TinyGPSPlus gps;
LiquidCrystal_I2C lcd(0x27, 16, 2);
secTimer myTimer;
unsigned long seconds=0;
const byte numChars = 32;
char receivedChars[numChars];
int turnValue;
int oldturnValue;
int lightValue=0;
int oldLightValue=0;
int humValue=0;
int oldHumValue=0;
int tempValue=0;
int oldTempValue=0;
int leftTurnStatus = 0;
int rightTurnStatus = 0;
int GPSLon = 0;
int GPSLat = 0;
int GPSsatellites = 0;
int GPSSpeed = 0;
int GPSAltitude = 0;
bool autoIndicator = true;
bool leftIndicator = false;
bool rightIndicator = false;
bool mainLights = false;
boolean newData = false;
int sensorLoop = 0;
const int RELAY_PIN_LEFT = 8;
const int RELAY_PIN_RIGHT = 9;
const int RELAY_PIN_HORN = 10;
const int RELAY_PIN_MAIN = 11;
static const int RXPin = 3, TXPin = 4;
static const uint32_t GPSBaud = 9600;
int buttonState = 0;
int button2State = 0;
String glat = "";
int second1Loop = 0;
int second2Loop = 0;
int second4Loop = 0;
int second10Loop = 0;
String dataBuffer = "";
bool valueChange = false;
bool leftTurnPressed = false;
bool rightTurnPressed = false;
bool leftBlinkOn = false;
bool rightBlinkOn = false;
void setup() {
delay(200);
Serial.begin(19200);
Serial1.begin(9600);
Serial.println("<init>CCKit is ready></init>\n");
myTimer.startTimer(); //start the timer
seconds=myTimer.readTimer();
pinMode(RELAY_PIN_HORN, OUTPUT);
pinMode(RELAY_PIN_LEFT, OUTPUT);
pinMode(RELAY_PIN_RIGHT, OUTPUT);
pinMode(RELAY_PIN_MAIN, OUTPUT);
digitalWrite(RELAY_PIN_HORN, HIGH);
digitalWrite(RELAY_PIN_LEFT, HIGH);
digitalWrite(RELAY_PIN_RIGHT, HIGH);
digitalWrite(RELAY_PIN_MAIN, HIGH);
lcd.init();
lcd.backlight();
lcd.setCursor(0,0);
lcd.print("CCkit 1.0 ");
lcd.setCursor(0,1);
lcd.print("Dash:ready");
int analogValue = analogRead(A0);
int oldAnalogValue = -1;
int oldLightValue = -1;
}
void loop() {
recvWithStartEndMarkers();
showNewData();
if (seconds!=myTimer.readTimer()) {
seconds=myTimer.readTimer();
second1Loop++;
second2Loop++;
second4Loop++;
second10Loop++;
}
bool valueChange = false;
getSensorData();
getInputData();
}
void getInputData(){
if (second1Loop == 1){
if (leftTurnPressed){
if (rightBlinkOn){
rightBlinkOn = false;
lcd.setCursor(0,0);
lcd.print(" ");
} else {
leftBlinkOn = true;
}
}
if (rightTurnPressed){
if (leftBlinkOn){
leftBlinkOn =false;
lcd.setCursor(0,0);
lcd.print(" ");
} else {
rightBlinkOn = true;
}
}
leftTurnPressed = false;
rightTurnPressed = false;
}
if (second1Loop == 1){
if (leftBlinkOn){
digitalWrite(RELAY_PIN_LEFT, LOW);
}
if (rightBlinkOn){
digitalWrite(RELAY_PIN_RIGHT, LOW);
}
}
if (second2Loop ==2){
if (leftBlinkOn){
digitalWrite(RELAY_PIN_LEFT, HIGH);
}
if (rightBlinkOn){
digitalWrite(RELAY_PIN_RIGHT, HIGH);
}
}
if (!leftBlinkOn){
digitalWrite(RELAY_PIN_LEFT, HIGH);
}
if (!rightBlinkOn){
digitalWrite(RELAY_PIN_RIGHT, HIGH);
}
// reset secondloops
if (second1Loop == 1){
second1Loop = 0;
}
if (second2Loop == 2){
second2Loop = 0;
}
if (second4Loop == 4){
second4Loop = 0;
}
if (second10Loop == 10){
second10Loop = 0;
}
}
void getSensorData(){
if (second1Loop == 1){
valueChange = false;
// ss.begin(GPSBaud);
}
if (second2Loop == 2){
// GPS
smartDelay(500);
valueChange = true;
GPSLon = gps.location.lng();
GPSLat = gps.location.lat();
GPSSpeed = gps.speed.kmph();
GPSAltitude = gps.altitude.meters();
GPSsatellites = gps.satellites.value();
Serial.print(GPSLon);
Serial.print("\n");
}
if (second4Loop == 4){
int lightValue = analogRead(A1);
if (abs(lightValue - oldLightValue) > 10){
oldLightValue = lightValue;
valueChange = true;
}
int chk = DHT.read11(DHT11_PIN);
tempValue = DHT.temperature;
if (tempValue != oldTempValue){
oldTempValue = tempValue;
valueChange = true;
}
humValue = DHT.humidity;
if (humValue != oldHumValue){
oldHumValue = humValue;
valueChange = true;
}
}
if (second1Loop == 1){
if (valueChange){
}
}
}
void recvWithStartEndMarkers() {
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
if (recvInProgress == true) {
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
}
}
else if (rc == startMarker) {
recvInProgress = true;
}
}
}
void showNewData() {
if (newData == true) {
// process commands
String order;
String exec;
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print(receivedChars);
order = strtok(receivedChars,"=");
exec = strtok(NULL,"&");
dataBuffer = "<data>";
dataBuffer += " <call>";
dataBuffer += " <type>" + order + "</type>";
dataBuffer += " <order>" + exec + "</order>";
dataBuffer += " </call>";
dataBuffer += " <results>";
if (order == "display"){
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print(exec);
}
if (order == "command"){
if (exec == "clrDisplay"){
lcd.setCursor(0,1);
lcd.print(" ");
}
if (exec == "parkOn"){
dataBuffer += " <state name=\"park\">" + exec + "</state>";
}
if (exec == "parkOff"){
dataBuffer += " <state name=\"park\">" + exec + "</state>";
}
if (exec == "leftBlinkOn"){
leftBlinkOn = true;
dataBuffer += " <state name=\"leftIndicator\">" + String(leftBlinkOn) + "</state>";
}
if (exec == "leftBlinkOff"){
leftBlinkOn = false;
dataBuffer += " <state name=\"leftIndicator\">" + String(leftBlinkOn) + "</state>";
}
if (exec == "rightBlinkOn"){
rightBlinkOn = true;
dataBuffer += " <state name=\"rightIndicator\">" + String(rightBlinkOn) + "</state>";
}
if (exec == "rightBlinkOff"){
rightBlinkOn = false;
dataBuffer += " <state name=\"rightIndicator\">" + String(rightBlinkOn) + "</state>";
}
if (exec == "mainLightsOn"){
digitalWrite(RELAY_PIN_MAIN, LOW);
dataBuffer += " <state name=\"mainLights\">" + exec + "</state>";
}
if (exec == "mainLightsOff"){
digitalWrite(RELAY_PIN_MAIN, HIGH);
dataBuffer += " <state name=\"mainLights\">" + exec + "</state>";
}
if (exec == "breakLightsOn"){
dataBuffer += " <state name=\"breakLights\">" + exec + "</state>";
}
if (exec == "breakLightsOff"){
dataBuffer += " <state name=\"breakLights\">" + exec + "</breakLights>";
}
if (exec == "setAutoIndicatorOff"){
dataBuffer += " <state name=\"autoIndicator\">" + String(autoIndicator) + "</state>";
}
if (exec == "setAutoIndicatorOn"){
dataBuffer += " <state name=\"autoIndicator\">" + String(autoIndicator) + "</state>";
}
if (exec == "reset"){
dataBuffer += " <state name=\"reset\">Reset in 2 seconds</state>";
}
} else if (order == "request"){
if (exec == "lightStatus" | exec == "allData"){
dataBuffer += " <dataSet name=\"lightStatus\">";
dataBuffer += " <state name=\"park\">" + exec + "</state>";
dataBuffer += " <state name=\"rightIndicator\">" + String(rightBlinkOn) + "</state>";
dataBuffer += " <state name=\"mainLights\">" + exec + "</state>";
dataBuffer += " <state name=\"breakLights\">" + exec + "</state>";
dataBuffer += " </dataSet>";
} else if (exec == "sensorValues" | exec == "allData"){
} else if (exec == "GPSdata" | exec == "allData"){
dataBuffer += " <dataSet name=\"GPSStatus\">";
dataBuffer += " <state name=\"GPSLon\">" + String(GPSLon) + "</state>";
dataBuffer += " <state name=\"GPSLat\">" + String(GPSLat) + "</state>";
dataBuffer += " <state name=\"GPSSpeed\">" + String(GPSSpeed) + "</state>";
dataBuffer += " <state name=\"GPSAltitude\">" + String(GPSAltitude) + "</state>";
dataBuffer += " <state name=\"GPSsatellites\">" + String(GPSsatellites) + "</state>";
dataBuffer += " </dataSet>";
} else if (exec == "sensorData" | exec == "allData"){
dataBuffer += " <dataSet name=\"sensorData\">";
dataBuffer += " <state name=\"temperature1\">" + String(tempValue) + "</state>";
dataBuffer += " <state name=\"humidity1\">" + String(humValue) + "</state>";
dataBuffer += " </dataSet>";
} else if (exec == "internalSettings" | exec == "allData"){
dataBuffer += " <dataSet name=\"internalSettings\">";
dataBuffer += " <state name=\"autoIndicator\">" + String(autoIndicator) + "</state>";
dataBuffer += " </dataSet>";
} else {
//Something went wrong
}
}
Serial.print(dataBuffer);
dataBuffer = "";
newData = false;
}
}
static void smartDelay(unsigned long ms) {
unsigned long start = millis();
do {
while (Serial1.available()) {
gps.encode(Serial1.read());
//Serial.print(Serial1.read());
}
} while (millis() - start < ms);
}
static void printFloat(float val, bool valid, int len, int prec) {
String retVal = "";
if (!valid) {
while (len-- > 1) {
// Serial.print('*');
}
// Serial.print(' ');
} else {
// Serial.print(val, prec);
int vi = abs((int)val);
int flen = prec + (val < 0.0 ? 2 : 1); // . and -
flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
for (int i=flen; i<len; ++i) {
// Serial.print(' ');
}
}
smartDelay(0);
glat = val;
glat += "!";
glat += prec;
}
static void printInt(unsigned long val, bool valid, int len) {
char sz[32] = "*****************";
if (valid) {
sprintf(sz, "%ld", val);
}
sz[len] = 0;
for (int i=strlen(sz); i<len; ++i) {
sz[i] = ' ';
}
if (len > 0) {
sz[len-1] = ' ';
}
// Serial.print(sz);
smartDelay(0);
}
static void printDateTime(TinyGPSDate &d, TinyGPSTime &t) {
if (!d.isValid()) {
// Serial.print(F("********** "));
} else {
char sz[32];
sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year());
Serial.print(sz);
}
if (!t.isValid()) {
// Serial.print(F("******** "));
} else {
char sz[32];
// sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
Serial.print(sz);
}
printInt(d.age(), d.isValid(), 5);
smartDelay(0);
}
static void printStr(const char *str, int len) {
int slen = strlen(str);
for (int i=0; i<len; ++i) {
// Serial.print(i<slen ? str[i] : ' ');
}
smartDelay(0);
}
This script can turn on relays and get GPS and sensor information and pass it to the socket server. Like everything else, it is incomplete, sometimes buggy and rather ugly.
Then there are the odd cases what stuff just needs to be checked. This is done either in Python or in Bash script, because I am a sucker for punishment! This shell script basically monitors the mounted drives and when something changes it does its thing. Its thing being detecting media and GXP files on the device and copying it to the right location or build a playlist and start Cmus. When removed, CMus will be stopped.
All IO with Cmus is done via the socket server, so this script has very little to say about that :
#!/bin/bash
#needs sudo apt-get install screen
echo "CCKit USBMonitor daemon"
echo "_______________________"
active=1
mediaDrive=""
drivelistRaw=$(ls /media/pi/ -h -w 1)
activeDrives=$(ls /media/pi/ -h -w 1 | grep -c '^')
#build compare array
while IFS= read -r line; do
driveArray+=($line)
done <<< "$drivelistRaw"
#check if old media drive is still mounted
if [ -f /var/www/html/CCKit/tmp/mediaDrive.tmp ]
then
oldMediaDrive=$(cat /var/www/html/CCKit/tmp/mediaDrive.tmp)
mediaPath="/media/pi/${oldMediaDrive}/music/";
GPX="/media/pi/${oldMediaDrive}/GPX/"
if [ -d $GPX ]
then
echo "GPX folder found. Copy to main location"
cpGPX=$(cp "${GPX}"/* /var/www/html/CCKit/data/routes/gpx/)
fi
if [ -d "$mediaPath" ]
then
echo "Found Media Drive."
echo "Waiting for cmus socket"
screen -d -m -S cmus /usr/bin/cmus
sleep 1
cmus-remote -p
else
rm /var/www/html/CCKit/tmp/mediaDrive.tmp
fi
fi
while [ $active -le 2 ]
do
#check drives
drivelistRaw=$(ls /media/pi/ -h -w 1)
foundDrives=$(ls /media/pi/ -h -w 1 | grep -c '^')
drivecount=1
if [ "$foundDrives" != "$activeDrives" ]
then
#driver removed or added
if [ "$mediaDrive" == "" ]
then
echo 'no media drive'
while IFS= read -r line; do
if [[ ${driveArray[@]} =~ $line ]]
then
echo "$line" "drive found"
else
# this is the new drive
echo "New Drive found :" "$line"
driveArray+=($line)
activeDrives=$(ls /media/pi/ -h -w 1 | grep -c '^')
# check if drive has a media folder
musicf="/media/pi/${line}/music"
GPX="/media/pi/${line}/GPX/"
if [ -d $GPX ]
then
echo "GPX folder found. Copy to main location"
cpGPX=$(cp "${GPX}"/* /var/www/html/CCKit/data/routes/gpx/)
fi
if [ -d $musicf ]; then
echo "Media Directory found."
mediaDrive=${musicf}
#scan media folder for mp3's
MP3listRaw=$(find "${musicf}/" -type f -iname "*.mp3")
#echo "" > /var/www/html/CCKit/tmp/tmp.pl
while IFS= read -r line; do
#echo "file" "$line"
echo "$line" >> /var/www/html/CCKit/tmp/tmp.pl
done <<< "$MP3listRaw"
sleep 1
cp /var/www/html/CCKit/tmp/tmp.pl ~/.config/cmus/lib.pl
cp /var/www/html/CCKit/tmp/tmp.pl ~/.config/cmus/playlist.pl
echo "$mediaDrive" > /var/www/html/CCKit/tmp/mediaDrive.tmp
echo "Waiting for cmus socket"
#touch /var/www/html/CCKit/tmp/cmus.trigger
screen -d -m -S cmus /usr/bin/cmus
sleep 1
cmus-remote -p
else
echo "No mediaFolder Found."
#pkill cmus
fi
fi
done <<< "$drivelistRaw"
else
echo 'drive removed'
#check if it was the media drive
drivelistRaw=$(ls /media/pi/ -h -w 1)
activeDrives=$(ls /media/pi/ -h -w 1 | grep -c '^')
mediaRemoved=false
while IFS= read -r line; do
if [[ ${driveArray[@]} == $mediaDrive ]]
then
mediaRemoved=true
echo "mediaDrive removed"
fi
done <<< "$drivelistRaw"
driveArray=()
while IFS= read -r line; do
driveArray+=($line)
done <<< "$drivelistRaw"
#stop player and reset daemon
if [ $mediaRemoved ]
then
rm /var/www/html/CCKit/tmp/mediaDrive.tmp
mediaDrive=""
pkill cmus
fi
fi
fi
sleep 1
done
So behind the frontend, these 3 types of programmes all tie the stuff together. We have one nice central place where the data can be retrieved from. Now that is what I call a stack.
There are also a couple of packages I use to extract the data or play media, but that is for another time. This is messy enough as it is!
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.