Created
November 11, 2017 10:58
Prototype IR control to MQTT gateway
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import signal | |
import uuid | |
import threading | |
import sys | |
import time | |
import datetime | |
import subprocess | |
import paho.mqtt.client as mqtt | |
import evdev | |
import json | |
def log(s): | |
m = "[%s] %s\n" % (datetime.datetime.utcnow(), s) | |
sys.stderr.write(m + "\n") | |
class Watcher: | |
"""this class solves two problems with multithreaded | |
programs in Python, (1) a signal might be delivered | |
to any thread (which is just a malfeature) and (2) if | |
the thread that gets the signal is waiting, the signal | |
is ignored (which is a bug). | |
The watcher is a concurrent process (not thread) that | |
waits for a signal and the process that contains the | |
threads. See Appendix A of The Little Book of Semaphores. | |
http://greenteapress.com/semaphores/ | |
I have only tested this on Linux. I would expect it to | |
work on the Macintosh and not work on Windows. | |
From: http://code.activestate.com/recipes/496735-workaround-for-missed-sigint-in-multithreaded-prog/ | |
""" | |
def __init__(self): | |
""" Creates a child thread, which returns. The parent | |
thread waits for a KeyboardInterrupt and then kills | |
the child thread. | |
""" | |
self.child = os.fork() | |
if self.child == 0: | |
return | |
else: | |
self.watch() | |
def watch(self): | |
try: | |
os.wait() | |
except KeyboardInterrupt: | |
# I put the capital B in KeyBoardInterrupt so I can | |
# tell when the Watcher gets the SIGINT | |
print 'KeyBoardInterrupt' | |
self.kill() | |
sys.exit() | |
def kill(self): | |
try: | |
os.kill(self.child, signal.SIGKILL) | |
except OSError: pass | |
# The callback for when the client receives a CONNACK response from the server. | |
def on_connect(client, userdata, flags, rc): | |
print("Connected with result code "+str(rc)) | |
# Subscribing in on_connect() means that if we lose the connection and | |
# reconnect then subscriptions will be renewed. | |
#client.subscribe("topic") | |
# The callback for when a PUBLISH message is received from the server. | |
def on_message(client, userdata, msg): | |
msgpayload = str(msg.payload) | |
print(msg.topic+" "+msgpayload) | |
class MQTTClient(threading.Thread): | |
def __init__(self, clientid, mqttcfg): | |
super(MQTTClient, self).__init__() | |
serverip = mqttcfg["mqtt"]["serverip"] | |
port = mqttcfg["mqtt"]["port"] | |
log("MQTT connecting to %s:%u" % (serverip, port)) | |
self.mqttclient = mqtt.Client(clientid, protocol=mqtt.MQTTv31) | |
self.mqttclient.on_connect = on_connect | |
self.mqttclient.on_message = on_message | |
self.mqttclient.connect(serverip, port) | |
self.connected = True | |
log("MQTT connected %s:%u" % (serverip, port)) | |
def run(self): | |
while self.connected: | |
rc = self.mqttclient.loop(10) | |
if rc == 7: | |
log("MQTT attempting reconnect") | |
self.mqttclient.reconnect() | |
try: | |
log("MQTT attempting disconnect") | |
self.disconnect() | |
except Exception, e: | |
pass | |
def disconnect(self): | |
log("Signalling disconnect to MQTT loop") | |
self.connected = False | |
key_state = {} | |
def get_modifiers(): | |
global key_state | |
ret = [] | |
for x in key_state.keys(): | |
if key_state[x] == 1: | |
ret.append(x) | |
ret.sort() | |
if len(ret) == 0: | |
return "" | |
return "_" + "_".join(ret) | |
modifiers = ["KEY_LEFTSHIFT", "KEY_RIGHTSHIFT", "KEY_LEFTCTRL", "KEY_RIGHTCTRL"] | |
ignore = ["KEY_NUMLOCK"] # the number keys on the remote always set and unset numlock - this is superfluous for my use-case | |
def set_modifier(keycode, keystate): | |
global key_state, modifiers | |
if keycode in modifiers: | |
key_state[keycode] = keystate | |
def is_modifier(keycode): | |
global modifiers | |
if keycode in modifiers: | |
return True | |
return False | |
def is_ignore(keycode): | |
global ignore | |
if keycode in ignore: | |
return True | |
return False | |
class InputMonitor(threading.Thread): | |
def __init__(self, mqttclient, device, topic): | |
super(InputMonitor, self).__init__() | |
self.mqttclient = mqttclient | |
self.device = evdev.InputDevice(device) | |
self.topic = topic | |
log("Monitoring %s and sending to topic %s" % (device, topic)) | |
def run(self): | |
global key_state | |
# Grab the input device to avoid keypresses also going to th | |
# Linux console (and attempting to login) | |
self.device.grab() | |
for event in self.device.read_loop(): | |
if event.type == evdev.ecodes.EV_KEY: | |
k = evdev.categorize(event) | |
set_modifier(k.keycode, k.keystate) | |
if not is_modifier(k.keycode) and not is_ignore(k.keycode): | |
if k.keystate == 1: | |
msg = k.keycode + get_modifiers() | |
self.mqttclient.publish(self.topic, msg) | |
if __name__ == "__main__": | |
try: | |
Watcher() | |
# Read MQTT broker config (example contents: { | |
# "mqtt":{ | |
# "serverip":"10.52.2.41", | |
# "port":1883, | |
# "protocol":{ | |
# "protocolId":"MQIsdp", | |
# "protocolVersion":3 | |
# } | |
# } | |
#} | |
mqttcfg = json.load(file("../config/config_mqtt.json")) | |
myname = "pi_" + '_'.join(("%012X" % uuid.getnode())[i:i+2] for i in range(0, 12, 2)) | |
mq = MQTTClient(myname, mqttcfg) | |
mq.start() | |
# The MQTT topic is based on our DEVICEID | |
topic = "IR/" + os.environ["DEVICEID"] | |
im0 = InputMonitor(mq.mqttclient, "/dev/input/event0", topic) | |
im0.start() | |
im1 = InputMonitor(mq.mqttclient, "/dev/input/event1", topic) | |
im1.start() | |
except Exception as e: | |
log("Top level exception: %s" % str(e)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment