import os import json import itertools import logging import paho.mqtt.client as mqtt import vortex logging.basicConfig(level=logging.INFO) class VortexSpejsIOTClient(mqtt.Client): topic_prefix = "iot/polycom/" def __init__(self, config, *args, **kwargs): super(VortexSpejsIOTClient, self).__init__(*args, **kwargs) self.config = config self.logger = logging.getLogger(self.__class__.__name__) self.vortex = vortex.VortexConnection( self.config["control"]["host"], self.config["control"]["port"] ) self.vortex.handlers.append(self.on_vortex_message) self.device_id = "" def on_connect(self, client, userdata, flags, rc): self.subscribe(self.topic_prefix + "+/+/set") self.logger.info("Connected") self.device_id = list(self.vortex.discover().keys())[0] self.vortex[self.device_id].call_single("BLAUTO0") def run(self): self.connect(**self.config["mqtt"]) self.loop_start() self.vortex.loop() def multi_call(self, cmd, channels, args): for ch in channels: self.vortex[self.device_id].call_single( "%s%s%s" % (cmd, ch, args), wait=False ) def matrix_call(self, cmd, pairs, args): for s, t in pairs: self.vortex[self.device_id].call_single( "%s%s,%s,%s" % (cmd, s, t, args), wait=False ) def notify(self, node, attribute, value): if isinstance(value, bool): value = str(value).lower() else: value = str(value) self.publish("%s%s/%s" % (self.topic_prefix, node, attribute), value) def on_message(self, client, userdata, msg): topic = msg.topic[len(self.topic_prefix) :] self.logger.info("mqtt -> %r %r", topic, msg.payload) node, attrib, _ = topic.split("/") msg.payload = msg.payload.decode("utf-8") if node in self.config["inputs"]: if attrib == "gain": self.multi_call("GAINI", self.config["inputs"][node], msg.payload) elif attrib == "mute": self.multi_call( "MUTEI", self.config["inputs"][node], "1" if msg.payload == "true" else "0", ) elif attrib == "mode": self.multi_call( "MIC", self.config["inputs"][node], "1" if msg.payload == "mic" else "0", ) elif node in self.config["outputs"]: if attrib == "gain": self.multi_call("GAINO", self.config["outputs"][node], msg.payload) elif attrib == "mute": self.multi_call( "MUTEO", self.config["outputs"][node], "1" if msg.payload == "true" else "0", ) elif ":" in node: # This is matrix operation... inp, _, out = node.partition(":") if inp not in self.config["inputs"] or out not in self.config["outputs"]: self.logger.warning("Invalid route: %r", node) return inp_chs = self.config["inputs"][inp] out_chs = self.config["outputs"][out] pairs = zip(itertools.cycle(inp_chs), out_chs) if attrib == "mute": self.matrix_call("MMUTE", pairs, "1" if msg.payload == "true" else "0") elif attrib == "gain": self.matrix_call("MGAIN", pairs, msg.payload) def on_vortex_message(self, msg): self.logger.info("vortex -> %r", msg) device_id, msg = msg[:3], msg[3:] if self.device_id != device_id.decode(): self.logger.debug("%r/%r: Invalid device id", device_id, self.device_id) return if msg.startswith(b"MMUTE"): inp, out, muted = msg[5:].decode().split(",") inp_label = self.input_from_channel(inp) out_label = self.output_from_channel(out) self.notify("%s:%s" % (inp_label, out_label), "mute", muted == "1") elif msg.startswith(b"MGAIN"): inp, out, gain = msg[5:].decode().split(",") inp_label = self.input_from_channel(inp) out_label = self.output_from_channel(out) self.notify("%s:%s" % (inp_label, out_label), "gain", gain) elif msg.startswith(b"GAINI"): msg = msg.decode() channel_id, gain = msg[5], msg[6:] label = self.input_from_channel(channel_id) self.notify(label, "gain", gain) elif msg.startswith(b"MUTEI"): msg = msg.decode() channel_id, muted = msg[5], msg[6:] label = self.input_from_channel(channel_id) self.notify(label, "mute", muted == "1") def input_from_channel(self, channel): return self.find_label(self.config["inputs"], channel) def output_from_channel(self, channel): return self.find_label(self.config["outputs"], channel) def find_label(self, labels, channel): for label, channels in labels.items(): if channel in map(str, channels): return label return None def main(): config = {} with open(os.environ["CONFIG_PATH"], "rb") as fd: config = json.load(fd) client = VortexSpejsIOTClient(config) client.run() if __name__ == "__main__": main()