4
\$\begingroup\$

This is a continuation from this question:

Reading analog moisture signal from Arduino over USB to RPi3 and publishing via paho MQTT

I made the advised changes, and added a bunch of new stuff, I'd like it if I could get some further constructive criticism on it. Especially the error handling I have added in.

I was having a lot of issues with reading the Arduino serial output and kept getting very weird output so a I baked in some basic error handling and validation of the received data from it.

I have also included the listener that persists the MQTT message to an InfluxDB database.

The flow of the project goes like this:

  1. Moisture reading from moisture sensor to Arduino
  2. Arduino to Raspberry Pi 3 via serial USB
  3. Raspberry Pi 3 to MQTT Broker
  4. MQTT Broker to InfluxDB and then Grafana

import serial
import time
import RPi.GPIO as GPIO
import paho.mqtt.client as mqtt
import logging
import sys

logging.basicConfig(filename='planty.log', level=logging.ERROR)

MIN = 330
MAX = 970
ALERT_LOW = 10
ALERT_HIGH = 90
ALERT_THRESHOLD = 30
MQTT_HOST = 'hostname'
MQTT_PORT = 1883
MQTT_USER = 'root'
MQTT_PASS = 'root'
PIN_POT = {7:0}


def read_sensor():
    """
    Read analog moisture reading from moisture probe via Arduino via USB.
    try exists as Arduino sometimes passes bogus readings which causes
    IndexErrors
    """
    try:
        ser = serial.Serial('/dev/ttyACM0', 9600)
        raw_reading = ser.readline().strip()
        print raw_reading
        if  8 > len(raw_reading) >= 5:
            # If reading is valid length do:
            reading = {'pot': int(raw_reading.split(':')[1]),
                    'moisture_raw': int(raw_reading.split(':')[0])}
        else:
            reading = {'invalid':1}
    except: # catch *all* exceptions
        e = 'Error:%s Reading: %s' % (str(sys.exc_info()[0]), raw_reading)
        logging.error(e)
        reading = {'invalid':1}
    finally:
        ser.close()
        return reading


def validate(data):
    check = 0
    try:
        if 'invalid' not in data:
            # if there is no invalid key
            # print 'Invlaid key not detected'
            if  1024 > data['moisture_raw'] > 0:
                # Valid moisture reading
                # print 'Valid moisture reading'
                if data['pot'] in PIN_POT:
                    # Valid serial reading of pin
                    # print 'PIN_POT key exists'
                    check = 1
                else:
                    print 'PIN_POT key does not exist'
            else:
                print 'Invalid moisture reading'
        else:
            print 'Invalid key detected'
    except:
        e = 'Error:%s Reading: %s' % (str(sys.exc_info()[0]), raw_reading)
        logging.error(e)
    finally:
        return check


def process_raw_data():
    """
    Calls read_sensor(), checks if the reading is valid (sometimes over 1023 or null)
    Assigns the correct Pot for the Pin used via PIN_POT dictionary
    Normalizes the reading if it falls outside the ranges
    """
    valid = 0
    while valid == 0:
        data = read_sensor()
        valid = validate(data)
    data['pot'] = PIN_POT[data['pot']]
    # Set moisture_adj
    if data['moisture_raw'] < MIN:
        data['moisture_adj'] = MIN
    elif data['moisture_raw'] > MAX:
        data['moisture_adj'] = MAX
    else:
        data['moisture_adj'] = 0
        data['moisture'] = moisture(data['moisture_raw'])
        print data
        return data
    data['moisture'] = moisture(data['moisture_adj'])
    print data
    return data


def moisture(data):
    return int(round(100-(100/float(MAX-MIN))*(data-MIN), 0))


def pub_mqtt(output, topic):
    """Publish to MQTT for given topic"""
    mqtt_client = mqtt.Client()
    mqtt_client.username_pw_set(MQTT_USER, MQTT_PASS)
    mqtt_client.connect(MQTT_HOST, MQTT_PORT)
    mqtt_client.publish(topic, output)


def alerts(data):
    """Generates alerts that are humanly readable for certain moisture thresholds"""
    if data['moisture'] <= ALERT_LOW:
        output = 'LOW ALERT'
        relay(data['pot'])
        time.sleep(60*5)
    elif ALERT_LOW > data['moisture'] <= ALERT_THRESHOLD:
        output = 'THRESHOLD ALERT'
        relay(data['pot'])
        time.sleep(60*5)
    elif data['moisture'] >= ALERT_HIGH:
        output = 'HIGH ALERT'
    else:
        return
    pub_mqtt(output, 'Planty/alerts')


def relay(pin):
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        GPIO.setup(pin, GPIO.OUT, initial=1)
        # Message pin on
        pub_mqtt(str(pin) + ':1', 'Planty/relay')
        GPIO.output(pin, 0)
        time.sleep(5)
        # Message pin off
        pub_mqtt(str(pin) + ':0', 'Planty/relay')
        GPIO.output(pin, 1)
    finally:
        GPIO.cleanup()


def debugger(data):
    """This is just to test each reading if there are issues"""
    output ='\n=====DEBUG LOG=====\n' \
            'raw is: {}\n' \
            'raw_adjusted is: {}\n' \
            'soil moisture is: {}%\n' \
            .format(data['moisture_raw'],
            data['moisture_adj'],
            data['moisture'])
    pub_mqtt(output, 'Planty/debug')


def main():
    while 1:
        data = process_raw_data()
        output = '%d:%d:%d:%d' \
        % (data['pot'],
        data['moisture_raw'],
        data['moisture_adj'],
        data['moisture'])
        pub_mqtt(output, 'Planty/moisture_readings')
        # alerts(data)
        # debugger(data)


if __name__ == '__main__':
    main()

Listener:

import paho.mqtt.client as mqtt
from influxdb import InfluxDBClient

INFLUX_HOST = 'hostname'
INFLUX_PORT = 8086
INFLUX_DB = 'Planty'
INFLUX_USER = 'root'
INFLUX_PASS = 'root'
MQTT_HOST = 'hostname'
MQTT_PORT = 1883
MQTT_USER = 'root'
MQTT_PASS = 'root'
MQTT_TOPIC = 'Planty/moisture_readings'


def on_message(client, userdata, msg):
    raw_message = msg.payload.split(':')
    data = {}
    print raw_message
    data['pot'] = raw_message[0]
    data['moisture_raw'] = raw_message[1]
    data['moisture_adj'] = raw_message[2]
    data['moisture'] = raw_message[3]
    json_body = [
        {
            "measurement": "moisture_readings",
            "tags": {
                "pot": data['pot']
                },
            "fields": {
                "moisture_raw": data['moisture_raw'],
                "moisture_adj": data['moisture_adj'],
                "moisture": data['moisture']
            }
        }
    ]
    client = InfluxDBClient(INFLUX_HOST,INFLUX_PORT, INFLUX_USER, INFLUX_PASS, INFLUX_DB)
    client.write_points(json_body)


def main():
    mqttc = mqtt.Client()
    mqttc.username_pw_set(MQTT_USER, MQTT_PASS)
    mqttc.connect(MQTT_HOST, MQTT_PORT)
    mqttc.subscribe(MQTT_TOPIC)
    mqttc.on_message = on_message
    mqttc.loop_forever()

if __name__ == '__main__':
    main()
\$\endgroup\$
  • 1
    \$\begingroup\$ MQTT_HOST is missing a closing quote. Can you check that there is no other error of that sort? \$\endgroup\$ – Mathias Ettinger Nov 13 '16 at 15:53
  • \$\begingroup\$ @MathiasEttinger that was just from me removing my personal settings. Fixed. \$\endgroup\$ – KyleMHB Nov 13 '16 at 17:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.