pHaTsensorNet Progress!
Breadboarded a prototype wireless pH sensor that:
- Runs on an Adafruit Bluefruit Feather 32u4 board
- Powered via USB and/or rechargeable LiPoly battery
- Reads pH data off Atlas Scientific EZO circuit connected to pH probe
- Puts pH data in Bluetooth Low Energy (BLE) GAP packets & broadcasts them
#include <Arduino.h>
#include <SPI.h>
#include <Adafruit_ATParser.h>
#include <Adafruit_BLE.h>
#include <Adafruit_BLEBattery.h>
#include <Adafruit_BLEEddystone.h>
#include <Adafruit_BLEGatt.h>
#include <Adafruit_BLEMIDI.h>
#include <Adafruit_BluefruitLE_SPI.h>
#include <Adafruit_BluefruitLE_UART.h>
#include <SoftwareSerial.h>
// from eddystone ex start
#define DEBUG 0
#include "BluefruitConfig.h"
#define FACTORYRESET_ENABLE 1
#define MINIMUM_FIRMWARE_VERSION "0.7.0"
#define URL "http://mdc?pH="
Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);
Adafruit_BLEEddystone eddyBeacon(ble);
#define rx 11 //define what pin rx is going to be 11 IS CORRECT
#define tx 12 //define what pin tx is going to be
SoftwareSerial myserial(rx, tx); //define how the soft serial port is going to work
String inputstring = ""; //a string to hold incoming data from the PC
String sensorstring = ""; //a string to hold the data from the Atlas Scientific product
String urlstring = URL;
char urlcharArray[17];
boolean input_string_complete = false; //have we received all the data from the PC
boolean sensor_string_complete = false; //have we received all the data from the Atlas Scientific product
float pH; //used to hold a floating point number that is the pH
float prev_pH;
void setup() {
ble.begin(VERBOSE_MODE);
ble.factoryReset();
myserial.begin(9600); //set baud rate for the software serial port to 9600
eddyBeacon.begin(true);
inputstring.reserve(10); //set aside some bytes for receiving data from the PC
sensorstring.reserve(30); //set aside some bytes for receiving data from Atlas Scientific product
}
void loop() { //here we go...
if (myserial.available() > 0) { //if we see that the Atlas Scientific product has sent a character
char inchar = (char)myserial.read(); //get the char we just received
if (inchar != '.') { // exclude the decimal point because we know it's to 3 decimal places
// and we'll need to make a valid URI from this string
sensorstring += inchar; //add the char to the var called sensorstring
}
if (inchar == '\r') { //if the incoming character is a <CR>
sensor_string_complete = true; //set the flag
}
}
if (sensor_string_complete == true) { //if a string from the Atlas Scientific product has been received in its entirety
//uncomment this section to see how to convert the pH reading from a string to a float
if (isdigit(sensorstring[0])) { //if the first character in the string is a digit
prev_pH = pH;
pH = sensorstring.toFloat(); //convert the string to a floating point number so it can be evaluated by the Arduino
if (abs (pH - prev_pH) > 0.1) {
urlstring += sensorstring;
urlstring.toCharArray(urlcharArray,20);
eddyBeacon.stopBroadcast();
eddyBeacon.setURL(urlcharArray);
eddyBeacon.startBroadcast();
urlstring = URL;
}
}
sensorstring = ""; //clear the string
sensor_string_complete = false; //reset the flag used to tell if we have received a completed string from the Atlas Scientific product
}
}
2. Wrote a python script ble2mqtt that:
- runs on a Rasperry Pi Zero W
- reads pH data from BLE GAP stream using bluez stack & bluepy library
- timestamps & formats pH & RSSI data as numerical, JSON & URL encoded MQTT messages
- sends MQTT messages to broker on data aggregation server (Raspberry Pi 3)
- TODO: modularize & set it up as a systemd daemon that starts up & runs automatically on power-up
- Sample data as it's read & sent up to mqtt broker:
pi@pizerow1:~ $ sudo ./ble2mqtt.py
Device e5:b5:5a:fa:76:de (random), RSSI=-69 dB
9 Complete Local Name Adafruit Bluefruit LE
3 Complete 16b Services aafe
22 16b Service Data aafe10ee026d64633f70483d34363036
2018-04-06 19:21:02.805284 e5:b5:5a:fa:76:de -69 http://mdc?pH=4606 4.6 3.1622776601683795
None published to mqtt broker with return code: 1
None published to mqtt broker with return code: 2
None published to mqtt broker with return code: 3
...
#!/usr/bin/python3
# ble2mqtt - script to read data coming into BLE device
# from a particular eddystone beacon
# put it in a json string
# and send it up to an mqtt broker
from json import JSONEncoder as json
from binascii import unhexlify
from datetime import datetime
# convert TX & RSSI to distance
# TX is measured from beacon
from math import pow
_TX_ = -59
def getDistance (RSSI, TX):
return pow(10, (TX - RSSI) / (10 * 2));
# scan for & pick up BLE beacon signal using bluepy
# to get bluepy: pip install bluepy
from bluepy.btle import Scanner, DefaultDelegate
_pHprobe1_addr = 'e5:b5:5a:fa:76:de'
class ScanDelegate(DefaultDelegate):
def __init__(self):
DefaultDelegate.__init__(self)
def handleDiscovery(self, dev, isNewDev, isNewData):
if isNewDev:
pass #print ("Discovered device", dev.addr)
elif isNewData:
pass #print ("Received new data from", dev.addr)
# publish to remote MQTT broker on Pi3
# pip install paho-mqtt
import paho.mqtt.client as mqtt
broker_num = '192.168.1.31'
broker_name = 'pithree1'
broker_port = 1883
def on_publish(client, userdata, result):
print(userdata, " published to mqtt broker with return code: ",result)
#pass
client = mqtt.Client("ph_topic")
client.on_publish = on_publish
client.connect(broker_num, broker_port)
while True:
scanner = Scanner().withDelegate(ScanDelegate())
devices = scanner.scan(10.0)
for dev in devices:
if dev.addr == _pHprobe1_addr:
print ("Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi))
for (adtype, desc, value) in dev.getScanData():
print (adtype, desc, value)
if adtype == 22:
pH = (int(value[-8:-6]) - 30) + \
(int(value[-6:-4]) - 30) * 0.1 + \
(int(value[-4:-2]) - 30) * 0.01
dist = getDistance(dev.rssi, _TX_)
pH_url = 'http://' + str(unhexlify(value[-22:]))[2:-1]
print (datetime.now(), dev.addr, dev.rssi, pH_url, pH, dist)
pH_json = json().encode (\
{'measurement': 'pH',
'time': str(datetime.now()),
'fields': {
'device': dev.addr,
'ph_url': pH_url,
'rssi': dev.rssi,
'dist': dist,
'pH' : pH } } )
# make a big json structure with all the things we want to put in the message
ret = client.publish ("pH_url", pH_url)
ret = client.publish ("pH_json", pH_json)
ret = client.publish ("pH", pH)
Data Aggregationon the Raspberry Pi 3
Sample data in MQTT broker on 'pH' numerical topic:
pi@pithree1:~ $ mosquitto_sub -h 192.168.1.31 -t pH
4.55
4.54
4.55
4.54
...
Sample data in MQTT broker on 'pH_url' topic
pi@pithree1:~ $ mosquitto_sub -h 192.168.1.31 -t pH_url
http://mdc?pH=4545
http://mdc?pH=4556
http://mdc?pH=4556
Sample data in MQTT broker on 'pH_json' topic:
pi@pithree1:~ $ mosquitto_sub -h 192.168.1.31 -t pH_json
{"measurement": "pH", "fields": {"device": "e5:b5:5a:fa:76:de", "ph_url": "http://mdc?pH=4559", "rssi": -53, "dist": 0.5011872336272722, "pH": 4.55}, "time": "2018-04-07 10:13:48.753482"}
{"measurement": "pH", "fields": {"device": "e5:b5:5a:fa:76:de", "ph_url": "http://mdc?pH=4560", "rssi": -53, "dist": 0.5011872336272722, "pH": 4.56}, "time": "2018-04-07 10:13:58.832027"}
{"measurement": "pH", "fields": {"device": "e5:b5:5a:fa:76:de", "ph_url": "http://mdc?pH=4559", "rssi": -59, "dist": 1.0, "pH": 4.55}, "time": "2018-04-07 10:14:08.900167"}
So, now we can do something interesting with the data, e.g. loading it into InfluxDB and graphing it and...RAISING ALERTS!!