Hi all,
I am writing a little code to run at startup and monitor for keyboard clicks. Here's the skinny:
I have a Pi 4B and an official RPi 7" LCD in a Smartipi Touch Pro case. It is sitting on my desk.
I have two desktops enabled on the Pi and a Dakboard running on the first desktop. When I first turn on the Pi, it loads the Dakboard and all is well. I have a little keyboard on the desk for the Pi and I use it to pop over to the second desktop to do work with the OS as needed. The script in question is meant to run in the background and do a little count up to whatever the variable "maxidle" is set to. Once it reaches that, the script checks the active desktop and IF the active desktop isn't #1 (the Dakboard), it switches the active desktop over to that. Keyboard input should reset the count up to 0. In short, it puts the display back to a dakboard after I stop messing with the keyboard for some time.
The issue I have is that in testing, the keyboard input does reset the loop and whatnot. From time to time though, the script just stops on keyboard input. It doesn't reset the count. Here is my code thus far. Any advice is greatly welcomed:
Code:
Select all
from pynput.keyboard import Listener
import subprocess
from subprocess import call
import time
maxidle = 3*60 #cycles
def on_press(key):
global keyed
a = key.char
if a:
print("key pressed!!")
keyed = True
except AttributeError:
a = key
if a:
print("special key pressed!!")
keyed = True
while True:
lastkeyed = 0
keyed = False
print("keyed is", keyed)
listener = Listener(on_press=on_press)
listener.start()
while not keyed:
if lastkeyed < maxidle:
print("lastkeyed is", lastkeyed)
lastkeyed += 1
elif lastkeyed == maxidle:
lastkeyed += 1
print("lastkeyed is", lastkeyed)
print("maxidle reached (",maxidle,"). checking active desktop.")
outputcheck = subprocess.check_output(["wmctrl", "-d"])
outputcheckstr = outputcheck.decode('utf-8')
splita,splitb = outputcheckstr.split("Primary",2)
activedesk = "*"
if activedesk in splita:
print("Primary screen already active. do not change.")
if activedesk in splitb:
print("Secondary screen active. changing.")
call (["wmctrl", "-s", "0"])
elif lastkeyed > maxidle:
lastkeyed += 1
print("lastkeyed is", lastkeyed)
time.sleep(1)
listener.stop()
print("resetting primary loop")
This is one of two scripts I have written for the project. The other is for the Adafruit BS412 PIR that I used in place of the Pi camera on the front of the SmartiPi case (drilling required for fit). It does a similar count up for 20 minutes on lack of PIR detection and then blanks the screen. Obviously, I have disabled the build-in screen blanking to do this method. One interesting bit of code that I needed there was a sort of "is this really motion" check because the PIR throws false positives that close to the Pi. The net result of that check is a delay of 8 or so seconds before turning on the monitor after valid motion is detected.
Thanks!
Let me propose an object oriented approach:
ScreenAction, responsible for the screen switching (did not find the action when IDLE and then go back to ACTIVE)
StateHandler is responsible to sort all keypress- and timeout-events. Instead of counting loop count, there is a Timer used.
Code:
Select all
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pynput.keyboard
import subprocess
import threading
# for development environment, set debug=True
# for production environment , set debug=False
debug = True
if debug:
MAXIDLE = 10 # while developing, do not wait too long
else:
MAXIDLE = 3*60
class ScreenAction:
def idle(self):
if debug: print("ScreenAction.idle()")
if not debug:
pass # could not find this action in your code
def acive(self):
if debug: print("ScreenAction.active()")
if not debug:
outputcheck = subprocess.check_output(["wmctrl", "-d"])
outputcheckstr = outputcheck.decode('utf-8')
splita,splitb = outputcheckstr.split("Primary",2)
activedesk = "*"
if activedesk in splita:
print("Primary screen already active. do not change.")
if activedesk in splitb:
print("Secondary screen active. changing.")
subprocess.call (["wmctrl", "-s", "0"])
class StateHandler():
START = 'START'
IDLE = 'IDLE'
ACTIVE = 'ACTIVE'
def __init__(self):
self.state = StateHandler.START
# the callback come from either Timer or pyinput
# to handle these threaded environments a Lock is needed
self.lock = threading.Lock()
self.timer = threading.Timer(MAXIDLE, self.timeout)
self.timer.start()
def keypress(self):
with self.lock:
if self.state == StateHandler.START:
screen_action.acive()
self.state = StateHandler.ACTIVE
elif self.state == StateHandler.IDLE:
screen_action.acive()
self.state = StateHandler.ACTIVE
elif self.state == StateHandler.ACTIVE:
else:
print("unknown state !", self.state)
if debug: print("state", self.state)
self.timer.cancel()
self.timer=threading.Timer(MAXIDLE, self.timeout)
self.timer.start()
def timeout(self):
with self.lock:
if self.state == StateHandler.START:
screen_action.idle()
self.state = StateHandler.IDLE
elif self.state == StateHandler.IDLE:
elif self.state == StateHandler.ACTIVE:
screen_action.idle()
self.state = StateHandler.IDLE
else:
print("unknown state !", self.state)
if debug: print("state", self.state)
print("started")
screen_action = ScreenAction()
state_handler = StateHandler()
def keypress(key):
# if filtering on key is needed do this here
state_handler.keypress()
listener = pynput.keyboard.Listener(on_press=keypress)
listener.start()
wait = threading.Event()
wait.wait()
except KeyboardInterrupt:
except Exception as e:
print(e)
print("terminated")