Skip to content

Instantly share code, notes, and snippets.

@jamesbulpin
Created November 11, 2017 10:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jamesbulpin/b940e7d81e2e65158f12e59b4d6a0c3c to your computer and use it in GitHub Desktop.
Save jamesbulpin/b940e7d81e2e65158f12e59b4d6a0c3c to your computer and use it in GitHub Desktop.
Prototype IR control to MQTT gateway
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