This post describe how to control a steppermotor with Domoticz.
Supplies needed:
- Easydriver
- A raspberry Pi
- A steppermotor NEMA 17
- Installed with Raspbian
- Installed with Domoticz
- A working circuit for the steppermotor
Control the stepper with domoticz
In the previous post we made it possible to run the steppermotor with a python script. Hower, in the big picture of the swimmingpool automation, i want control the valve for the sunheating. In this case i want to decide how far the valve must be opened. For now i have 4 steps. From 0 steps if the valve is closed to 1000 steps if the valve is complete open.
Step 1: Create a python webserver and motormanagement
I want to have the following endpoints:
GET http://localhost:9090/getWanted -> give me the current wanted step
GET http://localhost:9090/getCurrent -> give me the current step
GET http://localhost:9090/setWanted/<steps> -> set the wanted steps to the given number
From domoticz we can add a dummy switch to control the stepper.
I created 2 pythonscripts, motormanagement.py and stepper-motor.py.
~/motormanagement.py
In this file we manage all the functionality. Here we need to import the GPIO libs. For testing purposes, the motor is not driven bug logs are printed to the screen. The motormanagement is controlled by stepper-motor.py.
~/stepper-motor.py
This is the file with the main thread. We want to have two threads (as deamon). One thread is handling the webserver events and the other thread is controlling the motor. Every second the thread (MyMotor) is watching the current and wanted variables. If they are different, the motor is turned on and the motor spint into the right direction (max 250 steps).
motormanagment.py
import time
class MotorManagement:
GPIO_DIRECTION = 11
GPIO_ENABLE = 16
GPIO_ENGINE = 18
CURRENT_ENGINE_STATE = None
CURRENT_DIRECTION_STATE = None
def __init__(self):
print('INIT MotorManagement, set pins')
def resetMotor(self):
pinTriggert = False
self.turnOnEngine()
print('RESET MOTOR, TURN LEFT UNTIL PIN TRIGGERT')
while not pinTriggert:
self.spinLeft(1)
pinTriggert = True
print('MOTOR IS RESET')
def spinLeft(self, steps):
self.setDirection('L')
self.spinMotor(steps)
return True
def spinRight(self, steps):
self.setDirection('R')
self.spinMotor(steps)
return True
def spinMotor(self, steps):
if (self.CURRENT_ENGINE_STATE != True):
print('CANT SPIN, ENGINE OFF')
return False
print('MOTORSPIN: ' + str(steps))
while (steps > 0):
steps -= 1
time.sleep(0.001)
print('MOTORSPIN DONE')
return True
def setDirection(self, direction):
if (self.CURRENT_DIRECTION_STATE != direction):
self.CURRENT_DIRECTION_STATE = direction
print('MOTORMANGEMENT: DIRECTION:' + str(direction))
return True
def turnOnEngine(self):
if (self.CURRENT_ENGINE_STATE != True):
print('MOTORMANGEMENT: TURNON')
self.CURRENT_ENGINE_STATE = True
return True
def turnOffEngine(self):
if (self.CURRENT_ENGINE_STATE != False):
print('MOTORMANGEMENT: TURNOFF')
self.CURRENT_ENGINE_STATE = False
return True
stepper-motor.py
# threading.Thread
import threading
import time
import bottle
import sys, os, signal
from motormanagement import MotorManagement
class Settings():
currentStep = 0
wantedStep = 0
class ServerT():
deamon = True
def __init__(self,settings):
self.settings = settings
def setWanted(self, steps):
self.settings.wantedStep = int(steps)
return ("OK")
def setCurrent(self, steps):
self.settings.currentStep = int(steps)
return ("OK")
def getWanted(self):
return(str(self.settings.wantedStep))
def getCurrent(self):
return(str(self.settings.currentStep))
class ServerThread(threading.Thread):
deamon = True
def __init__(self, settings):
self.settings = settings
super().__init__()
def kill(self):
try:
os.kill(self, signal.SIGKILL)
except OSError: pass
def run(self):
myapp = ServerT(settings = self.settings)
bottle.route("/getWanted")(myapp.getWanted)
bottle.route("/getCurrent")(myapp.getCurrent)
bottle.route("/setWanted/<steps>")(myapp.setWanted)
bottle.route("/setCurrent/<steps>")(myapp.setCurrent)
bottle.run(host='localhost', port=9090)
class MyMotor(threading.Thread):
deamon = True
STEP_SIZE = 250
def __init__(self, settings):
super().__init__()
self.settings = settings
self.motorManagement = MotorManagement()
self.motorManagement.resetMotor()
self.settings.currentStep = 0
self.kill_received = False
def run(self):
while not self.kill_received:
print('Motor current/wanted:' + str(self.settings.currentStep) + "/" + str(self.settings.wantedStep) )
if (self.settings.wantedStep != self.settings.currentStep):
self.motorManagement.turnOnEngine()
if (self.settings.wantedStep > self.settings.currentStep):
stepsToTake = self.settings.wantedStep - self.settings.currentStep
if (stepsToTake > self.STEP_SIZE):
stepsToTake = self.STEP_SIZE
self.motorManagement.spinRight(stepsToTake)
self.settings.currentStep += stepsToTake
elif (self.settings.wantedStep < self.settings.currentStep):
stepsToTake = self.settings.currentStep - self.settings.wantedStep
if (stepsToTake > self.STEP_SIZE):
stepsToTake = self.STEP_SIZE
self.motorManagement.spinLeft(stepsToTake)
self.settings.currentStep -= stepsToTake
if (self.settings.currentStep == self.settings.wantedStep):
self.motorManagement.turnOffEngine()
time.sleep(1)
print('Main thread died, turn engine off')
self.motorManagement.turnOffEngine()
def has_live_threads(threads):
return True in [t.isAlive() for t in threads]
if __name__ == '__main__':
threads = []
settings = Settings()
t = ServerThread(settings)
t.start()
threads.append(t)
t2 = MyMotor(settings)
t2.start()
threads.append(t2)
while has_live_threads(threads):
try:
[titem.join(1) for titem in threads
if titem is not None and titem.isAlive()]
except KeyboardInterrupt:
# Ctrl-C handling and send kill to threads
print ("Sending kill to threads...")
for t in threads:
t.kill_received = True
#todo stop bottle webserver.. for now press 2times ctr+c
break
print ("Exited")
sys.exit()
You can start the stepper-motor with python3.
python ~/stepper-motor.py
Navigate to http://localhost:9090/setWanted/1000 and see what happend 🙂
NOTE: If you get an error like this
Traceback (most recent call last):
File "./stepper-motor.pi", line 4, in
import bottle
ImportError: No module named bottle
You need to install bottle, but to install bottle you need to have pip
sudo apt-get install python3-pip
After that, install bottle
pip3 install bottle
Step 2: Add virtual controller for the steppermotor
a) Add dummy hardware: Setup -> Hardware -> Type = Dummy
b) Add a dummy Virtual switch by clicking on the button in the list of hardware.
In the popup enter the following parameters:
Name: Steppermotor
Type: Switch
Press OK to add the switch
Step 3: Configure the steppermotor switch
In Domoticz goto Switches en edit the Steppermotor switch.
We give the new switch the following settings:
Switch type: Selector
Selector Levels: 0%, 25%, 50%, 75%, 100%
Selector Actions:
0 = http://localhost:9090/setWanted/0
10 = http://localhost:9090/setWanted/250
20 = http://localhost:9090/setWanted/500
30 = http://localhost:9090/setWanted/750
40 = http://localhost:9090/setWanted/1000
It should look like this.
Ofcourse you can play with the percentages and steps.
Step 4: Monitoring
We also want to now the current step of the motor. For this we created an endpoint http://localhost:9090/getCurrent. We can put the result of that call into a measure utility.
In domoticz, on the hardware page (setup -> hardware), create a virtual sensor of the type Counter. Give it the name: ‘Steppermotor current’.
We now create a dVents script to retrieve the current value from the Python script.
Goto: Setup -> More Options -> Events
Add a new script of the type: ‘dzVents’ with the following script.
return {
on = {
timer = {
'every minute' -- just an example to trigger the request
},
httpResponses = {
'trigger' -- must match with the callback passed to the openURL command
}
},
execute = function(domoticz, item)
if (item.isTimer) then
domoticz.openURL({
url = 'http://localhost:9090/getCurrent',
method = 'GET',
callback = 'trigger', -- see httpResponses above.
})
end
if (item.isHTTPResponse) then
if (item.statusCode == 200) then
local someValue = item.data
domoticz.devices('Steppermotor current').updateCounter(someValue)
else
domoticz.log('There was a problem handling the request', domoticz.LOG_ERROR)
domoticz.log(item, domoticz.LOG_ERROR)
end
end
end
}
Here you see a script witch runs every minute, get the currentStep from the stepper motor and put that value into the measuresensor. Make sure you activate the event.
Step 5: Autostart python on boot with screen
You can autostart a script by adding it to the /etc/rc.local file. The disadvantage of this is that it is executes as the root user. Another issue is that the rc.local file needs to be finished, while this python script will run as a deamon. A command to start the stepper-motor server is:
(sleep 10; su pi -c '/home/pi/scripts/stepper-motor.py')&
A more elegant way is with screen. With screen you can dettached and reattach to the screen where the stepper-motor is running in. To install screen type:
sudo apt-get install screen
To start a screen as the pi user, use this command:
su pi -c '/usr/bin/screen -S py -d -m /usr/bin/python3.5 /home/pi/scripts/stepper-motor.py'
Now you can use these commands to attach or detach.
#list all screens
screen -ls
#start a new screen
screen
#dettach the current screen
ctrl + a + d
#reattach to a screen
screen -r
#stop a screen
kill