PiZero W connected to two peripherals (GPIO and USB): how to continuously read from both at same time?

I have a raspberry pizero W that is connected via GPIO pins to a flowmeter and USB to a barcode scanner. I have python script that uses a callback function to be alerted when the GPIO input is sensed. This python script needs to be continuously running on the pizero in order to recognize when the flowmeter is activated and process the input.

The problem is that I also have a barcode scanner attached via USB to the pizero. I would like the pizero to also recognize when a barcode is scanned and process that input as well.

The pizero should then send a message that incorporates both information from the flowmeter and information from the barcode scanner.

Is there a way to do this in the same python script? How can I have the pizero listen and process from two inputs simultaneously? Would separating this into two different scripts be easier to implement, and if so, can I run them both at the same time and somehow unify the information they provide in a 3rd continuously running script?

Thank you!

Some clarifications per comments (thank you for the input):

  • input pin from the flowmeter is GPIO 17 which is an SPI connection
  • also have a 5V power and ground pin connected.

The script needs to be run at system startup. I will look at systemctl as I hadn't heard of it until it was mentioned.

The Pi normally recognizes a barcode being scanned as keyboard input (i.e. a series of digits followed by a newline character) when the flowmeter is not attached.

When I send a message that includes the flowmeter and barcode information, I need to send a JSON object from python that includes both pieces of information and a time stamp of when the information was received.

This JSON object will be sent over wifi to a raspberry pi server with a static ip on the same home network as the pizero. The raspberry pi server has access to a Django database that should incorporate the JSON object information into the database.


Another, perhaps simpler option might be to use Redis. Redis is a very high performance, in-memory data-structure server. It is simple to install on a Raspberry Pi, Mac, Linux Windows or other machine. It allows you to share atomic integers, strings, lists, hashes, queues, sets and ordered sets between any number of clients across a network.

So the concept might be to have a separate program monitoring the flowmeter and stuffing the current reading into Redis as often as you like. Then another separate program reading barcodes and stuffing them into Redis as often as you like. And finally, have a control program, potentially somewhere else altogether on your network that can grab both the values as often as it likes.

Note you could run the Redis server on your Raspberry Pi or any other machine.

So, here is the flowmeter program - just change host to the IP address of machine running Redis:

#!/usr/bin/env python3

import redis
import time

host='localhost'
port=6379

# Connect to Redis
r = redis.Redis(host,port)

reading = 0
while True:
   # Generate synthetic reading that just increases every 500ms
   reading +=1 
   # Stuff reading into Redis as "fmReading"
   r.set("fmReading",reading)
   time.sleep(0.5)

Here is the barcode reading program:

#!/usr/bin/env python3

import redis
import time
from random import random, seed

host='localhost'
port=6379

# Connect to local Redis server
r = redis.Redis(host,port)

# Generate repeatable random numbers
seed(42)

while True:
   # Synthesize barcode and change every 2 seconds
   barcode = "BC" + str(int((random()*1000)))
   # Stuff barcode into Redis as "barcode"
   r.set("barcode",barcode)
   time.sleep(2)

And here is the master control program:

#!/usr/bin/env python3

import redis
import time

host='localhost'
port=6379

# Connect to Redis server
r = redis.Redis(host,port)

while True:
   # Grab latest flowmeter reading and barcode
   fmReading = r.get("fmReading")
   barcode   = r.get("barcode")
   print(f"Main: fmReading={fmReading}, barcode={barcode}")
   time.sleep(1)

Sample Output

Main: fmReading=b'10', barcode=b'BC676'
Main: fmReading=b'12', barcode=b'BC892'
Main: fmReading=b'14', barcode=b'BC892'
Main: fmReading=b'16', barcode=b'BC86'
Main: fmReading=b'18', barcode=b'BC86'
Main: fmReading=b'20', barcode=b'BC421'
Main: fmReading=b'22', barcode=b'BC421'
Main: fmReading=b'24', barcode=b'BC29'

Note that in order to debug this, you can also grab any readings using the command-line interface to Redis from any machine on your network, e.g. in Terminal:

redis-cli get barcode
"BC775"

If you wanted to show the values in a Web-browser written in PHP, you can use the PHP bindings to Redis to get the values too - very convenient!

Of course, you can adjust the program to send the timestamps of the readings with each one - possibly by using a Redis hash rather than a simple key. You could also implement a queue to send messages between program using Redis LPUSH and BRPOP.

Keywords: Redis, list, queue, hash, Raspberry Pi


Updated Answer

I have added some code for the barcode reader. I made it so that the barcode reader takes a variable amount of time, up to 5 seconds to take a reading and the flowmeter takes a constant 0.5 seconds so you can see that different threads are progressing at different rates independently of one another.

#!/usr/bin/env python3

from threading import Lock
import threading
import time
from random import seed
from random import random

# Dummy function to read SPI as I don't have anything attached
def readSPI():
    # Take 0.5s to read
    time.sleep(0.5)
    readSPI.static += 1
    return readSPI.static
readSPI.static=0

class FlowMeter(threading.Thread):
    def __init__(self):
        super(FlowMeter, self).__init__()
        # Create a mutex
        self.mutex = Lock()
        self.currentReading = 0

    def run(self):
        # Continuously read flowmeter and safely update self.currentReading
        while True:
            value = readSPI()
            self.mutex.acquire()
            self.currentReading = value
            self.mutex.release()

    def read(self):
        # Main calls this to get latest reading, we just grab it from internal variable
        self.mutex.acquire()
        value = self.currentReading
        self.mutex.release()
        return value

# Dummy function to read Barcode as I don't have anything attached
def readBarcode():
    # Take variable time, 0..5 seconds, to read
    time.sleep(random()*5)
    result = "BC" + str(int(random()*1000))
    return result

class BarcodeReader(threading.Thread):
    def __init__(self):
        super(BarcodeReader, self).__init__()
        # Create a mutex
        self.mutex = Lock()
        self.currentReading = 0

    def run(self):
        # Continuously read barcode and safely update self.currentReading
        while True:
            value = readBarcode()
            self.mutex.acquire()
            self.currentReading = value
            self.mutex.release()

    def read(self):
        # Main calls this to get latest reading, we just grab it from internal variable
        self.mutex.acquire()
        value = self.currentReading
        self.mutex.release()
        return value

if __name__ == '__main__':

    # Generate repeatable random numbers
    seed(42)

    # Instantiate and start flow meter manager thread
    fmThread = FlowMeter()
    fmThread.daemon = True
    fmThread.start()

    # Instantiate and start barcode reader thread
    bcThread = BarcodeReader()
    bcThread.daemon = True
    bcThread.start()

    # Now you can do other things in main, but always get access to latest readings
    for i in range(20):
        fmReading = fmThread.read()
        bcReading = bcThread.read()
        print(f"Main: i = {i} FlowMeter reading = {fmReading}, Barcode={bcReading}")
        time.sleep(1)

Sample Output

Main: i = 0 FlowMeter reading = 0, Barcode=0
Main: i = 1 FlowMeter reading = 1, Barcode=0
Main: i = 2 FlowMeter reading = 3, Barcode=0
Main: i = 3 FlowMeter reading = 5, Barcode=0
Main: i = 4 FlowMeter reading = 7, Barcode=BC25
Main: i = 5 FlowMeter reading = 9, Barcode=BC223
Main: i = 6 FlowMeter reading = 11, Barcode=BC223
Main: i = 7 FlowMeter reading = 13, Barcode=BC223
Main: i = 8 FlowMeter reading = 15, Barcode=BC223
Main: i = 9 FlowMeter reading = 17, Barcode=BC676
Main: i = 10 FlowMeter reading = 19, Barcode=BC676
Main: i = 11 FlowMeter reading = 21, Barcode=BC676
Main: i = 12 FlowMeter reading = 23, Barcode=BC676
Main: i = 13 FlowMeter reading = 25, Barcode=BC86
Main: i = 14 FlowMeter reading = 27, Barcode=BC86
Main: i = 15 FlowMeter reading = 29, Barcode=BC29
Main: i = 16 FlowMeter reading = 31, Barcode=BC505
Main: i = 17 FlowMeter reading = 33, Barcode=BC198
Main: i = 18 FlowMeter reading = 35, Barcode=BC198
Main: i = 19 FlowMeter reading = 37, Barcode=BC198

Original Answer

I would suggest you look at systemd and systemctl to get your application started at every system startup - example here.

As regards monitoring two things at once, I would suggest you use Python's threading module. Here is a quick example, I create an object subclassed from threading that manages your flow meter by constantly reading it and holding the current value in a variable that the main program can read at any time. You could start another similar one that manages your bar code reader and run them I parallel. I didn't want to do that and confuse you with double the code.

#!/usr/bin/env python3

from threading import Lock
import threading
import time

# Dummy function to read SPI as I don't have anything attached
def readSPI():
    readSPI.static += 1
    return readSPI.static
readSPI.static=0

class FlowMeter(threading.Thread):
    def __init__(self):
        super(FlowMeter, self).__init__()
        # Create a mutex
        self.mutex = Lock()
        self.currentReading = 0

    def run(self):
        # Continuously read flowmeter and safely update self.currentReading
        while True:
            value = readSPI()
            self.mutex.acquire()
            self.currentReading = value
            self.mutex.release()
            time.sleep(0.01)

    def read(self):
        # Main calls this to get latest reading, we just grab it from internal variable
        self.mutex.acquire()
        value = self.currentReading
        self.mutex.release()
        return value

if __name__ == '__main__':

    # Instantiate and start flow meter manager thread
    fmThread = FlowMeter()
    fmThread.start()

    # Now you can do other things in main, but always get access to latest reading
    for i in range(100000):
        fmReading = fmThread.read()
        print(f"Main: i = {i} FlowMeter reading = {fmReading}")
        time.sleep(1)

You could look at using logging to coordinate and unify your debugging and logging messages - see here.

You could look at events to let other threads know something needs doing when something reaches a critical level - example here.