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 acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.