Christian Bird
Christian Bird

Reputation: 71

Raspberry Pi, Socket Error "socket.gaierror: [Errno -3] Temporary failure in name resolution"

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

Answers (1)

Christian Bird
Christian Bird

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

Related Questions