Search code examples
pythonpython-multithreading

Can I create a thread in constructor?


I have a class for a DC-motor. It has attributes that can e.g. change the set rotational speed. In order to control the actual speed I would like to run a thread that guards the acceleration. So if a user sets a new speed the thread will slowly increase the actual speed to match the set speed. My approach:

import time
import threading
import RPi.GPIO as GPIO
    
class motorController:
    #constructors
    def __init__(self,maxPower,powerPin,directionPin,direction=True):
        self.maxPower = abs(maxPower)
        self.power = 0 #initially off
        self.setPower = 0
        
        #set pins motors are connected to
        self.powerPin = powerPin
        self.directionPin = directionPin
        self.direction = direction
        
        #initialize PWM
        GPIO.setmode(GPIO.BCM)
        #initialize powerPin
        GPIO.setup(self.powerPin, GPIO.OUT) # set pin to output
        self.powerPwm = GPIO.PWM(self.powerPin, 100)   # Initialize PWM on pwmPin 100Hz frequency, max is 8kHz, min is 10Hz
        self.powerPwm.start(self.power) # Start PWM with 0% duty cycle
        
        #initialize directionPin
        GPIO.setup(self.directionPin, GPIO.OUT,initial=self.direction) # set pin to output
        
        #initialize controll deamon
        self.__lastCall = time.time()
        self.setPointIsNotReached = threading.Condition() #currently unused
        self.deamonTimeLock = threading.Lock()
        self.controllThread = threading.Thread(target=self.__powerControllerDeamon())
        self.controllThread.setDaemon(True)
        self.controllThread.start()
        
    def setTargetPower(self,setPower):
        setPower = min(setPower,self.maxPower)
        self.setPower = max(setPower,-self.maxPower)
        with self.deamonTimeLock:
            self.__lastDeamonCall = t
    def __setPower(self,power):
        #limit power to maxPower
        self.power = min(abs(power),self.maxPower)
        self.powerPwm.ChangeDutyCycle(self.power)
        #set rotation direction
        if power < 0:
            self.direction = False
        else:
            self.direction = True
        GPIO.output(self.directionPin, self.direction) #set to 3.3V if direction is true, or 0V if direction is false
    def __powerControllerDeamon(self):
        #private method that controls the power of the motor
        t = time.time()
        dt = t-self.__lastCall
        
        if self.power > self.setPower:
            m = -50 #ramp from 100 to 0 in minimal 2 sec
        else:
            m = 20 # ramp from 0 to 100 in minimal 5sec
        
        newPower = self.power + m * dt
        #detect if power is reched through change of sign
        dP = self.setPower - self.power
        dP2 = self.setPower - newPower
        if dP*dP2 <= 0: #change of sign or one is zero
            newPower = self.setPower #set point reached
        else:
            with self.setPointIsNotReached
                self.setPointIsNotReached.notify()#set point NOT reached
        print(newPower)
        self.__setPower(newPower)
        with self.deamonTimeLock:
            self.__lastDeamonCall = t

and

from motorController import motorController as mC
import time

try:
    m1 = mC(100,18,17)
    m1.setTargetPower(100)
    time.sleep(10) #should have powered up by now
except BaseException as e:
    import RPi.GPIO as GPIO
    GPIO.cleanup()
    raise(e)

In this example, the thread function is called once and then never again.

I'm new to threading and python. My questions:

  1. Can I create a thread from a constructor and have it running until the main program ends (that's why i set deamon true)?
  2. If the answer is yes, what's my mistake?

Solution

  • Your __powerControllerDeamon method should contain a loop that runs until it reaches the desired speed or until it is told to stop.

    Since it doesn't contain a loop, it will run once and exit.

    Note that this loop should also contain a small amount of sleep, to prevent it from hogging the CPU.