Grovepi+ seems unstable

Hello Dexter-Users

TL;DR Error when using python2 and multiple sensors

I/O operation on closed file

or

argument must be an int, or have a fileno() method.

Background
I have a RaspberryPi 3 B+ and the grovePi Board
Connected to the grovePi are

  • 2x SPDT 4 Channel Relay
  • 2x Humidity and Temperature Sensor Pro
  • 2x Buttons
  • 2x Moisture Sesnsor

All the sample programs are working but as soon as I “connect” them or using more than one, i get one or both of the above error messages

My Code

import sys
import signal
import threading
import logging
import time
import datetime
import math

import grove

event_sigint = threading.Event()
event_button_pressed = threading.Event()
event_button1_pressed = threading.Event()
event_button2_pressed = threading.Event()
event_dht_reading = threading.Event()

LIGHT1_TIME_ON = datetime.time(16, 0, 0)
LIGHT1_TIME_OFF = datetime.time(20, 0, 0)

VENTILATOR1_TEMP_ON = 28
VENTILATOR1_CYCLE_MINUTE_ON = 10
VENTILATOR1_CYCLE_MINUTE_OFF = 20

BUTTON1_PIN = 2
BUTTON2_PIN = 3
DHT1_PIN = 4
DHT2_PIN = 8

temp1 = temp2 = 0
humi1 = humi2 = 0


def signal_handler(sig, frame):
    print("CTRL + C received!")
    event_sigint.set()
    sys.exit(0)


def read_buttons():
    while event_sigint.is_set() is False:
        print('waiting for event btn press')
        if grove.digitalRead(BUTTON1_PIN) > 0:
            # print('btn {} pressed'.format(BUTTON1_PIN))
            event_button1_pressed.set()

        if grove.digitalRead(BUTTON2_PIN) > 0:
            # print('btn {} pressed'.format(BUTTON2_PIN))
            event_button2_pressed.set()
        time.sleep(.1)


def main():
    global temp1, temp2, humi1, humi2

    signal.signal(signal.SIGINT, signal_handler)

    """ mark 1 """
    th_btn = threading.Thread(name='Read Buttons', target=read_buttons)
    th_btn.start()
    """ mark 2"""

    read_dht = True
    while event_sigint.is_set() is False:
        time_now = datetime.datetime.now()
        event_button_pressed.wait(.5)
        if event_button1_pressed.is_set():
            print('buttonX 1 pressed')
            event_button1_pressed.clear()

        if event_button2_pressed.is_set():
            print('buttonX 2 pressed')
            event_button2_pressed.clear()

        """ mark 3"""
        if time_now.second == 0:
            if read_dht:
                [temp1, humi1] = grove.dht(4, 1)
                [temp2, humi2] = grove.dht(8, 1)
                read_dht = False

                if math.isnan(temp1) is False and math.isnan(humi1) is False:
                    print("temp1 = %.02f C humidity1 = %.02f%%" % (temp1, humi1))

                if math.isnan(temp2) is False and math.isnan(humi2) is False:
                    print("temp2 = %.02f C humidity2 = %.02f%%" % (temp2, humi2))
        else:
            read_dht = True
        """ mark 4"""


        # time.sleep(.2)


if __name__ == '__main__':
    main()

If I comment out the the button stuff (mark1 - mark 2) everything is working fine.
also, if I comment out the dht_reading (mark3 - mark 4) everything is working fine.

but if both readings are active, i get the I/O Error.

I already tried different aproaches to read the vars. my initial version had a thread for everything -> no luck
I then created an event, so that when the dht_reading is running, no button operations are done and vice-versa -> no luck

The OS and the firmware are up to date. my next approach would be using C but I’m not sure if that would help. Do you have any suggestions for me?

my /boot/config.txt

# For more options and information see
# http://rpf.io/configtxt
# Some settings may impact device functionality. See link above for details

# uncomment if you get no picture on HDMI for a default "safe" mode
#hdmi_safe=1

# uncomment this if your display has a black border of unused pixels visible
# and your display can output without overscan
#disable_overscan=1

# uncomment the following to adjust overscan. Use positive numbers if console
# goes off screen, and negative if there is too much border
#overscan_left=16
#overscan_right=16
#overscan_top=16
#overscan_bottom=16

# uncomment to force a console size. By default it will be display's size minus
# overscan.
#framebuffer_width=1280
#framebuffer_height=720

# uncomment if hdmi display is not detected and composite is being output
hdmi_force_hotplug=1

# uncomment to force a specific HDMI mode (this will force VGA)
hdmi_group=2
hdmi_mode=16

# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
#hdmi_drive=2

# uncomment to increase signal to HDMI, if you have interference, blanking, or
# no display
#config_hdmi_boost=4

# uncomment for composite PAL
#sdtv_mode=2

#uncomment to overclock the arm. 700 MHz is the default.
#arm_freq=800

# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2s=on

# Uncomment this to enable the lirc-rpi module
#dtoverlay=lirc-rpi

# Additional overlays and parameters are documented /boot/overlays/README

# Enable audio (loads snd_bcm2835)
dtparam=audio=on
start_x=0
gpu_mem=128
dtparam=i2c1=on
dtparam=spi=off
dtparam=i2c_arm=on
dtparam=i2c_arm_baudrate=50000
enable_uart=0

My Goal
I have 8 relays
1 and 2 should be active every hour from minute 0 - 30
3 and 4 should be active from 16:00 - 20:00. If i press button 1, the relay 3 should toggle and for button 2 it’s the relay 4
Moisture sensore is not yet available but depending on the moisture, an LED, maybe a buzzer and relay 1-2 should be active

As soon as I get the I/O Error I cant abot the programm with CTRL + C. debugging showed me that I’m stuck in

# Write I2C block to the GrovePi
def write_i2c_block(block):
    counter = 0
    reg = block[0]
    data = block[1:]
    while counter < 3:
        try:
            i2c.write_reg_list(reg, data)
            time.sleep(0.002 + additional_waiting)
            return
        except IOError:
            counter += 1
            time.sleep(0.003)
            continue
        except Exception as e:
            logging.error('ERR: grove.py Line 90', exc_info=e)
            counter += 1
            time.sleep(0.003)
            continue

I always find it’s kinda funny to reply to myself.

I was further reading in these forums and found some threads about mutexes and the like

I’m stiil using the threading.Events() now together with mutexes

global

mutex = threading.Lock()

and for reading the button states

def read_buttons():
    while event_sigint.is_set() is False:
        mutex.acquire()
        try:
            # print('waiting for event btn press')
            if grove.digitalRead(BUTTON1_PIN) > 0:
                # print('btn {} pressed'.format(BUTTON1_PIN))
                event_button1_pressed.set()

            if grove.digitalRead(BUTTON2_PIN) > 0:
                # print('btn {} pressed'.format(BUTTON2_PIN))
                event_button2_pressed.set()
        finally:
            mutex.release()
        time.sleep(.1)

and last but not least, the DHT Sensor

read_dht = True
    while event_sigint.is_set() is False:
        time_now = datetime.datetime.now()
        event_button_pressed.wait(.5)
        if event_button1_pressed.is_set():
            print('buttonX 1 pressed')
            event_button1_pressed.clear()

        if event_button2_pressed.is_set():
            print('buttonX 2 pressed')
            event_button2_pressed.clear()

        # check temp and humidity every 5secs
        if time_now.second % 5 == 0:
            mutex.acquire()
            try:
                if read_dht:
                    [temp1, humi1] = grove.dht(4, 1)
                    [temp2, humi2] = grove.dht(8, 1)
                    read_dht = False

                    if math.isnan(temp1) is False and math.isnan(humi1) is False:
                        print("temp1 = %.02f C humidity1 = %.02f%%" % (temp1, humi1))

                    if math.isnan(temp2) is False and math.isnan(humi2) is False:
                        print("temp2 = %.02f C humidity2 = %.02f%%" % (temp2, humi2))
            finally:
                mutex.release()
        else:
            read_dht = True

Now i can start the programm and it works for at least 5mins (haven’t done any “long-time” tests yet)

I’ll go further and add more sensors and actors

2 Likes

Does it help to know we’re listening to you talking to yourself?

That’s peculiar. Python has what is called a GIL (Global Interpreter Lock) and that essentially limits a Python program to one real thread, no matter how many threads you spawn.

The only difference is when spawning different processes within a Python program - that’s when you actually need locks around shared resources.

Your program seems to behave as if there are multiple processes going in parallel.

Edit: Actually, I looked at the source code and I realized that the actual atomic I2C transfers are thread-safe because there’s the GIL in Python, but there aren’t locks to make the macro transactions (or as we should call them API calls) safe. The idea is that calling a function of the API is composed of multiple other I2C transfers. Since there’s no lock around these groups of atomic I2C transfers, when accessing the GrovePi concurrently, the API calls will overlay each other and cause your issue.

This alternative construct might save you some typing (and possible typo errors) in the future:

try:  print("temp1 = %.02f C humidity1 = %.02f%%" % (temp1, humi1))
except TypeError:  pass

I get it now - the sensor can return float(‘nan’). my bad.

Check the Important note:
https://dexterind.github.io/GrovePi/api/gpio/

yes, it does not help but I appreciate that you’re with me :slight_smile:

1 Like