Reputation: 71
I have been working on a simple sensor logger for my mini hydroponics setup, using the Google IOT Arm sample code (original code https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples/blob/master/CPUTemp/pi_cpu_temp_mqtt.py).
It has been working great. Every 60 seconds, the payload delivers sensor data to the Google Cloud, which I track in Data Studio.
I have added Adafruit sensors, and have been happily tracking the sensor data for a few weeks.
The Problem
My router automatically restarts once a day, at 3am, which has caused my Python code to hang. I could simply stop this restart, but instead prefer to catch or avoid the error entirely, in case of more unusual connection issues in future.
The Hope
The ability to reconnect, when able to, and to continue to send data to Google Cloud.
I will be adding control features in future, such as relayed lights and fans, so reliability is crucial to help my plants grow.
The Full Error
Traceback (most recent call last):
File "lily_telemetry.py", line 220, in <module>
main()
File "lily_telemetry.py", line 217, in main
main()
File "lily_telemetry.py", line 185, in main
client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port)
File "/home/pi/.local/lib/python3.5/site-packages/paho/mqtt/client.py", line 839, in connect
return self.reconnect()
File "/home/pi/.local/lib/python3.5/site-packages/paho/mqtt/client.py", line 962, in reconnect
sock = socket.create_connection((self._host, self._port), source_address=(self._bind_address, 0))
File "/usr/lib/python3.5/socket.py", line 694, in create_connection
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
File "/usr/lib/python3.5/socket.py", line 733, in getaddrinfo
for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -3] Temporary failure in name resolution
The Code
#!/usr/bin/python
import argparse
import json
import datetime
import time
import ssl
import subprocess
import board
import busio
import adafruit_mcp9808
import adafruit_tsl2591
import digitalio
import jwt
import paho.mqtt.client as mqtt
# Update and publish temperature readings at a rate of SENSOR_POLL per second.
SENSOR_POLL=60
# Initialise I2C Master Bus
i2c_bus = busio.I2C(board.SCL, board.SDA)
# Set temperature sensor inputs MCP9808 board
mcp1 = adafruit_mcp9808.MCP9808(i2c_bus)
mcp2 = adafruit_mcp9808.MCP9808(i2c_bus,0x1C)
# Set light sensor inputs TSL2591 board
tsl = adafruit_tsl2591.TSL2591(i2c_bus)
def create_jwt(project_id, private_key_file, algorithm):
"""Create a JWT (https://jwt.io) to establish an MQTT connection."""
token = {
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60),
'aud': project_id
}
with open(private_key_file, 'r') as f:
private_key = f.read()
print('Creating JWT using {} from private key file {}' .format(algorithm, private_key_file))
return jwt.encode(token, private_key, algorithm=algorithm)
def error_str(rc):
"""Convert a Paho error to a human readable string."""
return '{}: {}'.format(rc, mqtt.error_string(rc))
class Device(object):
"""Represents the state of a single device."""
def __init__(self):
self.temperature = 0
self.connected = False
def update_sensor_data(self):
# CPU Temp
rawdata = subprocess.check_output(["sudo", "/opt/vc/bin/vcgencmd", "measure_temp"]).decode().split('=', 1)[-1].rstrip()
sendtemp = '"temp":"{}"' .format(rawdata.replace("'", "-").split("-", 1)[0])
self.temperature = sendtemp
# Temperature Sensor 1 Data
self.sensortemperature_1 = '"sensor_temp_1":"{}"' .format(mcp1.temperature)
# Temperature Sensor 2 Data
self.sensortemperature_2 = '"sensor_temp_2":"{}"' .format(mcp2.temperature)
# Light Sensor Data
lux = tsl.lux
infrared = tsl.infrared
visible = tsl.visible
full_spectrum = tsl.full_spectrum
self.lightsensor_lux = '"light_sensor_lux":"{}"' .format(round(lux,4))
self.lightsensor_infrared = '"light_sensor_infrared":"{}"' .format(infrared)
self.lightsensor_visible = '"light_sensor_visible":"{}"' .format(visible)
self.lightsensor_full_spectrum = '"light_sensor_full_spectrum":"{}"' .format(full_spectrum)
def wait_for_connection(self, timeout):
"""Wait for the device to become connected."""
total_time = 0
while not self.connected and total_time < timeout:
time.sleep(1)
total_time += 1
if not self.connected:
raise RuntimeError('Could not connect to MQTT bridge.')
def on_connect(self, unused_client, unused_userdata, unused_flags, rc):
"""Callback for when a device connects."""
print('Connection Result:', error_str(rc))
self.connected = True
def on_disconnect(self, unused_client, unused_userdata, rc):
"""Callback for when a device disconnects."""
print('Disconnected:', error_str(rc))
print('...')
main()
self.connected = False
def on_publish(self, unused_client, unused_userdata, unused_mid):
"""Callback when the device receives a PUBACK from the MQTT bridge."""
print('Published message.')
print("Waiting {} seconds." .format(SENSOR_POLL))
def on_subscribe(self, unused_client, unused_userdata, unused_mid,
granted_qos):
"""Callback when the device receives a SUBACK from the MQTT bridge."""
print('Subscribed: ', granted_qos)
if granted_qos[0] == 128:
print('Subscription failed.')
def parse_command_line_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description='Lily Grow Logging using MQTT')
parser.add_argument(
'--project_id', required=True, help='GCP cloud project name')
parser.add_argument(
'--registry_id', required=True, help='Cloud IoT registry id')
parser.add_argument('--device_id', required=True, help='Cloud IoT device id')
parser.add_argument(
'--private_key_file', required=True, help='Path to private key file.')
parser.add_argument(
'--algorithm',
choices=('RS256', 'ES256'),
required=True,
help='Which encryption algorithm to use to generate the JWT.')
parser.add_argument(
'--cloud_region', default='us-central1', help='GCP cloud region')
parser.add_argument(
'--ca_certs',
default='roots.pem',
help='CA root certificate. Get from https://pki.google.com/roots.pem')
parser.add_argument(
'--num_messages',
type=int,
default=60,
help='Number of messages to publish.')
parser.add_argument(
'--mqtt_bridge_hostname',
default='mqtt.googleapis.com',
help='MQTT bridge hostname.')
parser.add_argument(
'--mqtt_bridge_port', default=8883, help='MQTT bridge port.')
return parser.parse_args()
def main():
args = parse_command_line_args()
# Create our MQTT client and connect to Cloud IoT.
client = mqtt.Client(
client_id='projects/{}/locations/{}/registries/{}/devices/{}'.format(
args.project_id, args.cloud_region, args.registry_id, args.device_id))
client.username_pw_set(
username='unused',
password=create_jwt(args.project_id, args.private_key_file,
args.algorithm))
client.tls_set(ca_certs=args.ca_certs, tls_version=ssl.PROTOCOL_TLSv1_2)
device = Device()
client.on_connect = device.on_connect
client.on_publish = device.on_publish
client.on_disconnect = device.on_disconnect
client.on_subscribe = device.on_subscribe
client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port)
client.loop_start()
# This is the topic that the device will publish telemetry events (temperature
# data) to.
mqtt_telemetry_topic = '/devices/{}/events'.format(args.device_id)
# Wait up to 5 seconds for the device to connect.
device.wait_for_connection(5)
# Update and publish temperature readings at a rate of SENSOR_POLL per second.
for _ in range(args.num_messages):
device.update_sensor_data()
# Time
sendtime = '"datetime":"{}"'.format(datetime.datetime.now().strftime("%Y-%m-%d"" ""%H:%M:%S.%f"))
# Payload
payload = '{{''{},{},{},{},{},{},{},{}''}}'.format(device.temperature, device.sensortemperature_1,device.sensortemperature_2,device.lightsensor_lux,device.lightsensor_infrared,device.lightsensor_visible,device.lightsensor_full_spectrum,sendtime)
# Message
print('Payload: {}'.format(payload))
# Publish, Sleep
client.publish(mqtt_telemetry_topic, payload, qos=1)
time.sleep(SENSOR_POLL)
client.disconnect()
client.loop_stop()
print('Finished loop successfully. Goodbye!')
main()
if __name__ == '__main__':
main()
Upvotes: 4
Views: 1647
Reputation: 71
Thank you hardillb for your suggestion, using from socket import gaierror
try:
client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port)
except gaierror as e:
print('Gaierror {}.'.format(e))
print('Waiting {} seconds before restarting Main'.format(sensor_delay))
time.sleep(sensor_delay)
restartlily()
restartlily()
restarts the whole script from the top
Upvotes: 2