tomquist / solix2mqtt

CLI tool to poll the Solix API for the latest sample data from all devices and publish them to an MQTT broker.
MIT License
38 stars 8 forks source link

Request: Enhance MQTT Publisher with Recursive Topic Publishing #14

Open robbieffm opened 3 months ago

robbieffm commented 3 months ago

Overview: This pull request introduces a new feature to the Publisher class, allowing it to publish individual key-value pairs from a JSON object to separate MQTT topics. The original functionality of publishing the entire JSON object to a single topic is preserved, while the new feature adds the ability to automatically distribute the data across multiple topics.

Changes Introduced:

src/publish.ts

import { AsyncMqttClient, connectAsync } from "async-mqtt";

/**
 * The Publisher class is responsible for publishing messages to an MQTT broker. 
 * It supports both publishing a complete JSON message to a single topic 
 * and recursively splitting the JSON into individual topics for detailed data publishing.
 */
export class Publisher {
  private client: AsyncMqttClient | undefined;

  /**
   * Constructs a new Publisher instance.
   *
   * @param url - The URL of the MQTT broker.
   * @param retain - An optional flag indicating whether the published messages should be retained by the broker.
   * @param clientId - An optional client ID to identify the connection. If not provided, the MQTT broker may assign one.
   * @param username - An optional username for broker authentication.
   * @param password - An optional password for broker authentication.
   */
  constructor(
    private readonly url: string,
    private readonly retain?: boolean,
    private readonly clientId?: string,
    private readonly username?: string,
    private readonly password?: string,
  ) {}

  /**
   * Ensures an active connection to the MQTT broker.
   *
   * @returns An instance of AsyncMqttClient representing the active connection.
   */
  private async getClient() {
    if (this.client && this.client.connected) {
      return this.client;
    }
    await this.client?.end(); // Ends any existing connection if present.
    this.client = await connectAsync(this.url, {
      clientId: this.clientId,
      username: this.username,
      password: this.password,
    });
    return this.client;
  }

  /**
   * Recursively publishes each key-value pair of a JSON object to separate MQTT topics.
   *
   * @param baseTopic - The base topic under which all subtopics will be published.
   * @param data - The data to be published, can be an object or a primitive value.
   */
  private async publishRecursively(baseTopic: string, data: any) {
    if (typeof data === 'object' && data !== null) {
      // If the data is an object, iterate over its keys and call this method recursively.
      for (const key in data) {
        if (data.hasOwnProperty(key)) {
          await this.publishRecursively(`${baseTopic}/${key}`, data[key]);
        }
      }
    } else {
      // If the data is a primitive value, publish it to the appropriate topic.
      await (await this.getClient()).publish(baseTopic, String(data), { retain: this.retain });
    }
  }

  /**
   * Publishes a message to a specified MQTT topic.
   * This method handles both sending the entire JSON message to a single topic 
   * and splitting the JSON into multiple topics for granular data distribution.
   *
   * @param topic - The MQTT topic to which the message will be published.
   * @param message - The message to be published. It can be a JSON object or any other data type.
   */
  async publish(topic: string, message: any) {
    // Publish the entire JSON object as a single message to the given topic.
    await (await this.getClient()).publish(topic, JSON.stringify(message), { retain: this.retain });

    // Recursively publish each element of the JSON object to its own topic.
    await this.publishRecursively(topic, message);
  }
}
tomquist commented 2 months ago

Hm, is this supposed to be a pull request? What’s the use-case? Looks like this issue is entirely AI-generated TBH.