Thread Safe PDALib solution - perhaps

Moderator: Steve

Thread Safe PDALib solution - perhaps

Postby alanmcdonley » Sun Jul 03, 2016 5:12 pm

In addition to commenting out the duplicate digitalWrite(pin,val) definition, I believe I found a solution to make digitalWrite(pin,val) thread-safe.

I was getting the following exceptions sometimes:
File "/home/pi/RWPi/PDALib.py", line 324, in writeDio
r = spi.xfer([0x40,reg,val&255,val>>8])
IOError: [Errno 9] Bad file descriptor


The fix seems to make digitalWrite() a critical section so that no other thread will use it:

Code: Select all
import threading

dio_lock = threading.Lock()

...

def digitalWrite(pin,val):
  with dio_lock:   # Critical Section - only one thread # ALAN
    if pin < 0:
      return -1
    if pin < 8:
      return pi.write(servopin[pin],val)
    if pin < 24:
      if val:   
        return setDioBit(DIO_OLAT,pin-8)
      else:
        return clearDioBit(DIO_OLAT,pin-8)
    return -1


Without this, I could only go three or four times through my robot loop before an exception. With this change to PDALib.v93.py, I have gone through the loop over 60 times, before getting an exception....

This time in readDIo().

PDALib.digitalWrite(Motors.M1DirB,0) #set to off/coast
File "/home/pi/RWPi/PDALib.py", line 193, in digitalWrite
return clearDioBit(DIO_OLAT,pin-8)
File "/home/pi/RWPi/PDALib.py", line 356, in clearDioBit
t = readDio(reg)
File "/home/pi/RWPi/PDALib.py", line 319, in readDio
r = spi.xfer([0x41,reg,0,0])
IOError: [Errno 9] Bad file descriptor



So the solution looks to be to add the following at the start of PDALib.93.py:
Code: Select all
...
import threading

...
dio_lock = threading.Lock()


and add the line:
Code: Select all
  with dio_lock:  # critical section #ALAN


to:

pinMode()
digitalRead()
digitalWrite()
analogRead()
analogWrite()
servoWrite()

I have not had an exception this evening. This was a shotgun approach rather than knowlege, so perhaps I over did it. I was getting tired of typing the: ps -ef | grep python, kill -9 xxxx

I would have to write a test program that launched threads to bang each PDALib public function repeatedly to be sure PDALib is now thread-safe, but not right now.
alanmcdonley
 
Posts: 91
Joined: Thu Jul 23, 2015 10:50 am
Location: Boynton Beach, Florida

Re: Thread Safe PDALib solution - perhaps

Postby mikronauts » Mon Jul 04, 2016 11:36 am

Good work!

I think a better way is to make readDio() and writeDio() thread safe (names from memory, just got back from a vacation) as that way all digital I/O will be safe.

Making readAnalog() behave the same way will also cover analog inputs :)
mikronauts
 
Posts: 119
Joined: Tue Sep 16, 2014 6:58 pm

Thread Safe PDALib - and test program

Postby alanmcdonley » Tue Jul 05, 2016 9:18 pm

I wrote a test program to pound the PDALib reasonably hard, and ran my ultrasonic ranging test in a separate ssh session at the same time.

Here is the final thread-safe PDALib.py (v93plus everywhere I added is marked with # ALAN:
Code: Select all
#!/usr/bin/python
#
# PDALib.py   Python API for Pi Droid Alpha educational robot controller
#
# Copyright 2015 William Henning
# http://Mikronauts.com
#
# PDALib provides an implementation of the RoboPi advanced robot controller for
# the Pi Droid Alpha educational robot controller.
#
# Please see the Pi Droid Alpha User Manual for documentation
#
# Aug.25/2015: v0.90 release (readDistance() not reliable)
# Sep.21/2015: v0.91 fixed Pi GPIO INPUT/OUTPUT constants, byte endian issue
# Feb.26/2015  v0.92 added readDistance2(trig,echo) support for HC-SR04
# see http://www.mikronauts.com/raspberry-pi/gpio-experiments/raspberry-pi-and-hc-sr04-distance-sensor-interfacing-with-c-and-python/
# Apr.12/2016: v0.93 fixed spi open/close mismatches
#

# Jul.05/2016: v0.93plus  # ALAN added thread-safe critical section locking
#                         # ALAN removed duplicate digitalWrite()


import time
import spidev
import pigpio
import threading     # ALAN  for threading.Lock() critical sectioning

# pin 0-7  = Pi GPIO's
# pin 8-23 = Dio bits

INPUT  = 0
OUTPUT = 1
PWM    = 2
SERVO  = 3
PING   = 4
TRIG   = 5
ECHO   = 6

mode     = [0,0,0,0,0,0,0,0]
diomode  = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
servopin = [4, 17, 18, 27, 22, 23, 24, 25]
servoval = [0, 0, 0, 0, 0, 0, 0, 0]
req12    = [[6,0,0], [6,64,0],[6,128,0],[6,192,0],[7,0,0], [7,64,0],[7,128,0],[7,192,0]]
req10    = [[1,128,0], [1,144,0],[1,160,0],[1,176,0],[1,192,0], [1,208,0],[1,224,0],[1,240,0]]

DIO_DIR  = 0x00 # I/O direction register, 0=output, 1=input
DIO_POL  = 0x02 # polarity invert config, if 1 inverted logic for pin
DIO_GPPU = 0x0C # pullup configuration register, 1 enables pullup for pin
DIO_GPIO = 0x12 # pin values
DIO_OLAT = 0x14 # output latch

dio_lock = threading.Lock()   # with dio_lock: to make any method thread safe # ALAN

pi       = pigpio.pi()
spi      = spidev.SpiDev()

spi.open(0,0)              # open(port,CS) port=0, CS=0|1
spi.max_speed_hz=1953000   # default speed undervalued divided voltages
spi.xfer([0x40,0xff,0xff]) # sets all pins as inputs
spi.close()

#
# Initialize library - for RoboPi compatibility, ignores arguments
#

def RoboPiInit(device, bps):
  return 0

#
# Shut down library
#

def RoboPiExit():
  pi.stop()

#
# return the current mode of digital pins (0..23)
#
# 0..7  are Raspberry Pi pins on the Servo header (PWM, SERVO, INPUT, OUTPUT, PING)
# 8..23 are DIO A0-A7 and B0-B7 respectively (INPUT, OUTPUT)
#

def readMode(pin):
  if pin < 0:
    return -1
  if pin < 8:
    return mode[pin]
  if pin < 24:
    return diomode[pin-8]
  return -1

#
# set the current mode of digital pins (0..23)
#
# 0..7  are Raspberry Pi pins on the Servo header (PWM, SERVO, INPUT, OUTPUT, PING)
# 8..23 are DIO A0-A7 and B0-B7 respectively (INPUT, OUTPUT)
#

def _echo1(gpio, level, tick):
   global _high
   _high = tick
     
def _echo0(gpio, level, tick):
   global _done, _high, _time
   _time = tick - _high
   _done = True
               
def pinMode(pin,newmode):
  global my_echo0, my_echo1
  if pin < 0:
    return -1
  if pin < 8: # only accept valid modes
    if mode[pin] == PWM: # stop PWM if setting for input or output
       pi.set_PWM_dutycycle(servopin[pin],0)
    if mode[pin] == TRIG:
       mode[pin] = newmode
    if mode[pin] == ECHO:
       mode[pin] = newmode
       my_echo1.cancel()
       my_echo0.cancel()
    if newmode == INPUT:
       mode[pin] = newmode
       pi.set_mode(servopin[pin], pigpio.INPUT)
    if newmode == OUTPUT:
       mode[pin] = newmode
       pi.set_mode(servopin[pin], pigpio.OUTPUT)
    if newmode == PWM:
       mode[pin] = newmode
       pi.set_PWM_frequency(servopin[pin],490)
    if newmode == PING:
       mode[pin] = newmode
    if newmode == TRIG:
       mode[pin] = newmode
    if newmode == ECHO:
       mode[pin] = newmode
       my_echo1 = pi.callback(22, pigpio.RISING_EDGE,  _echo1)
       my_echo0 = pi.callback(22, pigpio.FALLING_EDGE, _echo0)
    return 0
  if pin < 24: # only accept valid modes
    if newmode == INPUT:
       diomode[pin-8] = newmode
       setDioBit(0,pin-8)
       return 0
    if newmode == OUTPUT:
       diomode[pin-8] = newmode
       clearDioBit(0,pin-8)
       return 0
  return -1
 
#
# Read current value of digital pin (0..23)
#
# 0..7  are Raspberry Pi pins on the Servo header (PWM, SERVO, INPUT, OUTPUT, PING)
# 8..23 are DIO A0-A7 and B0-B7 respectively (INPUT, OUTPUT)
#

def digitalRead(pin):
  if pin < 0:
    return -1
  if pin < 8:
    return pi.read(servopin[pin])
  if pin < 24:
    return getDioBit(DIO_GPIO,pin-8)
  return -1
 
#
# Set the value of digital pins (0..23)
#
# 0..7  are Raspberry Pi pins on the Servo header (PWM, SERVO, INPUT, OUTPUT, PING)
# 8..23 are DIO A0-A7 and B0-B7 respectively (INPUT, OUTPUT)
#

def digitalWrite(pin,val):
  if pin < 0:
    return -1
  if pin < 8:
    return pi.write(servopin[pin],val)
  if pin < 24:
    if val:   
      return setDioBit(DIO_OLAT,pin-8)
    else:
      return clearDioBit(DIO_OLAT,pin-8)
  return -1
 
def digitalWrite(pin,val):
  if pin < 0:
    return -1
  if pin < 8:
    return pi.write(servopin[pin],val)
  if pin < 24:
    if val:   
      return setDioBit(DIO_OLAT,pin-8)
    else:
      return clearDioBit(DIO_OLAT,pin-8)
  return -1
 
#
# read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7)
#

def analogRead(adcnum):
 with dio_lock:
  if adcnum < 0:
    return -1   
  if adcnum > 7:
    return -1   
  spi.open(0,1)
  r = spi.xfer2(req10[adcnum])
  spi.close()
  adcout = ((r[1]&3) << 8) + r[2]
  return adcout
   
#
# read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7)
#
# shift left two bits to present 10 bit result as 12 bit result
# for RoboPi compatibility
#

def analogReadRaw(adcnum):
  return analogRead(adcnum)<<2
 
#
# Set motor speed 0..255 for specified pin (mode must be PWM)
#
# default to 490Hz like Arduino
#

def analogWrite(pin,val):
  if pin < 0:
    return -1
  if pin > 7:
    return -1
  pi.set_PWM_dutycycle(servopin[pin],val)
  return 0
 
#
# Get servo position last written to specified pin
#
 
def servoRead(pin):
  if pin < 0:
    return -1
  if pin > 7:
    return -1
  return servoval[pin]

#
# Set servo position for specified pin (mode must be SERVO)
#
 
def servoWrite(pin,val):
  if pin < 0:
    return -1
  if pin > 7:
    return -1
  servoval[pin] = val
  pi.set_servo_pulsewidth(servopin[pin], val)
  return 0
 
#
# readDistance() is not working properly yet - look for it in future releases
#

def readDistance(pin):
  if pin < 0:
    return -1
  if pin > 7:
    return -1
  if mode[pin] == PING:
    pi.set_mode(servopin[pin], pi.OUTPUT)
    pi.gpio_trigger(servopin[pin], 50, 1) # 50us, high pulse
    pi.set_mode(servopin[pin], pi.INPUT)
    ok = pi.wait_for_edge(servopin[pin], pigpio.RISING_EDGE, 0.01)
    if ok:
      t0 = pi.get_current_tick()
      ok = pi.wait_for_edge(servopin[pin], pigpio.FALLING_EDGE, 0.1)
      if ok:
        t1 = pi.get_current_tick()
        return (t1 - t0) # returns microseconds     
  return -1
 
#
# readDistance2() for HC-SR04 only
#
# for an explanation, see my article:
#
# http://www.mikronauts.com/raspberry-pi/gpio-experiments/raspberry-pi-and-hc-sr04-distance-sensor-interfacing-with-c-and-python/
#

def readDistance2(_trig, _echo):
   global pi, _done, _time
   _done = False
   pi.set_mode(servopin[_trig], pigpio.OUTPUT)
   pi.gpio_trigger(servopin[_trig],50,1)
   pi.set_mode(servopin[_echo], pigpio.INPUT)
   time.sleep(0.0001)
   tim = 0
   while not _done:
      time.sleep(0.001)
      tim = tim+1
      if tim > 50:
         return 0
   return _time / 58.068 # 29.034 # return as mm


#
# The following functions are not meant for public use, and may not be supported
# in the future. If you use them, do not be surprised if future versions of the
# library do not make them available.
#
# The following functions are NOT available on RoboPi
#

#
# Read specified MCP23S17 register (16 bits)
#

def readDio(reg):
 with dio_lock:
  spi.open(0,0) 
  r = spi.xfer([0x41,reg,0,0])
  spi.close()
  return (r[3]<<8)+r[2]
 
#
# Write to specified MCP23S17 register (16 bits)
#

def writeDio(reg,val):
  with dio_lock:
    spi.open(0,0)
    r = spi.xfer([0x40,reg,val&255,val>>8])
    spi.close()
    return r

#
# Set specific bit in specified register
#
   
def setDioBit(reg,bit):
  if bit < 0:
    return -1
  if bit > 15:
    return -1
  t = readDio(reg)
  t = t | (1<<bit)
  writeDio(reg,t)
  return 0

#
# Clear specific bit in specified register
#
   
def clearDioBit(reg,bit):
  if bit < 0:
    return -1
  if bit > 15:
    return -1
  t = readDio(reg)
  t = t & ~(1<<bit)
  writeDio(reg,t) 
  return 0

#
# Flip value of specific bit in specified register
#
   
def flipDioBit(reg,bit):
  if bit < 0:
    return -1
  if bit > 15:
    return -1
  t = readDio(reg)
  t = t ^ (1<<bit)
  writeDio(reg,t)
  return 0

#
# Get value of specific bit in specified register
#
   
def getDioBit(reg,bit):
  if bit < 0:
    return -1
  if bit > 15:
    return -1
  if readDio(reg) & (1<<bit):
    return 1
  return 0 

#
# Print out contents of all the MCP23S17 registers (16 bit)
#
   
def dumpDio():
    print "reg val"
    print "--------"
    for r in range(0,16):
      print "", format(r+r, '02X'), format(readDio(r+r),'04X')





And this is the test banger: PDALibThreadTest.py

Code: Select all
#!/usr/bin/python
#
# PDALibThreadTest.py   PDALib THREADING TEST
#

import PDALib
import myPyLib
import time
import sys
import threading
import signal
from datetime import datetime

# DIO 8..11 will be used as input testing

  # DIO 12 (A4)   Motor 1 Dir A (0=coast 1=F/Brake)
  # DIO 13 (A5)   Motor 1 Dir B (0=coast 1=R/Brake)

  # DIO 14 (A6)   Motor 2 Dir A (0=coast 1=F/Brake)
  # DIO 15 (A7)   Motor 2 Dir B (0=coast 1=R/Brake)

M1DirA = 12
M1DirB = 13
M2DirA = 14
M2DirB = 15

# SRV 4 (pin 3 of 0..7)  is unassigned (on my bot), use for servo read/write
SRVpin = 3




# ### PIN MODE INPUT CLASS ##################

class PinModeInput():

  # CLASS VARS (Avail to all instances)
  # Access as PinModeInput.class_var_name

  pollThreadHandle=None   # the SINGLE read sensor thread for the PinModeInput class   
  tSleep=0.1            # time for read_sensor thread to sleep
 

  # end of class vars definition

  def __init__(self,readingsPerSec=10):
    # SINGLETON TEST
    if (PinModeInput.pollThreadHandle!=None):
        print "Second PinModeInput Class Object, not starting pollingThread"
        return None

    # INITIALIZE CLASS INSTANCE

    # START A THREAD
    # threading target must be an instance
    print "PinModeInput worker thread readingsPerSec:",readingsPerSec
    PinModeInput.tSleep=1.0/readingsPerSec    #compute desired sleep
    PinModeInput.pollThreadHandle = threading.Thread( target=self.pollPinModeInput,
                                               args=(PinModeInput.tSleep,))
    PinModeInput.pollThreadHandle.start()
    print "PinModeInput worker thread told to start"
  #end init()

  # PinModeInput THREAD WORKER METHOD TO READ PinModeInput
  def pollPinModeInput(self,tSleep=0.1):     
    print "pollPinModeInput started with %f" % tSleep
    t = threading.currentThread()   # get handle to self (pollingPinModeInput thread)
    while getattr(t, "do_run", True):  # check the do_run thread attribute
      self.do()
      time.sleep(tSleep)
    print("do_run went false. Stopping pollPinModeInput thread")

   
  def do(self):  #set PinMode to Input - can be used as poll or directly
      for pin in range(8,11+1):
          PDALib.pinMode(pin,PDALib.INPUT)
      print "pinMode(8..11) set to input "
       
       
      return   # PinModeInput.do


  def cancel(self):
     print "PinModeInput.cancel() called"
     self.pollThreadHandle.do_run = False
     print "Waiting for PinModeInput.workerThread to quit"
     self.pollThreadHandle.join()

# ### END OF PIN MODE INPUT


# ### PIN MODE OUTPUT CLASS #########################

class PinModeOutput():

  # CLASS VARS (Avail to all instances)
  # Access as PinModeOutput.class_var_name


  pollThreadHandle=None   # the SINGLE read sensor thread for the PinModeOutput class   
  tSleep=0.1            # time for read_sensor thread to sleep
 

  # end of class vars definition

  def __init__(self,readingsPerSec=10):
    # SINGLETON TEST
    if (PinModeOutput.pollThreadHandle!=None):
        print "Second PinModeOutput Class Object, not starting pollingThread"
        return None

    # INITIALIZE CLASS INSTANCE

    # START A THREAD
    # threading target must be an instance
    print "PinModeOutput worker thread readingsPerSec:",readingsPerSec
    PinModeOutput.tSleep=1.0/readingsPerSec    #compute desired sleep
    PinModeOutput.pollThreadHandle = threading.Thread( target=self.pollPinModeOutput,
                                               args=(PinModeOutput.tSleep,))
    PinModeOutput.pollThreadHandle.start()
    print "PinModeOutput worker thread told to start"
  #end init()

  # PinModeOutput THREAD WORKER METHOD TO READ PinModeOutput
  def pollPinModeOutput(self,tSleep=0.1):     
    print "pollPinModeOutput started with %f" % tSleep
    t = threading.currentThread()   # get handle to self (pollingPinModeOutput thread)
    while getattr(t, "do_run", True):  # check the do_run thread attribute
      self.do()
      time.sleep(tSleep)
    print("do_run went false. Stopping pollPinModeOutput thread")

   
  def do(self):  #set PinMode to Input - can be used as poll or directly
      PDALib.pinMode(M1DirA,PDALib.OUTPUT)  #init motor1 dirA/Fwd    enable
      PDALib.pinMode(M1DirB,PDALib.OUTPUT)  #init motor1 dirB/Bkwd  enable
      PDALib.pinMode(M2DirA,PDALib.OUTPUT)  #init motor2 dirA/Fwd    enable
      PDALib.pinMode(M2DirB,PDALib.OUTPUT)  #init motor2 dirB/Bkwd  enable
      print "pinMode(12..15) set to OUTPUT "
       
       
      return   # PinModeOutput.do


  def cancel(self):
     print "PinModeOutput.cancel() called"
     self.pollThreadHandle.do_run = False
     print "Waiting for PinModeOutput.workerThread to quit"
     self.pollThreadHandle.join()

# ### END OF PIN MODE OUTPUT

# ### MOTOR DIRECTION CLASS ############################

class MotorDirs():

  # CLASS VARS (Avail to all instances)
  # Access as MotorDirs.class_var_name
  motorDir = 0


  pollThreadHandle=None   # the SINGLE read sensor thread for the MotorDirs class   
  tSleep=0.1            # time for read_sensor thread to sleep
 

  # end of class vars definition

  def __init__(self,readingsPerSec=10):
    # SINGLETON TEST
    if (MotorDirs.pollThreadHandle!=None):
        print "Second MotorDirs Class Object, not starting pollingThread"
        return None
    PDALib.pinMode(M1DirA,PDALib.OUTPUT)  #init motor1 dirA/Fwd    enable
    PDALib.pinMode(M1DirB,PDALib.OUTPUT)  #init motor1 dirB/Bkwd  enable
    PDALib.pinMode(M2DirA,PDALib.OUTPUT)  #init motor2 dirA/Fwd    enable
    PDALib.pinMode(M2DirB,PDALib.OUTPUT)  #init motor2 dirB/Bkwd  enable


    # INITIALIZE CLASS INSTANCE

    # START A THREAD
    # threading target must be an instance
    print "MotorDirs worker thread readingsPerSec:",readingsPerSec
    MotorDirs.tSleep=1.0/readingsPerSec    #compute desired sleep
    MotorDirs.pollThreadHandle = threading.Thread( target=self.pollMotorDirs,
                                               args=(MotorDirs.tSleep,))
    MotorDirs.pollThreadHandle.start()
    print "MotorDirs worker thread told to start"
  #end init()

  # MotorDirs THREAD WORKER METHOD TO READ MotorDirs
  def pollMotorDirs(self,tSleep=0.1):     
    print "pollMotorDirs started with %f" % tSleep
    t = threading.currentThread()   # get handle to self (pollingMotorDirs thread)
    while getattr(t, "do_run", True):  # check the do_run thread attribute
      self.do()
      time.sleep(tSleep)
    print("do_run went false. Stopping pollMotorDirs thread")

   
  def do(self):  #set motor dirs to all 0 - can be used as poll or directly

      # toggle all direction pins
      self.motorDir = (self.motorDir + 1) % 2
      PDALib.digitalWrite(M1DirA,self.motorDir)  #set
      PDALib.digitalWrite(M1DirB,self.motorDir)  #set
      PDALib.digitalWrite(M2DirA,self.motorDir)  #set
      PDALib.digitalWrite(M2DirB,self.motorDir)  #set
      print "digitalWrite MotorDirs to %d " % self.motorDir
     
       
       
      return   # MotorDirs.do


  def cancel(self):
     print "MotorDirs.cancel() called"
     self.pollThreadHandle.do_run = False
     print "Waiting for MotorDirs.workerThread to quit"
     self.pollThreadHandle.join()

# ### END OF MOTOR DIRS

     


# ### READ MODE CLASS #######################################

class ReadMode():

  # CLASS VARS (Avail to all instances)
  # Access as ReadMode.class_var_name

  pollThreadHandle=None   # the SINGLE read sensor thread for the ReadMode class   
  tSleep=0.1            # time for read_sensor thread to sleep
 

  # end of class vars definition

  def __init__(self,readingsPerSec=10):
    # SINGLETON TEST
    if (ReadMode.pollThreadHandle!=None):
        print "Second ReadMode Class Object, not starting pollingThread"
        return None

    # INITIALIZE CLASS INSTANCE

    # START A THREAD
    # threading target must be an instance
    print "ReadMode worker thread readingsPerSec:",readingsPerSec
    ReadMode.tSleep=1.0/readingsPerSec    #compute desired sleep
    ReadMode.pollThreadHandle = threading.Thread( target=self.pollReadMode,
                                               args=(ReadMode.tSleep,))
    ReadMode.pollThreadHandle.start()
    print "ReadMode worker thread told to start"
  #end init()

  # ReadMode THREAD WORKER METHOD TO READ ReadMode
  def pollReadMode(self,tSleep=0.1):     
    print "pollReadMode started with %f" % tSleep
    t = threading.currentThread()   # get handle to self (pollingReadMode thread)
    while getattr(t, "do_run", True):  # check the do_run thread attribute
      self.read()
      time.sleep(tSleep)
    print("do_run went false. Stopping pollReadMode thread")

   
  def read(self):  #READ THE ReadMode - can be used as poll or directly
      pinmode=[]
      for pin in range(0,23+1):
          pinmode.append( PDALib.readMode(pin) )
      print "readMode(0..23): ",pinmode
      return   # ReadMode.state


  def cancel(self):
     print "ReadMode.cancel() called"
     self.pollThreadHandle.do_run = False
     print "Waiting for ReadMode.workerThread to quit"
     self.pollThreadHandle.join()

# ### END OF READ MODE

# ### READ ANALOG CLASS #################################

class ReadAnalog():

  # CLASS VARS (Avail to all instances)
  # Access as ReadAnalog.class_var_name

  pollThreadHandle=None   # the SINGLE read sensor thread for the ReadAnalog class   
  tSleep=0.1            # time for read_sensor thread to sleep
 

  # end of class vars definition

  def __init__(self,readingsPerSec=10):
    # SINGLETON TEST
    if (ReadAnalog.pollThreadHandle!=None):
        print "Second ReadAnalog Class Object, not starting pollingThread"
        return None

    # INITIALIZE CLASS INSTANCE

    # START A THREAD
    # threading target must be an instance
    print "ReadAnalog worker thread readingsPerSec:",readingsPerSec
    ReadAnalog.tSleep=1.0/readingsPerSec    #compute desired sleep
    ReadAnalog.pollThreadHandle = threading.Thread( target=self.pollReadAnalog,
                                               args=(ReadAnalog.tSleep,))
    ReadAnalog.pollThreadHandle.start()
    print "ReadAnalog worker thread told to start"
  #end init()

  # ReadAnalog THREAD WORKER METHOD TO READ ReadAnalog
  def pollReadAnalog(self,tSleep=0.1):     
    print "pollReadAnalog started with %f" % tSleep
    t = threading.currentThread()   # get handle to self (pollingReadAnalog thread)
    while getattr(t, "do_run", True):  # check the do_run thread attribute
      self.read()
      time.sleep(tSleep)
    print("do_run went false. Stopping pollReadAnalog thread")

   
  def read(self):  #READ THE ReadAnalog - can be used as poll or directly
      valueDAC=[]
      for pin in range(0,7+1):
          valueDAC.append( PDALib.analogRead(pin) )
      print "analogRead(0..7): ",valueDAC
      return   # ReadAnalog.read

  def cancel(self):
     print "ReadAnalog.cancel() called"
     self.pollThreadHandle.do_run = False
     print "Waiting for ReadAnalog.workerThread to quit"
     self.pollThreadHandle.join()

# ### END OF READ ANALOG

# ### SERVO READ CLASS ####################################

class ServoRead():

  # CLASS VARS (Avail to all instances)
  # Access as ServoRead.class_var_name

  pollThreadHandle=None   # the SINGLE read sensor thread for the ServoRead class   
  tSleep=0.1            # time for read_sensor thread to sleep
 

  # end of class vars definition

  def __init__(self,readingsPerSec=10):
    # SINGLETON TEST
    if (ServoRead.pollThreadHandle!=None):
        print "Second ServoRead Class Object, not starting pollingThread"
        return None
    PDALib.pinMode(SRVpin,PDALib.PWM)  # init free pin to PWM

    # INITIALIZE CLASS INSTANCE

    # START A THREAD
    # threading target must be an instance
    print "ServoRead worker thread readingsPerSec:",readingsPerSec
    ServoRead.tSleep=1.0/readingsPerSec    #compute desired sleep
    ServoRead.pollThreadHandle = threading.Thread( target=self.pollServoRead,
                                               args=(ServoRead.tSleep,))
    ServoRead.pollThreadHandle.start()
    print "ServoRead worker thread told to start"
  #end init()

  # ServoRead THREAD WORKER METHOD TO READ ServoRead
  def pollServoRead(self,tSleep=0.1):     
    print "pollServoRead started with %f" % tSleep
    t = threading.currentThread()   # get handle to self (pollingServoRead thread)
    while getattr(t, "do_run", True):  # check the do_run thread attribute
      self.read()
      time.sleep(tSleep)
    print("do_run went false. Stopping pollServoRead thread")

   
  def read(self):  #READ THE ServoRead - can be used as poll or directly
       srvValue=PDALib.servoRead(SRVpin)
       print "SERVOREAD: servoRead(%d) = %d uS" % (SRVpin, srvValue)
       return   # ServoRead.state


  def cancel(self):
     print "ServoRead.cancel() called"
     self.pollThreadHandle.do_run = False
     print "Waiting for ServoRead.workerThread to quit"
     self.pollThreadHandle.join()

# END OF ServoRead CLASS


# ### SERVO WRITE CLASS #########################################

class ServoWrite():

  # CLASS VARS (Avail to all instances)
  # Access as ServoWrite.class_var_name
  lastServoSetting = 1250  # start at center of range

  pollThreadHandle=None   # the SINGLE read sensor thread for the ServoWrite class   
  tSleep=0.1            # time for read_sensor thread to sleep
 

  # end of class vars definition

  def __init__(self,readingsPerSec=10):
    # SINGLETON TEST
    if (ServoWrite.pollThreadHandle!=None):
        print "Second ServoWrite Class Object, not starting pollingThread"
        return None

    # INITIALIZE CLASS INSTANCE

    # START A THREAD
    # threading target must be an instance
    print "ServoWrite worker thread readingsPerSec:",readingsPerSec
    ServoWrite.tSleep=1.0/readingsPerSec    #compute desired sleep
    ServoWrite.pollThreadHandle = threading.Thread( target=self.pollServoWrite,
                                               args=(ServoWrite.tSleep,))
    ServoWrite.pollThreadHandle.start()
    print "ServoWrite worker thread told to start"
  #end init()

  # ServoWrite THREAD WORKER METHOD TO READ ServoWrite
  def pollServoWrite(self,tSleep=0.1):     
    print "pollServoWrite started with %f" % tSleep
    t = threading.currentThread()   # get handle to self (pollingServoWrite thread)
    while getattr(t, "do_run", True):  # check the do_run thread attribute
      self.do()
      time.sleep(tSleep)
    print("do_run went false. Stopping pollServoWrite thread")

   
  def do(self):  #do THE ServoWrite - can be used as poll or directly
       self.lastServoSetting = (self.lastServoSetting + 1) % 2501
       PDALib.servoWrite(SRVpin,self.lastServoSetting) # set to new position
       return   # ServoWrite.do


  def cancel(self):
     print "ServoWrite.cancel() called"
     self.pollThreadHandle.do_run = False
     print "Waiting for ServoWrite.workerThread to quit"
     self.pollThreadHandle.join()

# END OF ServoWrite CLASS


# ##### PDALib MULTI-THREAD TEST #########################################


# ######### CNTL-C ##############################
# Callback and setup to catch control-C and quit program

_funcToRun=None

def signal_handler(signal, frame):
  print '\n** Control-C Detected'
  if (_funcToRun != None):
     _funcToRun()
  sys.exit(0)     # raise SystemExit exception

# Setup the callback to catch control-C
def set_cntl_c_handler(toRun=None):
  global _funcToRun
  _funcToRun = toRun
  signal.signal(signal.SIGINT, signal_handler)

# #########



# CANCEL ALL THREADS - Control-C callback function

def cancelAll():
    global readMode,pinModeInput,pinModeOutput,motorDirs,readServo,writeServo

    readMode.cancel()
    pinModeInput.cancel()
    pinModeOutput.cancel()
    motorDirs.cancel()
    readDAC.cancel()
    readServo.cancel()
    writeServo.cancel()
 

###################  PDALib Threading Test Main() ########

def main():
  global readMode,pinModeInput,pinModeOutput,motorDirs,readDAC,readServo,writeServo
  # note: lowercase X is object, uppercase X is class (everywhere in code)

  print "PDALib Thread Test Started.  Ctrl-C to end"
 
  # Create all threads (loops per second)
  readMode=ReadMode(11)  #create an instance which starts the readMode thread
  pinModeInput=PinModeInput(12)
  pinModeOutput=PinModeOutput(13)
  motorDirs=MotorDirs(14)
  readDAC=ReadAnalog(15)
  readServo=ServoRead(16)
  writeServo=ServoWrite(17)

  set_cntl_c_handler(cancelAll)  # Set CNTL-C handler
   
  try:
    while True:
      print "\n MAIN executing", datetime.now
      time.sleep(1)
    #end while
  except SystemExit:
    PDALib.RoboPiExit()
    print "PDALib Thread Test: Bye Bye"   
  except:
    print "Exception Raised"
    cancelAll()
    traceback.print_exc() 

# TO RUN THIS TEST:   ##############################
# chmod a+x PDALibThreadTest.py
# ./PDALibThreadTest.py

if __name__ == "__main__":
    main()


alanmcdonley
 
Posts: 91
Joined: Thu Jul 23, 2015 10:50 am
Location: Boynton Beach, Florida

Re: Thread Safe PDALib solution - perhaps

Postby mikronauts » Wed Jul 06, 2016 8:56 am

Looks GREAT!

Thank you for adding the locks!

The only thing I'd do different is I'd use the same lock for analogRead() ... as it uses the same physical channel with a different chip select.
mikronauts
 
Posts: 119
Joined: Tue Sep 16, 2014 6:58 pm

Re: Thread Safe PDALib and Test Prog

Postby alanmcdonley » Wed Jul 06, 2016 9:26 am

mikronauts wrote:Looks GREAT!

Thank you for adding the locks!

The only thing I'd do different is I'd use the same lock for analogRead() ... as it uses the same physical channel with a different chip select.


Actually I did, but forgot to mark it with # ALAN

Code: Select all
def analogRead(adcnum):
 with dio_lock:
  if adcnum < 0:
alanmcdonley
 
Posts: 91
Joined: Thu Jul 23, 2015 10:50 am
Location: Boynton Beach, Florida


Return to Pi Droid Alpha

Who is online

Users browsing this forum: No registered users and 5 guests

cron