john brohan
Published © GPL3+

Headless Raspberry Pi Health Monitor for Paraplegics

A headless project needs to get its IP, send results to the cloud for analysis on a phone. It also needs a way to update the software.

IntermediateWork in progressOver 8 days1,614
Headless Raspberry Pi Health Monitor for Paraplegics

Things used in this project

Hardware components

Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
×1
Xiaomi Mijia Bluetooth Temperature Humidity Sensor
Very basic info, but seems reliable. $20 but large!
×1
TEMPERATURE HUMIDITY DEW POINT BLUETOOTH SENSOR BEACON
The Blue Maestro is $40 but very easy to use and it is also about the size of a silver dollar
×1
Load cell and hx711
×1

Story

Read more

Code

Send an email from a web server

PHP
This PHP script runs on a web server and can be called from a python program to send an email. The call is managed by the rerquests library to send http calls to web servers
example call from python...

import requests
url = "https://mydomain.com/webtest/UFTSendSimpleEmail.php?subject=Autonomic_Dysreflexia&to="+getEmail()+"&from=autonomicdysreflexia@gmail.com&body="+body
print(url)
try:
r = requests.get(url)
except:
print ("Exception",sys.exc_info()[0])

A webserver it is needed for the automatic updating of the programs in this project. I have found http://nearlyfreespeech.net/ very good and you can start for only $5. It offers PHP and MYSQL which are the tools used here. You do not need to have a domain name, you can use the default (http://<yourname>.nfshost.com/) domain name since nobody will ever need to type them in (except you)
<?php
 require_once "swiftmailer/lib/swift_required.php";
    error_reporting(E_ERROR);
   // var_dump($_GET);
    $from = urldecode($_GET['from']);    
    $body = urldecode($_GET['body']);
    $to = urldecode($_GET['to']);
    $subject = urldecode($_GET['subject']);
  //  echo "<br>to=".$to;
  //   echo "<br>from=".$from;
  //    echo "<br>body=".$body;
  //    echo "<br>subjuect".$subject;
    $transport = Swift_MailTransport::newInstance();
    $message = Swift_Message::newInstance();
    $message->setTo(array( $to ));
    $message->setSubject($subject);
    $message->setBody($body);
    $message->setFrom($from);
    $mailer = Swift_Mailer::newInstance($transport);
    
    $mailer->send($message);
?>

Send yourself an email with the wifi IP address

Python
The working directory is /home/pi/hx711py
save your email in hx711py/data/user.email

Headless Raspberry Pi computers don't have a display or a keyboard. Usually you use ssh to program them, which requires knowing the ip address (the syntax to start ssh on a mac is "ssh pi@192.168.86.78" where the "192... is the ip address). It can vary at each reboot. This is a program to run during the boot procedure /etc/rc.local to send yourself an email containing the ip address!
Note that you need to set up the pi initially using a keyboard and screen. Be sure to enable the SSH interface. When you are setting up other pi's to send to customers you can just install the software on an SD card and then use that to initialize the Pi and get things going.
import time
import os
import subprocess
import commands
import re
import sys
import requests
import netifaces as ni
import md5
import stat
#functions
def findQuoted(string):
	needle = '"'
	st  = string.find(needle)
	end = string.rfind(needle)
	return string[st+1:end]
def findssid():
	return findQuoted(subprocess.check_output('iwgetid'))
def getEmail():
	file = open("/home/pi/hx711py/data/user.email", "r")
	em = file.read()
	return em.strip()
def sendEmail(body):
	print (body)
	url = "https://mydomain.com/webtest/UFTSendSimpleEmail.php?subject=Autonomic_Dysreflexia&to="+getEmail()+"&from=autonomicdysreflexia@gmail.com&body="+body
	print(url)
	try:
  		r = requests.get(url)  		
  	except:
  		print ("Exception",sys.exc_info()[0])

#main line starts here.....
ssid = findssid()
body = ""
if ssid == "hotspot":
	#here is the stuff about finding the wifi ssid and password...
else:
	#this is the case where we are connected to a house wifi
	ni.ifaddresses('wlan0')
	ip = ni.ifaddresses('wlan0')[ni.AF_INET][0]['addr']
	body = "your apparatus is connected to the House WiFi "
	sendEmail(body + findssid() +" at "+ ip +" all seems to be well!")

Get House wifi ssid and password (part1)

PHP
This part of the project is to get the house wifi ssid and password to the raspberry pi, so that it can boot on the house wifi. The manufacturer needs not to know the ssid and password, they are private to the user.

The approach we have taken is to send an email containing a link to a web page to the client. The web page asks for ssid and password. The web page NEEDS to be opened on a computer/phone etc connected to hotspot/12345678 (more on this further down). The web page submits its data to a http server on the raspberry pi, where it is stored in the correct place so that as the raspberry pi reboots it will link to the house wifi. The password and ssid do not go across the internet, they go only within the local subnet. Given the length and complexity of these instructions the user can make repeated attempts to get it right provided he reboots (on hotspot) before each attempt.

The theory of this part of the project is as follows.
The user activates his local hotspot on and Android or iPhone or tablet. He MUST use the ssid "hotspot" and the password "12345678" (no quotes!!).
The raspberry pi 'knows' about hotspot/12345678 in its list of available internet connections in /etc/wpa_supplicant/wpa_supplicant.conf. It is the only internet connection in this file when the box arrives at the client's house.
As the raspberry pi boots up it tries to connect to the internet using a connection in the /etc/wpa_supplicant/wpa_supplicant.conf. If 'hotspot' is working, it will connect.
When the raspberry pi connects to the internet during boot-up it sends an email to the user.
The user's email address is stored in /home/pi/hx711py/user.email. It is put there in the factory before sending it out.
During the boot-up process, if the wifi connection is 'hotspot' the email it sends will include a http link to a web page containing a form asking for the ssid and password.
When you press the "send" button in the form the url is constructed containing the ssid and password and sent to a webserver on the raspberry pi.
The webserver adds the new ssid and password to the /etc/wpa_supplicant/wpa_supplicant.conf making a new 'netrwork' entry. ( actually it copies over a file containing only the 'hotspot' network entry and adds the new network entry). This implies that it can 'remember' only one wifi connection at a time, you need to repeat the hotspot/12345678 gobbledygook when you move house or go on an extended stay.

When the raspberry pi is rebooted it will seek either hotspot or the new house wifi. The Hotspot has a priority of 2 which means that it will be preferred. So turn off the hotspot before rebooting! Also use the 'hotspot' to set the raspberry pi onto a new network, or just use the hotspot if you are staying at a hotel for a few days.

This PHP code is stored on http://www.<your domain>.com/webtest/sendToRaspi.php
the parameters ?ip=<ip.addr>&<youremail> are applied to the url in AD_Monitor.start.py in the bootup sequence.
<?
$ip= $_GET['ip'];
$email = $_GET['email'];
//echo "ip = ".$ip;
$action = "http://".$ip.":5000/cakes?";
//echo "<br> action = ".$action;

?>
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="jquery.mobile-1.4.5.min.css">
<script src="jquery-1.11.3.min.js"></script>
<script src="jquery.mobile-1.4.5.min.js"></script>
</head>
<body>



<div data-role="page" data-dialog="true" id="pagetwo">
  <div data-role="header">
    <h1>Autonomic Dysreflexia WiFisetup</h1>
  </div>

  <div data-role="main" class="ui-content">
    <p>The Autonomic Dysreflexia program on the Raspberry Pi needs the credentials of your wifi.<br>The SSID is something like PeterWifi and the Password is whatever it is, be careful about upper and lower case!<br> The Android App 'Autonomic Dysreflexia' shows the gradual filling of the urine collection bag and warns if it stops. Access to this app is controlled by your email <? echo $email; ?> address and the 'App Password' you create here.</p>
  <form action = <? echo $action; ?> method = "get" target="_blank">
    SSID:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="text"name = "ssid"  size = "20" placeholder= "Like 'PeterBrownWifi'" style = "text-transform: none" >
    Password: <input type = "text" name = "password" size = "20" placeholder="Maybe 'TopSecretPassKey'" style = "text-transform: none">
    App Password: <input type = "text" name = "apppassword" size = "20" placeholder="'for use in Android app'" style = "text-transform: none">
    <input type = "submit" value = "SEND"  width = "10">
  </form>
  </div>

  <div data-role="footer">
    <h1>These data go directly to the RaspberryPi</h1>
  </div>
</div> 


</body>
</html>

Get House wifi ssid and password (part2)

Python
This python program AD_Monitor.start.py runs during bootup. Its main purpose is to send the user an email confirming that he is connected to the house wifi at ip 192.168... or to confirm that he is connected to the 'hotspot/12345678' and include a link to a web page stored on your domain. Click on this link! This web page will invite you to send your house ssid and password to a webserver on the raspberry pi. This is covered in (part1) in obsessive detail.
import time
import os
import subprocess
import commands
import re
import sys
import requests
import netifaces as ni
import md5
import stat
orig_stdout = sys.stdout
f = open('/home/pi/hx711py/rc.local.log', 'w')
sys.stdout = f

#functions
import subprocess
def findQuoted(string):
	needle = '"'
	st  = string.find(needle)
	end = string.rfind(needle)
	return string[st+1:end]
def findssid():
	return findQuoted(subprocess.check_output('iwgetid'))
def getEmail():
	file = open("/home/pi/hx711py/data/user.email", "r")
	em = file.read()
	return em.strip()
def sendEmail(body):
	print (body)
	url = "https://mydomain.com/webtest/UFTSendSimpleEmail.php?subject=Autonomic_Dysreflexia&to="+getEmail()+"&from=autonomicdysreflexia@gmail.com&body="+body
	url1 = "https://uroflowtracings.com/webtest/UFTSendSimpleEmail.php?subject=Autonomic_Dysreflexia&to="+"jbrohan@gmail.com"+"&from=autonomicdysreflexia@gmail.com&body="+body

	print(url)
	try:
  		r = requests.get(url)
  		r = requests.get(url1)
  	except:
  		print ("Exception",sys.exc_info()[0])
def file_age_in_seconds(pathname):
    return int(time.time() - os.stat(pathname)[stat.ST_MTIME])
#main line starts here.....
ssid = findssid()
body = ""
if ssid == "hotspot":
	ni.ifaddresses('wlan0')
	ip = ni.ifaddresses('wlan0')[ni.AF_INET][0]['addr']
	link = "https://mydomain.com/webtest/sendToRaspi.php?ip="+ip+"&email="+getEmail()
	body = " Click on the link below so send the wifi name (ssid) and the password to the apparatus. The Autonomic Dysreflexia apparatus \
	needs to use your house wifi to send the readings to the cloud, so that your carer can get alerted on her Android.\n "
	#body = "poobah"
	p = file_age_in_seconds("/etc/wpa_supplicant/wpa_supplicant.conf")
        print p
	if p < 1200:
		print "file is less than 10 minutes old"
		body = "your apparatus is still connected to the hotspot"
		sendEmail(body + findssid() +" at "+ ip +" all seems to be well!\nYou can turn off the hotspot on your Phone now, and it will use the House wifi!" + link)
	else: 	
		sendEmail(body+link)
else:
	ni.ifaddresses('wlan0')
	ip = ni.ifaddresses('wlan0')[ni.AF_INET][0]['addr']
	body = "your apparatus is connected to the House WiFi "
	#body = "plonkoplinko"
	sendEmail(body + findssid() +" at "+ ip +" all seems to be well!")


# move these commands from the rc.local to a puython program to give more flexibility and 
# and uniformity to the "over the Air" updating system...
cmd1 = "sudo /home/pi/hx711py/python AD_Monitor.md5.py update"
cmd2 = "sudo python /home/pi/hx711py/AD_Monitor.web.py &"
cmd3 = "sudo python /home/pi/hx711py/AD_Monitor.hx711.py &"
cmd4 = "sudo python /home/pi/hx711py/AD_Monitor.moisture.py &"


os.system(cmd1)
time.sleep(0.5)
print (cmd1)
os.system(cmd2)
time.sleep(0.5)
print (cmd2)
os.system(cmd3)
time.sleep(0.5)
print (cmd3)
os.system(cmd4)
time.sleep(0.5)
print (cmd4)
print "AD_Monitor launched"

Get House wifi ssid and password (part3) the raspberry pi webserver!

Python
It is important to keep the password private, and not to store it on the web. The raspberry pi distribution contains 'flask' which can process http requests.
The task of this AD_Monitor.web.py is to get the ssid and password parameters from the web form in "Get House wifi ssid and password (part1)" and write them onto /etc/wpa_supplicant/wpa_supplicant.conf in addition to the network entry for 'hotspot/12345678' which is found in /etc/wpa_supplicant/wpa_supplicant.conf.copy
The net result of this is that the raspi will boot onto the house wifi if the hotspot is not active.
The syntax of flask is unexpected, but it seems to achieve its purpose of linking a web form to the raspberry pi and processing the action correctly.
Make sure you are using the hotspot/12345678 connection when you run the form! (that's three times I remind you because I forgot several times during testing!!)
#webServe.py AD_Monitor.web.py
from shutil import copyfile
from flask import Flask,request
import os
import sys
orig_stdout = sys.stdout
f = open('/home/pi/hx711py/rc.local.log', 'w')
sys.stdout = f
app = Flask(__name__)
@app.route('/')
def index():
	return 'Hello complicated world!'
@app.route('/cakes',methods=["GET"])    
def cakes():
	try:
		a = request.args.get('id')
		ssid = request.args.get('ssid')
		passw = request.args.get('password')
		copyfile ("/etc/wpa_supplicant/wpa_supplicant.conf.copy","/etc/wpa_supplicant/wpa_supplicant.conf")
		supp = ["network={\n","\tssid=\""+ ssid+"\"\n","\tpsk=\"" + passw +"\"\n" , "\tkey_mgmt=WPA-PSK\n" + "\tpriority=1\n" + "}\n"]
		print supp
		suppFile = open("/etc/wpa_supplicant/wpa_supplicant.conf","a")
		suppFile.writelines(supp)
		suppFile.close()
		print "rebooting network"
		#os.system('ifconfig')
		os.system('sudo shutdown -r +1 "reboot in one minute"')
		#sleep (5)
		#os.system('sudo ifconfig wlan0 up')
		#sleep (20)
	except:
		 print ("Exception",sys.exc_info()[0])
		 return "failed"
	return "OK - close the personal hotspot. The raspi will reboot in one minute on your house WiFi."
if __name__ == '__main__':
	app.run(debug=True, host = '0.0.0.0')

Update the code running on a headless Raspberry Pi automatically, checking every 24h (python)

Python
As you wish to add new sensors to a project or correct errors that are discovered in actual practice, you need a way to do this "Over the Air", so that the code can be kept up to date even though there is no way for the user to interact with the headless pi. This procedure is automatic and is part of the reboot procedure. The system is rebooted every 24h using cron.

The outline of this updating system.

From time to time the contents of the development and testing raspberry pi are ready to be published, maybe just one file or maybe all of them. The .php files which run on <myDomain> webserver are shown here as documentation

The AD_Monitor.md5.py with the 'create' parameter will upload all the files in the list to the AD_MonitorMd5 table in a database in <myDomain.com>. This is a manual task, done occasionally by the developer.
Every reboot of the headless raspberry pi, AD_Monitor.md5.py with the 'update' parameter examines the database to see if it has a newer version of the file. If so the new version is decrypted , downloaded as a temp name, md5 is checked and if all is well, the original file is removed and the temp name is renamed to the filename.
If there is an error and the file is not downloaded and renamed correctly, then at the next reboot it will try again. Pretty much the only thing that can fail is a power failure during this process, which requires a power up reboot anyway. It could possibly run out of disk space, but unlikely with this application.
The PHP file is simply the side that actually deals with the database, forming and executing the MYSQL statements. Note the UFTUtilitiesMysqli.php is intentionally omitted here. The execQ() function is really all you need and it's obvious!

The fields in the AD_MonitorMd5 table are:-
md5 the checksum of the file
filename
lastModified time stamp
fileContents and the encoded contents of the file (Note, the file must have no trailing blanks)
inc an auto-increment identifier, not used.

There is a list of files to be managed around line 23 of the AD_Monitor.md5.py, and this needs to be added to as more files are added to the system.
The md5 checksums are managed in the python part using hashlib on the regular code
The encrypting and decrypting are managed in the php. It does not handle trailing spaces well, eliminate them.
# write md5 digest to database for each .py file in the hx711py folder
import os , os.path
import hashlib
import requests
import sys
import datetime
import time
def getEmail():
    file = open("/home/pi/hx711py/data/user.email", "r")
    em = file.read()
    return em.strip()
def sendEmail(body):
    print (body)
    url1 = "https://mydomain.com/webtest/UFTSendSimpleEmail.php?subject=AD_Monitor Update error&to="+"jbrohan@gmail.com"+"&from=autonomicdysreflexia@gmail.com&body="+body
    print(url)
    try:
        r = requests.get(url1)
    except:
        print ("Exception",sys.exc_info()[0])

homeDir = "/home/pi/hx711py"
directory = os.listdir(homeDir)
fileList = ["AD_Monitor.start.py","AD_Monitor.moisture.py","AD_Monitor.web.py","AD_Monitor.md5.py","AD_Monitor.start.py","AD_Monitor.hx711.py","AD_Monitor.calibrate.py"]
print fileList
print sys.argv
if len(sys.argv) > 1:
    if sys.argv[1] == "update":
        url = "https://mydomain.com/webtest/AD_Monitor.md5.php?update=true"
        try:
            r = requests.get(url)
        except:
            print ("Exception",sys.exc_info()[0])
            sys.exit()
        p = r._content
        rLine = p.split("*")
        for s in rLine:
            t = s.split(",")
            if t[0] == "":
                sys.exit()
            mtime = long(os.path.getmtime(t[0]))
            utc_time = datetime.datetime.utcfromtimestamp(mtime)
            #print utc_time, str(utc_time)
            if str(utc_time) < t[2]:
                print "update this one",t[0]
                url = "https://mydomain.com/webtest/AD_Monitor.md5.php?getfile=true&filename="+t[0]
                try:
                    r = requests.get(url)
                    print "r** =",r
                    #print "r** =",r.text
                except:
                    print ("Exception",sys.exc_info()[0])
                    sys.exit()
                p = r._content
                with open("temp.download","w") as text_file:
                    text_file.write(p)
                checksum = hashlib.md5(open("temp.download", 'rb').read()).hexdigest()
                print "checksum = ",checksum, t[0], t[1]
                if checksum == t[1]:
                    print "hoohaa"
                    os.remove(t[0])
                    os.rename("temp.download",t[0])
                else:
                    body = "Error during Update (AD_Monitor.md5.py) done on " +  time.strftime("%Y-%m-%d %H:%M")
                    body = body + " remote user = " + getEmail() + " offending file = " + t[0]
                    body = body + "download checksum = " + checksum + " storage checksum = " + t[1] + "\n"
                    sendEmail(body)
                    print body
    


    if sys.argv[1] == "create":
        url = "https://mydomain.com/webtest/AD_Monitor.md5.php?new=true"
        try:
            r = requests.get(url)
        except:
            print ("Exception",sys.exc_info()[0])
        for file in fileList:
            if file.endswith('.py'):
                checksum = hashlib.md5(open(file, 'rb').read()).hexdigest()
                print file, checksum
                mtime = os.path.getmtime(file)
                mtime = mtime +1
                #make sure that the AD_Monitor.md5.py update picks it up
                t = "{:f}".format(mtime)

                #print t,t,t
                #last_modified_date = datetime.fromtimestamp(mtime)
                url = "https://mydomainƒuro.com/webtest/AD_Monitor.md5.php?create=true"
                url = url + "&md5="+checksum
                url = url + "&filename="+file
                url = url + "&lastModified="+t
                print url
                files = {'file': open(file)}
                try:
                  print files,"files"
                  r = requests.post(url, files = files)
                  print "r** =",r
                  #print "r** =",r.text
                except:
                  print ("Exception",sys.exc_info()[0])

Update the code running on a headless Raspberry Pi automatically, checking every 24h (php)

PHP
The encrypted program files are stored as a 'TEXT' in a mysql database along with an md5 hash of the original code and a creation date. If the creation date in the database is newer than the creation date of the same file on the user's raspberry pi, then the file is downloaded, decrypted and stored under a temporary name on the raspi its md5 checked and if all is OK it is renamed to the production file.
<?
//AD_Monitor.md5.php
require_once 'UFTUtilitiesMysqli.php';
writeLog1("start of AD_Monitor.md5.php");
$var = print_r($_GET,  true);writeLog1($var);
writeLog1("now the dollar post stuff...");
$var = print_r($_POST, true);writeLog1($var);
writeLog1("now thedollar files stuff..."); 
$var = print_r($_FILES, true);writeLog1($var);
if (isset($_GET['test'])){
	$b = encrypt("abcdef");
	$c = decrypt($b);
	echo $b."  " . $c;
}
if (isset($_GET['new'])){
	$q = "DELETE FROM AD_MonitorMd5 WHERE inc > 0;";
	execQ($q);
	return;
}
if (isset($_GET['create'])){
	$md5 = $_GET['md5'];
	$filename = $_GET['filename'];
	$lastModified = $_GET['lastModified'];
	$q = "DELETE FROM AD_MonitorMd5 WHERE filename = '$filename';";
	writeLog1($q);
	execQ($q);
	$d = date(intval($lastModified));	
	$dd = gmdate("Y-m-d H:i:s",$d);
	//echo $d."  ".$lastModified."--".$dd;
	//writeLog1($filename);
	//unlink($filename);
	//var_dump($_POST);
	move_uploaded_file($_FILES["file"]["tmp_name"], "tempTransfer");
	$fileContents = encrypt(file_get_contents("tempTransfer"));
	// unlink("tempTransfer");
	writeLog1("\nLength offileContents = ".strlen($fileContents));
	$q = "INSERT INTO AD_MonitorMd5 (md5,filename,lastModified,fileContents)  VALUES ('$md5','$filename','$dd','$fileContents');";
	//writeLog1($q);
	$result = execQ($q);
	writeLog1("\n result",$result);
	return;
}
if (isset($_GET['update'])){ 
	// print out the relevant data to decide which files to download!
	$q = "SELECT filename,lastModified,md5 FROM AD_MonitorMd5;";
	$result = execQ($q);
	$num=getNum($result);
	for ( $i = 0; $i < $num; $i++ ){
		$row = getRow($result);		
		echo $row['filename'].",";
		echo $row['md5'].",";
		echo $row['lastModified']."*";
	}
}
if (isset($_GET['getfile'])){
	$filename = $_GET['filename'];
	$q = "SELECT fileContents FROM AD_MonitorMd5 WHERE filename = '$filename';";
	$result = execQ($q);
	$row = getRow($result);	
	//var_dump($row);
	echo decrypt($row['fileContents']);
}
function writeLog1 ( $text ){
	//$text = "\n".$text;
	$log = fopen ( "logfile.txt", "a+" );
	fwrite ( $log, $text, strlen ($text) );
	fclose ( $log );
	//echo $text;
}

?>

Read two ble beacons to get temperature and relative humidity

Python
The data carried in a beacon can be ordered in almost any way that suits the author. In this case the approach is to identify two kinds of beacon, BlueMaestro (beautiful, small and well documented; and the Xiaomi Mijia Bluetooth Temperature Smart Humidity Sensor which has a Chinese manual, but works well and costs half the price of the BlueMaestro.

The approach we have taken here is not perfect, it's not even good, but it does extract the data accurately and should work properly for any client who just buys one (of these two models) and turns it on near his head. No need to identify it.

The actual calls to read ble data usually use characteristics and rely on a connection. With Beacons there may be no connection, just pick off the airwaves their advertising packets and use them if they are the right ones. There may be many other advertisers, but our solution is limited to having only one each of these beacons within range (maybe later we will improve this).

The program structure is a s follows.
- prepare the bluetooth adapter on the raspberry pi device.
- run hcitool lescan and hcidump --raw periodically to capture the advertising packets being emitted locally, store these into the ramdisk.
The way this works is that hcitool lescan identifies the beacons and opens a pathway inside the bluetooth adapter to listen for those advertising packets.
sudo hcidump captures these advertising packets and with the --raw > /mnt/ramdisk/hcidump.txt &' command writes all the advertising packets it sees to the ramdisk.
The pid for each command is obtained and the process killed after 1 sec. Note the maximum delay between advertising packets of the BlueMaestro is 800ms, so 1s delay is ample.

At this point we have the /mnt/ramdisk/hcidump.txt file containing all those advertising packets. The next step is to go through them and pick out the appropriate ones for our sensors and then locate the data points and convert these into decimal and output these as strings to /mnt/ramdisk/MandT.txt and /mnt/ramdisk/MandT2.txt
The readem() function basically assembles the packets from the lines using the fact that the first char of a packet output by hcidump is ">".
The packets are scanned for the device id and then when this is found the temperature and moisture is extracted.

I do not reccommend this approach and would be delighted for someone to show me a wiser approach to getting data from beacons.
import time
import os
import subprocess
import commands
import re

mjDevice = ""
def getPid(process):
	pid = subprocess.check_output(["pidof","-s",process])
	return pid

def gettem():
	cmd1 = 'hcitool lescan --duplicates > /dev/null &'
	cmd2="sudo kill -15 "
	cmd3 = 'sudo hcidump --raw  > /mnt/ramdisk/hcidump.txt &'
	os.system(cmd3)

	time.sleep(0.5)
	os.system (cmd1)
	time.sleep(1)
	pid = getPid('hcitool')
	#print("pid found  ",pid)	
	#print (cmd2 + pid)
	os.system(cmd2 + pid)
	time.sleep(0.5)
	#print("stop Dump")
	pidDump = getPid('hcidump')
	#print("pidDump found  ",pidDump)
	#print (cmd2 + pidDump )
	os.system(cmd2 + pidDump)

def readem():
	global mjDevice
	with open("/mnt/ramdisk/hcidump.txt") as f:
		packet = ""
		count = 0
		for line in (f):
			if line[0] == '>':
				packet = re.sub(r"[\n\t\s]*", "", packet)
				#print " ",packet
				if "33011764" in packet:
					temperature = packet[55:59]
					temp = float(eval("0x"+temperature)/10.0)
					#print temp
					moisture   =  packet[59:63]
					moist = float(eval("0x"+moisture)/10.0)
					#print moist
					fout = open( "/mnt/ramdisk/MandT.txt", 'w' )
					fout.write( "&t1="+str(temp) + '&m1=' + str(moist) + '\n' )
					fout.close()
				if "4D4A5F48545F5631" in packet:					
					mjDevice = packet[15:27]
					#print "f",packet
					#print mjDevice
				if mjDevice in packet and mjDevice != "":					
					if mjDevice + "0D1004" in packet:
						#print "got One"
						search = mjDevice + "0D1004"
						#print "search", search
						pos = packet.find(search)
						if pos > -1:
							#print packet
							#print "pos", pos
							#print packet[pos:pos+8]
							temperature = packet[pos+18+2:pos+18+2+2] + packet[pos+18:pos+18+2]
							moisture    = packet[pos+22+2:pos+22+2+2] + packet[pos+22:pos+22+2] 
							#print "t", temperature, "m", moisture
							temp2  = float(eval("0x"+temperature)/10.0)
							moist2 = float(eval("0x"+moisture)/10.0)
							fout2= open( "/mnt/ramdisk/MandT2.txt", 'w' )
							fout2.write( "&t2="+str(temp2) + '&m2=' + str(moist2) + '\n' )
							fout2.close()
				packet = ""
			packet = packet + line

print " this program reads a Blue Maestro Moisture meter"
print " and writes the Temp and Moist as floats 5.1 to  "
print " the ramdisk file /mnt/ramdisk/MandT.txt where it "
print " can be read by darshan.py to send to the database."
one = 1
while one > 0:
	os.system("sudo hciconfig hci0 down")
	os.system("sudo hciconfig hci0 up")
	gettem()
	readem()
	time.sleep(60)

read the Load-Cell Hx711

Python
The central part of the system is to read the weight attached to the load cell and post it to the database every couple of minutes.
Loadcells are conveniently connected to an hx711 digitizer.
The load cell has a metal element attached by the white glue in such a way that as the aluminium bar bends as weight is applied to one end the metal element changes its resistance. The Hx711 uses Wheatstone bridge approach to measure its resistance to 24 bits. In this program we read the 24 bits by raising and lowering the CLK line 24 times and each time reading the binary value on the DATA pin. This produces a 24 bit measurement of the resistance of the element on the aluminium load cell. A simple two point calibration procedure gives and offset and slope for converting these readings into grams.
The approach is not terribly accurate anyway, and in a varying temperature setting it's quite poor. If the counting to 24 gets off a bit then the results are wild. In this implementation we use a median rather than the mean. We make 71 readings with a short pause between each reading and then sort the results and pick the middle one to output every 2.5 minutes.

This python program is also responsible for sending up to 3 moisture and temperature values from bluetooth low energy beacons along with the weight to the database.

Database fields:-
id is the salted md5 hash of the user email. This will later be compared to a salted md5 hash computed in an Android app. This works well.
weight in g
time as a Unix timestamp, and 3 sets of temperature of relative humidity from the moisture meters.

The simplest way to connect two running python programs is through a file, in this case I use a Ram Disk made in the boot-up sequence in /etc/rc.local as

printf "make a 2Mb ramdisk"
if [!-e / mnt/ramdisk]; then
sudo mkdir /mnt/ramdisk
fi
sudo mount -t tmpfs -o size=2M none /mnt/ramdisk
touch /mnt/ramdisk/testfile

Note that this ramdisk survives a reboot.

The program AD_Monitor.moisture.py writes its temperature and humidity results to the ramdisk every 1 minute, and they are picked up every 2.5 minutes. There appears to me to be no great need to do things more precisely.

The calibration data is stored as binary representation and read using the pickle library.
The php target darshan.php in mydomain.com is a simple database program where the values are inserted into the database.
import RPi.GPIO as gpio
import time
import requests
import sys
import md5
import pickle
import re
import os.path
DT =11
SCK=8

HIGH=1
LOW=0

sample=0
val=0

gpio.setwarnings(False)
gpio.setmode(gpio.BCM)
gpio.setup(SCK, gpio.OUT)
orig_stdout = sys.stdout
f = open('/home/pi/hx711py/rc.local.log', 'w')
sys.stdout = f
print "setup as BCM DT = ",DT," SCK = ",SCK


def getEmail():
  file = open("/home/pi/hx711py/data/user.email", "r")
  em = file.read()
  return em.strip()
def hashAString(string):
  return md5.new(string+"a").hexdigest()
def readCount():
  i=0
  Count=0
  gpio.setup(DT, gpio.OUT)
  gpio.output(DT,1)
  gpio.output(SCK,0)
  gpio.setup(DT, gpio.IN)

  while gpio.input(DT) == 1:
      i=0
  for i in range(24):
        gpio.output(SCK,1)
        Count=Count<<1

        gpio.output(SCK,0)
        #time.sleep(0.001)
        if gpio.input(DT) == 0: 
            Count=Count+1
            #print Count
        
  gpio.output(SCK,1)
  Count=Count^0x800000
  time.sleep(0.001)
  gpio.output(SCK,0)
  return Count  

#begin()
#  # run the script darshanCalibrate.py to calibrate at eh factory first!!
try:
  with open ("/home/pi/hx711py/data/calibration.txt","rb") as f1:
    myList = pickle.load(f1)
except:
  print ("Exception",sys.exc_info()[0])
  print "You probably forgot to calibrate the load cell... run AD_Monitor.calibrate.py"
  exit()
slope = myList[0]
offset = myList[1]
print "slope = "+ str(slope) + " offset = "+str(offset)
flag=0
sumSamples = 0
nSamples = 0
listSamples = []
while 1:
    time.sleep(0.05)
    count= readCount()
    w=0
    w=(count-offset)/slope
    #print w,"g"
    sumSamples=sumSamples+w
    nSamples=nSamples+1
    listSamples.append(w)
    if nSamples > 70:
        avg = sumSamples / nSamples
        listSamples.sort()
        medianPos = nSamples / 2
        median = listSamples[medianPos]
        median = round(median,2)
        id = hashAString(getEmail())
        url = "https://mydomain.com/webtest/darshan.php?id="+id+"&weight="+str(median)
        #print url
        with open( "/mnt/ramdisk/MandT.txt") as f3:
          for line in (f3):
            url = url + line.strip()
            #print url

        if os.path.exists("/mnt/ramdisk/MandT2.txt"):
          #print url
          with open( "/mnt/ramdisk/MandT2.txt") as f4:
            for line in (f4):
              url = url + line.strip()
              #print url
        try:
          r = requests.get(url)
        except:
          print ("Exception",sys.exc_info()[0])
        nSamples = 0 
        sumSamples = 0
        listSamples = []
        time.sleep(95)

Credits

john brohan

john brohan

1 project • 2 followers
I'm interested in using computers to help people, especially the elderly and the handicapped

Comments