2023-12-28 05:46:39 +00:00
|
|
|
#include <DigitalOutputIoT.hpp>
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Create a new DigitalOutputIoT object
|
|
|
|
*/
|
|
|
|
DigitalOutputIoT::DigitalOutputIoT()
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
this->state_report_topic = new char[10];
|
|
|
|
this->value_report_topic = new char[10];
|
2023-12-28 10:03:47 +00:00
|
|
|
this->digital_outputs_publish_enabled = true;
|
2023-12-28 06:14:18 +00:00
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Destroy the DigitalOutputIoT object
|
|
|
|
*/
|
|
|
|
DigitalOutputIoT::~DigitalOutputIoT()
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
delete[] this->state_report_topic;
|
|
|
|
delete[] this->value_report_topic;
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Initialize the DigitalOutputIoT object
|
|
|
|
*
|
|
|
|
* @note ALthough this function can be called inside the main program, it is recommended to use ESPMegaPRO::installCard() instead
|
|
|
|
*
|
|
|
|
* @param card_id The id of the card
|
|
|
|
* @param card The card object
|
|
|
|
* @param mqtt The PubSubClient object
|
|
|
|
* @param base_topic The base topic of the card
|
|
|
|
* @return True if the initialization is successful, false otherwise
|
|
|
|
*/
|
|
|
|
bool DigitalOutputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic)
|
|
|
|
{
|
2023-12-30 11:47:52 +00:00
|
|
|
ESP_LOGD("DigitalOutputIoT", "Beginning DigitalOutputIoT");
|
2023-12-28 06:34:51 +00:00
|
|
|
this->mqtt = mqtt;
|
|
|
|
this->base_topic = base_topic;
|
2023-12-31 19:39:51 +00:00
|
|
|
this->card = (DigitalOutputCard *)card;
|
|
|
|
this->card_id = card_id;
|
2023-12-28 05:46:39 +00:00
|
|
|
this->set_state_length = strlen(SET_STATE_TOPIC);
|
|
|
|
this->set_value_length = strlen(SET_VALUE_TOPIC);
|
|
|
|
this->state_length = strlen(STATE_TOPIC);
|
|
|
|
this->value_length = strlen(VALUE_TOPIC);
|
2023-12-28 10:03:47 +00:00
|
|
|
this->request_state_length = strlen(REQUEST_STATE_TOPIC);
|
|
|
|
this->publish_enable_length = strlen(PUBLISH_ENABLE_TOPIC);
|
2023-12-28 06:14:18 +00:00
|
|
|
strcpy(this->state_report_topic, "00/state");
|
|
|
|
strcpy(this->value_report_topic, "00/value");
|
2023-12-30 11:47:52 +00:00
|
|
|
ESP_LOGV("DigitalOutputIoT", "Registering callbacks inside DigitalOutputIoT::begin");
|
2023-12-28 12:01:37 +00:00
|
|
|
// Register callbacks
|
2023-12-31 19:39:51 +00:00
|
|
|
auto bindedCallback = std::bind(&DigitalOutputIoT::handleValueChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
2023-12-28 12:01:37 +00:00
|
|
|
this->card->registerChangeCallback(bindedCallback);
|
2023-12-30 11:47:52 +00:00
|
|
|
ESP_LOGV("DigitalOutputIoT", "DigitalOutputIoT::begin complete");
|
2023-12-28 05:46:39 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Handle the MQTT messages for the DigitalOutputIoT card
|
|
|
|
*
|
|
|
|
* @param topic The topic of the message
|
|
|
|
* @param payload The payload of the message
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::handleMqttMessage(char *topic, char *payload)
|
|
|
|
{
|
|
|
|
// Protocol for digital output card
|
|
|
|
// Note that pin is always 2 characters long and padded with 0 if necessary
|
|
|
|
// Set pin state topic: <pin>/set/state payload: 0/1
|
|
|
|
// Set pin pwm topic: <pin>/set/value payload: 0-4095
|
|
|
|
// Publish pin state topic: <pin>/state payload: 0/1
|
|
|
|
// Publish pin pwm topic: <pin>/value payload: 0-4095
|
|
|
|
// Publish all topic: requeststate payload: N/A
|
|
|
|
// Enable/disable publish topic: publish_enable payload: 0/1
|
2023-12-28 05:46:39 +00:00
|
|
|
uint8_t topic_length = strlen(topic);
|
2023-12-31 19:39:51 +00:00
|
|
|
if (this->processSetStateMessage(topic, payload, topic_length))
|
|
|
|
return;
|
|
|
|
if (this->processSetValueMessage(topic, payload, topic_length))
|
|
|
|
return;
|
|
|
|
if (this->processRequestStateMessage(topic, payload, topic_length))
|
|
|
|
return;
|
2023-12-28 05:46:39 +00:00
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Process a set state message
|
|
|
|
*
|
|
|
|
* @param topic The null terminated topic
|
|
|
|
* @param payload The null terminated payload
|
|
|
|
* @param topic_length The length of the topic
|
|
|
|
* @return True if the message is a set state message, false otherwise
|
|
|
|
*/
|
|
|
|
bool DigitalOutputIoT::processSetStateMessage(char *topic, char *payload, uint8_t topic_length)
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
// Check if the topic is a set state topic
|
|
|
|
// The correct format is <pin>/set/state
|
|
|
|
// This mean that the topic must end with /set/state
|
|
|
|
// Check if the 3rd character is /
|
2023-12-31 19:39:51 +00:00
|
|
|
if (topic[2] != '/')
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// The topic must be set_state_length + 2 characters long
|
2023-12-31 19:39:51 +00:00
|
|
|
if (topic_length != set_state_length + 2)
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Check if the topic ends with /set/state
|
2023-12-31 19:39:51 +00:00
|
|
|
if (!strncmp(topic + 2, SET_STATE_TOPIC, set_state_length))
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
// Get the pin number
|
2023-12-31 19:39:51 +00:00
|
|
|
uint8_t pin = (topic[0] - '0') * 10 + (topic[1] - '0');
|
2023-12-28 05:46:39 +00:00
|
|
|
// Get the state
|
|
|
|
bool state = false;
|
|
|
|
char state_char = payload[0];
|
2023-12-31 19:39:51 +00:00
|
|
|
if (state_char == '0')
|
|
|
|
{
|
2023-12-28 10:03:47 +00:00
|
|
|
state = false;
|
2023-12-31 19:39:51 +00:00
|
|
|
}
|
|
|
|
else if (state_char == '1')
|
|
|
|
{
|
2023-12-28 10:03:47 +00:00
|
|
|
state = true;
|
2023-12-31 19:39:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Set the state
|
2023-12-28 10:03:47 +00:00
|
|
|
card->setState(pin, state);
|
2023-12-28 05:46:39 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Process a set value message
|
|
|
|
*
|
|
|
|
* @param topic The null terminated topic
|
|
|
|
* @param payload The null terminated payload
|
|
|
|
* @param topic_length The length of the topic
|
|
|
|
* @return True if the message is a set value message, false otherwise
|
|
|
|
*/
|
|
|
|
bool DigitalOutputIoT::processSetValueMessage(char *topic, char *payload, uint8_t topic_length)
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
// Check if the topic is a set value topic
|
|
|
|
// The correct format is <pin>/set/value
|
|
|
|
// This mean that the topic must end with /set/value
|
|
|
|
// Check if the 3rd character is /
|
2023-12-31 19:39:51 +00:00
|
|
|
if (topic[2] != '/')
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// The topic must be set_value_length + 2 characters long
|
2023-12-31 19:39:51 +00:00
|
|
|
if (topic_length != set_value_length + 2)
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Check if the topic ends with /set/value
|
2023-12-31 19:39:51 +00:00
|
|
|
if (!strncmp(topic + 2, SET_VALUE_TOPIC, set_value_length))
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
// Get the pin number
|
2023-12-31 19:39:51 +00:00
|
|
|
uint8_t pin = (topic[0] - '0') * 10 + (topic[1] - '0');
|
2023-12-28 05:46:39 +00:00
|
|
|
// Get the value
|
|
|
|
uint16_t value = atoi(payload);
|
|
|
|
// Set the value
|
2023-12-28 10:03:47 +00:00
|
|
|
card->setValue(pin, value);
|
2023-12-28 05:46:39 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Process a request state message
|
|
|
|
*
|
|
|
|
* @param topic The null terminated topic
|
|
|
|
* @param payload The null terminated payload
|
|
|
|
* @param topic_length The length of the topic
|
|
|
|
* @return True if the message is a request state message, false otherwise
|
|
|
|
*/
|
|
|
|
bool DigitalOutputIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length)
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
// Check if the topic is a request state topic
|
|
|
|
// The correct format is requeststate
|
|
|
|
// This mean that the topic must be request_state_length characters long
|
|
|
|
// The topic must be request_state_length characters long
|
2023-12-31 19:39:51 +00:00
|
|
|
if (topic_length != request_state_length)
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Check if the topic is requeststate
|
2023-12-31 19:39:51 +00:00
|
|
|
if (!strncmp(topic, REQUEST_STATE_TOPIC, request_state_length))
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
// Publish the state of all pins
|
|
|
|
publishDigitalOutputs();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Publish the state of all digital outputs
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::publishDigitalOutputs()
|
|
|
|
{
|
|
|
|
if (!digital_outputs_publish_enabled)
|
|
|
|
return;
|
|
|
|
for (int i = 0; i < 16; i++)
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
publishDigitalOutput(i);
|
|
|
|
}
|
2023-12-28 05:46:39 +00:00
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Publish the state and value of the specified digital output
|
|
|
|
*
|
|
|
|
* @param pin The pin to publish
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::publishDigitalOutput(uint8_t pin)
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
publishDigitalOutputState(pin);
|
|
|
|
publishDigitalOutputValue(pin);
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Publish the state of the specified digital output
|
|
|
|
*
|
|
|
|
* @param pin The pin to publish
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::publishDigitalOutputState(uint8_t pin)
|
|
|
|
{
|
|
|
|
if (!digital_outputs_publish_enabled)
|
|
|
|
return;
|
2023-12-28 06:14:18 +00:00
|
|
|
state_report_topic[0] = pin / 10 + '0';
|
|
|
|
state_report_topic[1] = pin % 10 + '0';
|
2023-12-28 10:03:47 +00:00
|
|
|
publishRelative(state_report_topic, card->getState(pin) ? "1" : "0");
|
2023-12-31 19:39:51 +00:00
|
|
|
}
|
2023-12-28 06:14:18 +00:00
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Publish the value of the specified digital output
|
|
|
|
*
|
|
|
|
* @param pin The pin to publish
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::publishDigitalOutputValue(uint8_t pin)
|
|
|
|
{
|
|
|
|
if (!digital_outputs_publish_enabled)
|
|
|
|
return;
|
2023-12-28 06:14:18 +00:00
|
|
|
value_report_topic[0] = pin / 10 + '0';
|
|
|
|
value_report_topic[1] = pin % 10 + '0';
|
|
|
|
char payload[5];
|
|
|
|
sprintf(payload, "%d", card->getValue(pin));
|
2023-12-28 10:03:47 +00:00
|
|
|
publishRelative(value_report_topic, payload);
|
2023-12-28 05:46:39 +00:00
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Enable/disable publishing of digital outputs
|
|
|
|
*
|
|
|
|
* @param enabled True to enable publishing, false to disable publishing
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::setDigitalOutputsPublishEnabled(bool enabled)
|
|
|
|
{
|
2023-12-28 05:46:39 +00:00
|
|
|
digital_outputs_publish_enabled = enabled;
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Handle the value change of a pin
|
|
|
|
*
|
|
|
|
* @note This function is registered as a callback function with the DigitalOutputCard object
|
|
|
|
*
|
|
|
|
* @param pin The pin that changed
|
|
|
|
* @param state The new state of the pin
|
|
|
|
* @param value The new value of the pin
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::handleValueChange(uint8_t pin, bool state, uint16_t value)
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
publishDigitalOutput(pin);
|
2023-12-28 05:46:39 +00:00
|
|
|
}
|
2023-12-28 06:14:18 +00:00
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Publish all digital outputs
|
|
|
|
*
|
|
|
|
* @note This function is called by the ESPMegaIoT object
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::publishReport()
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
publishDigitalOutputs();
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Get the type of the IoT component
|
|
|
|
*
|
|
|
|
* @return The type of the IoT component
|
|
|
|
*/
|
|
|
|
uint8_t DigitalOutputIoT::getType()
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
return CARD_TYPE_DIGITAL_OUTPUT;
|
2023-12-28 07:52:52 +00:00
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief Subscribe to the MQTT topics used by the DigitalOutputIoT object
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::subscribe()
|
|
|
|
{
|
2023-12-28 08:56:05 +00:00
|
|
|
char topic[20];
|
2023-12-28 07:52:52 +00:00
|
|
|
// Subscribe to all set state topics
|
2023-12-31 19:39:51 +00:00
|
|
|
for (int i = 0; i < 16; i++)
|
|
|
|
{
|
2023-12-28 07:52:52 +00:00
|
|
|
sprintf(topic, "%02d/set/state", i);
|
|
|
|
subscribeRelative(topic);
|
|
|
|
}
|
|
|
|
// Subscribe to all set value topics
|
2023-12-31 19:39:51 +00:00
|
|
|
for (int i = 0; i < 16; i++)
|
|
|
|
{
|
2023-12-28 07:52:52 +00:00
|
|
|
sprintf(topic, "%02d/set/value", i);
|
|
|
|
subscribeRelative(topic);
|
|
|
|
}
|
|
|
|
// Subscribe to request state topic
|
|
|
|
subscribeRelative(REQUEST_STATE_TOPIC);
|
|
|
|
// Subscribe to publish enable topic
|
|
|
|
subscribeRelative(PUBLISH_ENABLE_TOPIC);
|
|
|
|
}
|
|
|
|
|
2023-12-31 19:39:51 +00:00
|
|
|
/**
|
|
|
|
* @brief The main loop for the DigitalOutputIoT object
|
|
|
|
*
|
|
|
|
* @note This function is not used, it is only here to implement the IoTComponent interface
|
|
|
|
*/
|
|
|
|
void DigitalOutputIoT::loop()
|
|
|
|
{
|
2023-12-28 06:14:18 +00:00
|
|
|
}
|