muckmuckmuck
Posts: 11
Joined: Fri Feb 12, 2016 6:22 am

Error accessing SHT35 (or SHT31) Temp and Humidity sensor in parallel via flask

Sun Jan 12, 2020 12:10 am

Background:
I'm a python newbie. I have a flask application, which runs a (private, LAN only) web service for gathering temperature, humidity, and runtime information for an air conditioner/heat pump.
It's an RV style ac/heat pump, which I use for hydroponically growing fodder for livestock...
I built a web service because my thermostat software (turns the HVAC system on/off) needs access to environmental information, but I also have some monitoring, and other tools which simultaneously need to access the environmentals, and HVAC status.

I'm using a DS18b20 sensor (temp only), and a SHT35 I2C sensor (Temperature/humidity).

Problem:
I can query the DS18B20 sensor as often as I want, including from simultaneous web clients (ie: curl) no problem. But when I try to query the SHT3X sensor from more than one client at a time, I get an error, and then I have to do a hard shutdown of the Raspberry pi in order for it to work again. I can query the SHT3X sensor reliably for weeks on end as long as only one client at a time request the data. Two clients, simultaneously, *kaboom*.

The only way I can think of to solve this is to build another python app, whose sole job is to query the SHT3X sensor every 10 seconds, write the value to disk, and then change my flask app to just read from disk, instead of from the sensor directly, but this seems hacky to me, and also more prone to false readings. Any other solution, including using a separate thread or something asynchronous in python seems beyond my skill level unless someone can provide me with some halfway decent examples. I'm open to suggestions. Thanks!

Error I'm receiving when accessing from two clients simultaneously:

Code: Select all

ERROR in app: Exception on /alljson [GET]
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.7/dist-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.7/dist-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.7/dist-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/local/lib/python3.7/dist-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.7/dist-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "./thermostat-webserver.py", line 134, in index
    outdoor_humidity=get_outdoor_humidity()
  File "./thermostat-webserver.py", line 71, in get_outdoor_humidity
    outdoor_sensor=get_sht3x_temp()
  File "./thermostat-webserver.py", line 28, in get_sht3x_temp
    bus.write_i2c_block_data(0x44, 0x2C, [0x06])
OSError: [Errno 121] Remote I/O error
My code (I removed some extra stuff, like the DS18 sensor, and other urls that aren't relevent):

Code: Select all

#!/usr/bin/python3
import RPi.GPIO as GPIO
import time
import datetime
import os
import sys
import smbus
import logging
import uptime
from flask import Flask
app = Flask(__name__)

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
GPIO.setup(23, GPIO.OUT)
GPIO.setup(24, GPIO.OUT)
GPIO.setup(25, GPIO.OUT)
# Get I2C bus for sht30 temp and humidity sensor
bus = smbus.SMBus(1)

def get_sht3x_temp():
  # SHT31 address, 0x44(68)
  bus.write_i2c_block_data(0x44, 0x2C, [0x06])

  time.sleep(0.5)

  # SHT31 address, 0x44(68)
  # Read data back from 0x00(00), 6 bytes
  # Temp MSB, Temp LSB, Temp CRC, Humididty MSB, Humidity LSB, Humidity CRC
  data = bus.read_i2c_block_data(0x44, 0x00, 6)

  # Convert the data
  temp = data[0] * 256 + data[1]
  cTemp = round(-45 + (175 * temp / 65535.0),1)
  fTemp = round(-49 + (315 * temp / 65535.0),1)
  humidity = 100 * (data[3] * 256 + data[4]) / 65535.0

  #Return the results in a dictionary
  return {'fTemp':fTemp, 'humidity':humidity}

def get_outdoor_temp():
  outdoor_sensor=get_sht3x_temp()
  return (outdoor_sensor['fTemp'])

def get_outdoor_humidity():
  outdoor_sensor=get_sht3x_temp()
  return (outdoor_sensor['humidity'])

@app.route("/")
@app.route("/alljson")
def index():
  unit_status=get_unit_status()
  indoor_temp=get_indoor_temp()
  outdoor_humidity=get_outdoor_humidity()
  outdoor_temp=get_outdoor_temp()
  return {'fan':unit_status['fan'], 'mode':unit_status['mode'], 'temp_indoor1':indoor_temp, 'temp_outdoor1':outdoor_temp, 'humidity_outdoor1':int(outdoor_humidity) }

if __name__ == "__main__":
  app.run(host='0.0.0.0', port=9999)
Sample output:

Code: Select all

[email protected]:~/fodder# curl http://127.0.0.1:9999/alljson
{"fan":0,"humidity_outdoor1":64,"mode":0,"temp_indoor1":65.2,"temp_outdoor1":52.8}
[email protected]:~/fodder# 

User avatar
paddyg
Posts: 2464
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Error accessing SHT35 (or SHT31) Temp and Humidity sensor in parallel via flask

Sun Jan 12, 2020 2:54 pm

Maybe you could cludge a kind of global lock

Code: Select all

...
def get_sht3x_temp():
  global sht3x_lock
  for _i in range(5): # or whatever retry and 'not available' system you want to use
    if sht3x_lock
      time.sleep(0.5)
    else:
      break
  if sht3x_lock:
    return {'fTemp':-666, 'humidity':-666}
  sht3x_lock = True
  # SHT31 address, 0x44(68)
  bus.write_i2c_block_data(0x44, 0x2C, [0x06])
  ...
  #Return the results in a dictionary
  sht3x_lock = False
  return {'fTemp':fTemp, 'humidity':humidity}
...
sht3x_lock = False
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

muckmuckmuck
Posts: 11
Joined: Fri Feb 12, 2016 6:22 am

Re: Error accessing SHT35 (or SHT31) Temp and Humidity sensor in parallel via flask

Mon Jan 13, 2020 1:36 am

FWIW, I solved this by using flask_apscheduler to run a background process once every 10 seconds to update the temperature and humidity to a global variable, and then just reference that global variable when I need it. An snippet of my code is below (won't work on its own, but shows how I used flask_apscheduler)

Code: Select all

from flask_apscheduler import APScheduler

def sht3xBackground():
  global BG_outdoor_temp
  global BG_outdoor_humidity
  BG_outdoor_temp=get_outdoor_temp()
  BG_outdoor_humidity=get_outdoor_humidity()
  logging.debug(f"Background SHT3X job: Temp is {BG_outdoor_temp} Humidity is {BG_outdoor_humidity}")

sht3xBackground()


class Config(object):
    SCHEDULER_API_ENABLED = True

scheduler = APScheduler()

if __name__ == '__main__':
  app = Flask(__name__)
  app.config.from_object(Config())

  scheduler.add_job(func=sht3xBackground, trigger='interval', seconds=10, misfire_grace_time=900, id="sht3xBackGroundUpdate")

  # it is also possible to enable the API directly
  # scheduler.api_enabled = True
  scheduler.init_app(app)
  scheduler.start()


  @app.route("/")
  @app.route("/alljson")
  def index():
    unit_status=get_unit_status()
    indoor_temp=get_indoor_temp()
    outdoor_humidity=BG_outdoor_humidity
    outdoor_temp=BG_outdoor_temp
    return {'fan':unit_status['fan'], 'mode':unit_status['mode'], 'temp_indoor1':indoor_temp, 'temp_outdoor1':outdoor_temp, 'humidity_outdoor1':int(outdoor_humidity) }


 app.run(host="0.0.0.0", port=9999, debug=False)


Return to “Python”