0

I have this code snippet running on a raspberry pi. It basically take pictures of people coming in and out of my room.

import RPi.GPIO as GP
import os
import socket


def opencallback(channel):
    print(GP.input(channel))
    if GP.input(channel):

        global closeEvent
        closeEvent = 1
    else:
        global openEvent
        openEvent = 1


def transmit(message):
    s = socket.create_connection((host, port))
    s.send(message)
    s.close()


def capture(cam, gpiolist, quick):
    GP.output(cam1, gpiolist[0])
    GP.output(cam2, gpiolist[1])
    GP.output(cam3, gpiolist[2])

    if quick:
        cmd = "raspistill -o capture_%d.jpg -t 2" % cam
    else:
        cmd = "raspistill -o capture_%d.jpg" % cam
    os.system(cmd)


# init
GP.setwarnings(False)
GP.setmode(GP.BOARD)

cam1 = 7
cam2 = 11
cam3 = 12
doorIn = 40
ledOut = 38

GP.setup(cam1, GP.OUT)  # camera Mux1
GP.setup(cam2, GP.OUT)  # camera Mux2
GP.setup(cam3, GP.OUT)  # camera Mux3
GP.setup(ledOut, GP.OUT)  # LED OUT GPIO 20
GP.setup(doorIn, GP.IN)  # Door detector in GPIO 21

GP.add_event_detect(doorIn, GP.BOTH, callback=opencallback)

GP.output(ledOut, False)
openEvent = 0
closeEvent = 0
host = '192.168.1.111'
port = 13579

# main
while True:
    if openEvent == 1:
        transmit("2-01")
        capture(2, (False, True, False), True)  # front cam
        transmit("2-03")
        openEvent = 0
    else:
        pass

    if closeEvent == 1:
        transmit("2-02")
        GP.output(ledOut, True)
        capture(3, (False, False, True), False)
        GP.output(ledOut, False)
        transmit("2-04")
        closeEvent = 0
    else:
        pass

Usually, I run it simply by using a standard call through command line and it doesn't load out the system.

However, I recently converted it to a service using systemd/ systemctl because I wanted to load that script in the background when I boot the pi. Now, this script is gulping a whole processor core by itself (as reported by htop). I didn't change anything to the code itself during the transition and when I run it the old way, it is still working okay. Most of the time, it is simply running the while loop doing nothing and waiting for a callback from GPIO and then execute some functions and go back to the while pass behavior.

My question is: what is causing the difference in computing power consumption between the two execution methods? Is there a way to fix this?

martineau
  • 119,623
  • 25
  • 170
  • 301
Simon Marcoux
  • 105
  • 2
  • 13
  • Can you execute the process with `nice` ? – JacobIRR Apr 04 '18 at 19:42
  • Trying using [`time.sleep`](https://stackoverflow.com/questions/510348/how-can-i-make-a-time-delay-in-python). This will pause the thread and let your CPU do other things (including nothing) – JDB Apr 04 '18 at 19:42
  • I could include time.sleep and I will probably miss any events, but it doesn't explain the major difference in computing power required. I'm also not sure what is nice. – Simon Marcoux Apr 04 '18 at 19:46
  • 2
    @Slylandro, I don't see how this would *ever* be CPU-efficient, no matter how you run it. Being in a tight loop that does continuous polling is *always* an expensive operation. I almost wonder if the version you tested with from the command line did something like logging to console (and, by association, causing the process to block until that output could be written) that effectively enforced a delay. – Charles Duffy Apr 04 '18 at 19:51
  • Linux kernel has the ability to grant priority to certain processes. This is called the "niceness" of a process. It is possible to set the niceness of systemd resources, and it may be running with a different niceness as a systemd resource than as a cli application. https://github.com/mikebrady/shairport-sync/issues/205 https://en.wikipedia.org/wiki/Nice_(Unix) – Him Apr 04 '18 at 19:53
  • @Scott, that's true, but a `nice`'d process will still use all available CPU, so it'll still be "gulping a whole processor core", as the OP is complaining about. – Charles Duffy Apr 04 '18 at 19:53
  • *This is just one possibility, that I think @JacobIRR was pointing out – Him Apr 04 '18 at 19:54
  • @CharlesDuffy, I had assumed that by "gulping the whole cpu", the problem was that other processes were not running properly, because this program was using up all the resources and not sharing. Changing the niceness would help with that thing. – Him Apr 04 '18 at 19:55
  • @Charles I wholly agree with that. Usually, when doing embedded programming (e.g on a arduino), there is a way to ''wake up'' the uC using an external IO. It will also react correctly to those type of loops. To answer your question, my GPIO pins are connected to a camera multiplexer and to a magnetic door detector. It needs not to react that fast. I tested the delay it indeed helps out. Regarding your second part, you're probably right about the fact that running it natively cause a natural delay that systemd doesn't execute. – Simon Marcoux Apr 04 '18 at 19:58
  • @Scott actually, it makes the RPi slightly overheat since it is inside an enclosed space. – Simon Marcoux Apr 04 '18 at 20:00
  • If both `openEvent` and `closeEvent` are not equal to 1, your code doesn't run any edge-detection/blocking code at all, so it's expected for it to be a tight loop that does nothing but eat CPU. – Charles Duffy Apr 04 '18 at 20:01
  • ...if you want to only run events when triggered by a callback, *do that*, and take out the endless loop. – Charles Duffy Apr 04 '18 at 20:02
  • @Charles openEvent and closeEvent are set to 1 during a callback of the GPIO. This is usually how it is done when doing embedded programming (you can clearly guess that my experience and reflex are based on embedded programming and not on system programming). Now the question is therefore, if I only want to run it via callback, I still need the script to continue running in some capacity until the callback occurs. The easy way out is via time.sleep. Odds are that you will not miss an event. Otherwise, I guess it requires a change in the code that is outside of my current knowledge. – Simon Marcoux Apr 04 '18 at 20:09
  • At the very least, the answer that you provided is quite elegant for my needs and will be in my knowledge base for the time being. Most of the embedded code that I wrote in the past (be it for work, or undergrad studies) didn't really tax the system or anything. Therefore, I might have gone lazy in my way of thinking simply because I never hit any snag. I don't have you're level of experience in embedded dev mostly because I'm still young in my career. Thank you for your time. – Simon Marcoux Apr 04 '18 at 20:22

2 Answers2

1

The edge-detection/callback code is efficient, only being invoked when an event takes place. However, the top-level while True: loop is extremely inefficient.

Consider the following modifications:

try:
    import Queue as queue # Python 2.x
except ImportError:
    import queue          # Python 3.x

eventQueue = queue.Queue()

def opencallback(channel):
    eventQueue.put(GP.input(channel))

# ...your existing setup code here, specifically including:
GP.add_event_detect(doorIn, GP.BOTH, callback=opencallback)

while True:
    event = eventQueue.get()
    if event:
        transmit("2-01")
        capture(2, (False, True, False), True)  # front cam
        transmit("2-03")
    else:
        transmit("2-02")
        GP.output(ledOut, True)
        capture(3, (False, False, True), False)
        GP.output(ledOut, False)
        transmit("2-04")

Note that this is not related to systemd -- you would have the same CPU usage issues with any other invocation method as well.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Ooh, I really like that! I completely forgot Python had a readily available Queue library. However, does this give the CPU a ready opportunity to switch away the way that time.sleep does? – Ketzak Apr 04 '18 at 20:12
  • 1
    @Ketzak, yes, it does. – Charles Duffy Apr 04 '18 at 20:13
  • All my other scripts that I wrote didn't have that problem since they were actually polling for a socket connection to happens in a similar way than this one. I took the time to implement it correctly tonight and, yes it solves the problem. Thanks again! – Simon Marcoux Apr 05 '18 at 04:05
0

Why not have the callbacks trigger the actions for open and close events directly? Just have the loop run a short time.sleep

def opencallback(channel):
    print(GP.input(channel))
    if GP.input(channel):
        transmit("2-02")
        GP.output(ledOut, True)
        capture(3, (False, False, True), False)
        GP.output(ledOut, False)
        transmit("2-04")
        closeEvent = 0
    else:
        transmit("2-01")
        capture(2, (False, True, False), True)  # front cam
        transmit("2-03")
Ketzak
  • 620
  • 4
  • 14