Example #1
0
  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
    }
  }
Example #2
0
 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);
 }
Example #3
0
 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);
   }
 }
Example #4
0
 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);
 }
/**
 * Wrapper to turn a Reader into an InputStream. The previous version of this class did not handle
 * correctly encode Unicode characters; it was used primarily for hash filtering, where the result
 * must be a reproducible byte sequence but needn't necessarily be exactly reconstructible to the
 * original character sequence. This stream is now used by link rewriters, which do have to produce
 * a valid decodable result, so it now handles character encoding. But this version isn't
 * necessarily reproducible because some encodings aren't unique - the exact sequence of bytes
 * produced can depend on the number of bytes read at a time.
 *
 * <p>If called with no encoder, this version reproduces the previous behavior, making it
 * polling-compatible.
 */
public class ReaderInputStream extends InputStream {

  protected static Logger log = Logger.getLogger("ReaderInputStream");

  /** Source Reader */
  private Reader in;

  private String encoding = null;
  private Charset cset;
  private CharsetEncoder encoder;

  private byte[] slack;

  private int begin;

  /**
   * Construct a <code>ReaderInputStream</code> for the specified <code>Reader</code>. This version
   * (no encoding specified) converts characters to bytes by casting, to be consistent with previous
   * behavior.
   *
   * @param reader <code>Reader</code>. Must not be <code>null</code>.
   */
  public ReaderInputStream(Reader reader) {
    if (reader == null) {
      throw new IllegalArgumentException("reader must not be null");
    }
    in = reader;
  }

  /**
   * Construct a <code>ReaderInputStream</code> for the specified <code>Reader</code>, with the
   * specified encoding.
   *
   * @param reader non-null <code>Reader</code>.
   * @param encoding charset name
   */
  public ReaderInputStream(Reader reader, String encoding) {
    this(reader);
    this.encoding = encoding;
  }

  /** Returns the next byte of the encoded character sequence */
  public synchronized int read() throws IOException {
    if (in == null) {
      throw new IOException("Stream Closed");
    }

    byte result;
    if (slack != null && begin < slack.length) {
      result = slack[begin];
      if (++begin == slack.length) {
        slack = null;
      }
    } else {
      byte[] buf = new byte[1];
      if (read(buf, 0, 1) <= 0) {
        return -1;
      } else {
        result = buf[0];
      }
    }

    return result & 0xFF;
  }

  /**
   * Reads from the <code>Reader</code> into a byte array
   *
   * @param b the byte array to read into
   * @param off the offset in the byte array
   * @param len the length in the byte array to fill
   * @return the actual number read into the byte array, -1 at the end of the stream
   * @exception IOException if an error occurs
   */
  public synchronized int read(byte[] b, int off, int len) throws IOException {
    if (in == null) {
      throw new IOException("Stream Closed");
    }
    if (len == 0) {
      return 0;
    }
    if (encoding == null) {
      return castingRead(b, off, len);
    }
    while (slack == null) {
      char[] buf = new char[len]; // might read too much
      int n = in.read(buf);
      if (n == -1) {
        return -1;
      }
      if (n > 0) {
        slack = new String(buf, 0, n).getBytes(encoding);
        begin = 0;
      }
    }

    if (len > slack.length - begin) {
      len = slack.length - begin;
    }

    System.arraycopy(slack, begin, b, off, len);

    if ((begin += len) >= slack.length) {
      slack = null;
    }

    return len;
  }

  static final int DEFAULT_BUFFER_CAPACITY = 16384;
  private char[] charBuffer;

  // Old version simply casts.  Must still be used for hashing unless
  // change polling version
  private int castingRead(byte[] outputBuf, int off, int len) throws IOException {
    if (charBuffer == null) {
      charBuffer = new char[DEFAULT_BUFFER_CAPACITY];
    }
    if (len > DEFAULT_BUFFER_CAPACITY) {
      len = DEFAULT_BUFFER_CAPACITY;
    }
    int numRead = in.read(charBuffer, 0, len);
    for (int ix = 0; ix < numRead; ix++) {
      outputBuf[off + ix] = (byte) charBuffer[ix];
    }
    return numRead;
  }

  /**
   * Marks the read limit of the StringReader.
   *
   * @param limit the maximum limit of bytes that can be read before the mark position becomes
   *     invalid
   */
  public synchronized void mark(final int limit) {
    try {
      in.mark(limit);
    } catch (IOException ioe) {
      throw new RuntimeException(ioe.getMessage());
    }
  }

  /**
   * @return the current number of bytes ready for reading
   * @exception IOException if an error occurs
   */
  public synchronized int available() throws IOException {
    if (in == null) {
      throw new IOException("Stream Closed");
    }
    if (slack != null) {
      return slack.length - begin;
    }
    if (in.ready()) {
      return 1;
    } else {
      return 0;
    }
  }

  /** @return false - mark is not supported */
  public boolean markSupported() {
    return false; // would be imprecise
  }

  /**
   * Resets the StringReader.
   *
   * @exception IOException if the StringReader fails to be reset
   */
  public synchronized void reset() throws IOException {
    if (in == null) {
      throw new IOException("Stream Closed");
    }
    slack = null;
    in.reset();
  }

  /**
   * Closes the Stringreader.
   *
   * @exception IOException if the original StringReader fails to be closed
   */
  public synchronized void close() throws IOException {
    if (in != null) {
      in.close();
      slack = null;
      in = null;
    }
  }

  /** Return the underlying Reader */
  public Reader getReader() {
    return in;
  }
}
Example #6
0
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);
  }
}