private void doConnect() { L.info( "Connecting to MQTT broker " + mqttc.getServerURI() + " with CLIENTID=" + mqttc.getClientId() + " and TOPIC PREFIX=" + topicPrefix); MqttConnectOptions copts = new MqttConnectOptions(); copts.setWill(topicPrefix + "connected", "0".getBytes(), 1, true); copts.setCleanSession(true); copts.setUserName("emonpi"); copts.setPassword("emonpimqtt2016".toCharArray()); try { mqttc.connect(copts); sendConnectionState(); L.info("Successfully connected to broker, subscribing to " + topicPrefix + "(set|get)/#"); try { mqttc.subscribe(topicPrefix + "set/#", 1); mqttc.subscribe(topicPrefix + "get/#", 1); shouldBeConnected = true; } catch (MqttException mqe) { L.log(Level.WARNING, "Error subscribing to topic hierarchy, check your configuration", mqe); throw mqe; } } catch (MqttException mqe) { L.log( Level.WARNING, "Error while connecting to MQTT broker, will retry: " + mqe.getMessage(), mqe); queueConnect(); // Attempt reconnect } }
void processMessage(String topic, MqttMessage msg) { L.fine("Received " + msg + " to " + topic); topic = topic.substring(topicPrefix.length(), topic.length()); if (topic.startsWith("set/")) processSetGet(topic.substring(4), msg, true); else if (topic.startsWith("get/")) processSetGet(topic.substring(4), msg, false); else L.warning("Ignored message " + msg + " to unknown topic " + topic); }
private void doPublish( String name, Object val, String src, String dpt, String textual, long updateTime, long lastChange) { JsonObject jso = new JsonObject(); jso.add("ts", updateTime).add("lc", lastChange).add("knx_src_addr", src).add("knx_dpt", dpt); if (textual != null) jso.add("knx_textual", textual); if (val instanceof Integer) jso.add("val", ((Integer) val).intValue()); else if (val instanceof Number) jso.add("val", ((Number) val).doubleValue()); else jso.add("val", val.toString()); String txtmsg = jso.toString(); MqttMessage msg = new MqttMessage(jso.toString().getBytes(StandardCharsets.UTF_8)); msg.setQos(0); msg.setRetained(true); try { String fullTopic = topicPrefix + "status/" + name; mqttc.publish(fullTopic, msg); L.finer("Published " + txtmsg + " to " + fullTopic); } catch (MqttException e) { L.log(Level.WARNING, "Error when publishing message " + txtmsg, e); } }
private void processSetGet(String namePart, MqttMessage msg, boolean set) { if (msg.isRetained()) { L.finer("Ignoring retained message " + msg + " to " + namePart); return; } // Now translate the topic into a group address GroupAddressInfo gai = GroupAddressManager.getGAInfoForName(namePart); if (gai == null) { L.warning( "Unable to translate name " + namePart + " into a group address, ignoring message " + msg); return; } L.fine("Name " + namePart + " translates to GA " + gai.address); String data = new String(msg.getPayload(), StandardCharsets.UTF_8); if (set) KNXConnector.doGroupWrite(gai.address, data, gai); else KNXConnector.doGroupRead(gai.address, data, gai); }
public static synchronized void queueNameFetch() { // We only attempt to fetch once every 10 minutes if (pendingFetch == null) { pendingFetch = new TimerTask() { @Override public void run() { fetchDeviceNames(); pendingFetch = null; } }; long delay = lastFetch + (10 * 60 * 1000) - System.currentTimeMillis(); Main.t.schedule(pendingFetch, delay > 0 ? delay : 0); L.info("Queued Device name fetch in " + delay + "ms"); } }
public class ReGaDeviceNameResolver { private static final Map<String, String> nameCache = new HashMap<>(); public static void fetchDeviceNames() { String nametable = System.getProperty("hm2mqtt.hm.jsonNameTable"); if (nametable != null) { fetchDeviceNamesFromNameTable(nametable); return; } if (Boolean.getBoolean("hm2mqtt.hm.disableReGa")) return; fetchDeviceNamesFromReGa(); } /* * Obtain device names from a JSON table (e.g. from hm-manager) */ private static void fetchDeviceNamesFromNameTable(String filename) { lastFetch = System.currentTimeMillis(); try (FileReader f = new FileReader(filename)) { JsonObject table = Json.parse(f).asObject(); int cnt = 0; synchronized (nameCache) { for (Member m : table) { nameCache.put(m.getName(), m.getValue().asString()); cnt++; } } L.log(Level.INFO, "Read " + cnt + " entries from name table " + filename); couldFetchOnce = true; DeviceInfo.resolveNames(); } catch (Exception e) { L.log(Level.WARNING, "Error reading device name table " + filename, e); } } /* * Obtain device names configured in the WebUI (ReGa) */ private static void fetchDeviceNamesFromReGa() { lastFetch = System.currentTimeMillis(); L.info("Obtaining ReGa device and channel names"); String r = TCLRegaHandler.sendHMScript( "string id;" + "foreach(id, root.Channels ().EnumUsedIDs())" + " {" + " var ch=dom.GetObject(id);" + " WriteLine(ch.Address()+\"\t\"+ch.Name());" + " }" + "foreach(id, root.Devices().EnumUsedIDs())" + " {" + " var d=dom.GetObject(id);" + " WriteLine(d.Address()+\":0\t\"+d.Name());" + " }"); if (r == null) return; String lines[] = r.split("\n"); for (String l : lines) { String p[] = l.split("\t"); synchronized (nameCache) { if (p.length == 2) nameCache.put(p[0], p[1]); } } couldFetchOnce = true; DeviceInfo.resolveNames(); } private static final Logger L = Logger.getLogger(ReGaDeviceNameResolver.class.getName()); public static String getNameForAddress(String address) { synchronized (nameCache) { return nameCache.get(address); } } private static long lastFetch; private static boolean couldFetchOnce; private static TimerTask pendingFetch; public static boolean couldTheoreticallyResolveNames() { return couldFetchOnce; } public static synchronized void queueNameFetch() { // We only attempt to fetch once every 10 minutes if (pendingFetch == null) { pendingFetch = new TimerTask() { @Override public void run() { fetchDeviceNames(); pendingFetch = null; } }; long delay = lastFetch + (10 * 60 * 1000) - System.currentTimeMillis(); Main.t.schedule(pendingFetch, delay > 0 ? delay : 0); L.info("Queued Device name fetch in " + delay + "ms"); } } }
public class MQTTHandler { private final Logger L = Logger.getLogger(getClass().getName()); public static void init() throws MqttException { instance = new MQTTHandler(); instance.doInit(); } private final String topicPrefix; private MQTTHandler() { String tp = System.getProperty("knx2mqtt.mqtt.topic", "knx"); if (!tp.endsWith("/")) tp += "/"; topicPrefix = tp; } private static MQTTHandler instance; private MqttClient mqttc; private void queueConnect() { shouldBeConnected = false; Main.t.schedule( new TimerTask() { @Override public void run() { doConnect(); } }, 10 * 1000); } private class StateChecker extends TimerTask { @Override public void run() { if (!mqttc.isConnected() && shouldBeConnected) { L.warning("Should be connected but aren't, reconnecting"); queueConnect(); } } } private boolean shouldBeConnected; private static boolean knxConnectionState; private void processSetGet(String namePart, MqttMessage msg, boolean set) { if (msg.isRetained()) { L.finer("Ignoring retained message " + msg + " to " + namePart); return; } // Now translate the topic into a group address GroupAddressInfo gai = GroupAddressManager.getGAInfoForName(namePart); if (gai == null) { L.warning( "Unable to translate name " + namePart + " into a group address, ignoring message " + msg); return; } L.fine("Name " + namePart + " translates to GA " + gai.address); String data = new String(msg.getPayload(), StandardCharsets.UTF_8); if (set) KNXConnector.doGroupWrite(gai.address, data, gai); else KNXConnector.doGroupRead(gai.address, data, gai); } void processMessage(String topic, MqttMessage msg) { L.fine("Received " + msg + " to " + topic); topic = topic.substring(topicPrefix.length(), topic.length()); if (topic.startsWith("set/")) processSetGet(topic.substring(4), msg, true); else if (topic.startsWith("get/")) processSetGet(topic.substring(4), msg, false); else L.warning("Ignored message " + msg + " to unknown topic " + topic); } private void doConnect() { L.info( "Connecting to MQTT broker " + mqttc.getServerURI() + " with CLIENTID=" + mqttc.getClientId() + " and TOPIC PREFIX=" + topicPrefix); MqttConnectOptions copts = new MqttConnectOptions(); copts.setWill(topicPrefix + "connected", "0".getBytes(), 1, true); copts.setCleanSession(true); copts.setUserName("emonpi"); copts.setPassword("emonpimqtt2016".toCharArray()); try { mqttc.connect(copts); sendConnectionState(); L.info("Successfully connected to broker, subscribing to " + topicPrefix + "(set|get)/#"); try { mqttc.subscribe(topicPrefix + "set/#", 1); mqttc.subscribe(topicPrefix + "get/#", 1); shouldBeConnected = true; } catch (MqttException mqe) { L.log(Level.WARNING, "Error subscribing to topic hierarchy, check your configuration", mqe); throw mqe; } } catch (MqttException mqe) { L.log( Level.WARNING, "Error while connecting to MQTT broker, will retry: " + mqe.getMessage(), mqe); queueConnect(); // Attempt reconnect } } private void doInit() throws MqttException { String server = System.getProperty("knx2mqtt.mqtt.server", "tcp://localhost:1883"); String clientID = System.getProperty("knx2mqtt.mqtt.clientid", "knx2mqtt"); mqttc = new MqttClient(server, clientID, new MemoryPersistence()); mqttc.setCallback( new MqttCallback() { @Override public void messageArrived(String topic, MqttMessage msg) throws Exception { try { processMessage(topic, msg); } catch (Exception e) { L.log(Level.WARNING, "Error when processing message " + msg + " for " + topic, e); } } @Override public void deliveryComplete(IMqttDeliveryToken token) { /* Intentionally ignored */ } @Override public void connectionLost(Throwable t) { L.log(Level.WARNING, "Connection to MQTT broker lost", t); queueConnect(); } }); doConnect(); Main.t.schedule(new StateChecker(), 30 * 1000, 30 * 1000); } private void doPublish( String name, Object val, String src, String dpt, String textual, long updateTime, long lastChange) { JsonObject jso = new JsonObject(); jso.add("ts", updateTime).add("lc", lastChange).add("knx_src_addr", src).add("knx_dpt", dpt); if (textual != null) jso.add("knx_textual", textual); if (val instanceof Integer) jso.add("val", ((Integer) val).intValue()); else if (val instanceof Number) jso.add("val", ((Number) val).doubleValue()); else jso.add("val", val.toString()); String txtmsg = jso.toString(); MqttMessage msg = new MqttMessage(jso.toString().getBytes(StandardCharsets.UTF_8)); msg.setQos(0); msg.setRetained(true); try { String fullTopic = topicPrefix + "status/" + name; mqttc.publish(fullTopic, msg); L.finer("Published " + txtmsg + " to " + fullTopic); } catch (MqttException e) { L.log(Level.WARNING, "Error when publishing message " + txtmsg, e); } } private void sendConnectionState() { try { instance.mqttc.publish( instance.topicPrefix + "connected", (knxConnectionState ? "2" : "1").getBytes(), 1, true); } catch (MqttException e) { /* Ignore */ } } public static void setKNXConnectionState(boolean connected) { knxConnectionState = connected; instance.sendConnectionState(); } public static void publish( String name, Object val, String src, String dpt, String textual, long updateTime, long lastChange) { instance.doPublish(name, val, src, dpt, textual, updateTime, lastChange); } }