document everything except InternalDisplay and IoT

This commit is contained in:
Siwat Sirichai 2024-01-01 02:39:51 +07:00
parent 4031b7555a
commit 789afc04dc
8 changed files with 398 additions and 74 deletions

View File

@ -1,5 +1,12 @@
#include <DigitalOutputCard.hpp> #include <DigitalOutputCard.hpp>
/**
* @brief Create a new Digital Output Card object with the specified address
*
* @note If you are using the ESPMegaI/O board, you should use the dip switch constructor
*
* @param address The ESPMegaI/O address of the card
*/
DigitalOutputCard::DigitalOutputCard(uint8_t address) : change_callbacks(){ DigitalOutputCard::DigitalOutputCard(uint8_t address) : change_callbacks(){
this->address = address; this->address = address;
// load default pin map // load default pin map
@ -10,14 +17,34 @@ DigitalOutputCard::DigitalOutputCard(uint8_t address) : change_callbacks(){
this->framBinded = false; this->framBinded = false;
this->callbacks_handler_index = 0; this->callbacks_handler_index = 0;
} }
// Instantiate the card with the specified position on the dip switch
/**
* @brief Create a new Digital Output Card object with the specified position on the dip switch
*
* @note The bit 0 are at the left of the dip switch
*
* @warning There are 5 switches on the dip switch, they should be unique accross all the cards
*
* @param bit0 The position of the first switch on the dip switch
* @param bit1 The position of the second switch on the dip switch
* @param bit2 The position of the third switch on the dip switch
* @param bit3 The position of the fourth switch on the dip switch
* @param bit4 The position of the fifth switch on the dip switch
*/
DigitalOutputCard::DigitalOutputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4) : DigitalOutputCard::DigitalOutputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4) :
DigitalOutputCard(0x20+bit0+bit1*2+bit2*4+bit3*8+bit4*16) DigitalOutputCard(0x20+bit0+bit1*2+bit2*4+bit3*8+bit4*16)
{ {
} }
// Initialize the card
/**
* @brief Initialize the Digital Output Card
*
* @note Although this function can be called inside the main program, it is recommended to use ESPMegaPRO::installCard() instead
*
* @return True if the initialization is successful, false otherwise
*/
bool DigitalOutputCard::begin() { bool DigitalOutputCard::begin() {
this->pwm = Adafruit_PWMServoDriver(this->address); this->pwm = Adafruit_PWMServoDriver(this->address);
this->pwm.begin(); this->pwm.begin();
@ -26,7 +53,15 @@ bool DigitalOutputCard::begin() {
// so we just return true // so we just return true
return true; return true;
} }
// Set the output to the specified state
/**
* @brief Write a digtal LOW or HIGH to the specified pin
*
* @note This function set both the state and the pwm value of the pin
*
* @param pin The pin to set the state
* @param state The logic level to set the pin to
*/
void DigitalOutputCard::digitalWrite(uint8_t pin, bool state) { void DigitalOutputCard::digitalWrite(uint8_t pin, bool state) {
this->pwm.setPin(virtualPinMap[pin], state ? 4095 : 0); this->pwm.setPin(virtualPinMap[pin], state ? 4095 : 0);
this->state_buffer[pin] = state; this->state_buffer[pin] = state;
@ -40,7 +75,15 @@ void DigitalOutputCard::digitalWrite(uint8_t pin, bool state) {
callback.second(pin, state, state ? 4095 : 0); callback.second(pin, state, state ? 4095 : 0);
} }
} }
// Set the output to the specified pwm value
/**
* @brief Write a pwm value to the specified pin
*
* @note This function set both the state and the pwm value of the pin
*
* @param pin The pin to set the pwm value
* @param value The pwm value to set
*/
void DigitalOutputCard::analogWrite(uint8_t pin, uint16_t value) { void DigitalOutputCard::analogWrite(uint8_t pin, uint16_t value) {
// If value is greater than 4095, set it to 4095 // If value is greater than 4095, set it to 4095
if (value > 4095) value = 4095; if (value > 4095) value = 4095;
@ -58,25 +101,49 @@ void DigitalOutputCard::analogWrite(uint8_t pin, uint16_t value) {
} }
} }
// Dummy loop function /**
* @brief The main loop for the Digital Output Card object.
*
* @note This function is not used, it is only here to implement the ExpansionCard interface
*/
void DigitalOutputCard::loop() { void DigitalOutputCard::loop() {
} }
// Get the state of the specified pin /**
* @brief Get the state of the specified pin
*
* @param pin The pin to get the state
* @return The state of the pin
*/
bool DigitalOutputCard::getState(uint8_t pin) { bool DigitalOutputCard::getState(uint8_t pin) {
return this->state_buffer[pin]; return this->state_buffer[pin];
} }
// Get the pwm value of the specified pin /**
* @brief Get the pwm value of the specified pin
*
* @param pin The pin to get the pwm value
* @return The pwm value of the pin
*/
uint16_t DigitalOutputCard::getValue(uint8_t pin) { uint16_t DigitalOutputCard::getValue(uint8_t pin) {
return this->value_buffer[pin]; return this->value_buffer[pin];
} }
// Get type of card /**
* @brief Get the type of the card
*
* @return The type of the card
*/
uint8_t DigitalOutputCard::getType() { uint8_t DigitalOutputCard::getType() {
return CARD_TYPE_DIGITAL_OUTPUT; return CARD_TYPE_DIGITAL_OUTPUT;
} }
/**
* @brief Set the state of the specified pin
*
* @param pin The pin to set the state
* @param state The state of the pin
*/
void DigitalOutputCard::setState(uint8_t pin, bool state) { void DigitalOutputCard::setState(uint8_t pin, bool state) {
this-> state_buffer[pin] = state; this-> state_buffer[pin] = state;
this->pwm.setPin(pin, state*value_buffer[pin]); this->pwm.setPin(pin, state*value_buffer[pin]);
@ -88,6 +155,12 @@ void DigitalOutputCard::setState(uint8_t pin, bool state) {
} }
} }
/**
* @brief Set the pwm value of the specified pin
*
* @param pin The pin to set the pwm value
* @param value The pwm value to set
*/
void DigitalOutputCard::setValue(uint8_t pin, uint16_t value) { void DigitalOutputCard::setValue(uint8_t pin, uint16_t value) {
// If value is greater than 4095, set it to 4095 // If value is greater than 4095, set it to 4095
if (value > 4095) value = 4095; if (value > 4095) value = 4095;
@ -102,15 +175,36 @@ void DigitalOutputCard::setValue(uint8_t pin, uint16_t value) {
} }
} }
/**
* @brief Register a callback function for the specified pin
*
* @param callback The callback function to be called, the first parameter is the pin, the second parameter is the state, the third parameter is the pwm value
* @return The handler of the callback function
*/
uint8_t DigitalOutputCard::registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback) { uint8_t DigitalOutputCard::registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback) {
this->change_callbacks[this->callbacks_handler_index] = callback; this->change_callbacks[this->callbacks_handler_index] = callback;
return this->callbacks_handler_index++; return this->callbacks_handler_index++;
} }
/**
* @brief Unregister a callback function
*
* @param handler The handler of the callback function to be unregistered
*/
void DigitalOutputCard::unregisterChangeCallback(uint8_t handler) { void DigitalOutputCard::unregisterChangeCallback(uint8_t handler) {
this->change_callbacks.erase(handler); this->change_callbacks.erase(handler);
} }
/**
* @brief Load a pin map
*
* A pin map is an array of 16 elements that maps the physical pins to virtual pins
* The virtual pins are the pins that are used in the callback functions and are used for all the functions in this class
* The physical pins are the pins on the Output IC, This can be found on the schematic of the ESPMegaI/O board
* This function is useful if you want to change the number identification of the pins to match your project needs
*
* @param pinMap The pin map to load
*/
void DigitalOutputCard::loadPinMap(uint8_t pinMap[16]) { void DigitalOutputCard::loadPinMap(uint8_t pinMap[16]) {
for(int i = 0; i < 16; i++) { for(int i = 0; i < 16; i++) {
this->pinMap[i] = pinMap[i]; this->pinMap[i] = pinMap[i];
@ -118,12 +212,27 @@ void DigitalOutputCard::loadPinMap(uint8_t pinMap[16]) {
} }
} }
/**
* @brief Bind a FRAM to the card
*
* @note The Output Card use 34 bytes of FRAM
*
* @warning If the fram range overlap with another card, undefined behavior will occur
*
* @param fram The FRAM to bind
* @param address The address of the card in the FRAM
*/
void DigitalOutputCard::bindFRAM(FRAM *fram, uint16_t address) { void DigitalOutputCard::bindFRAM(FRAM *fram, uint16_t address) {
this->fram = fram; this->fram = fram;
this->framBinded = true; this->framBinded = true;
this->framAddress = address; this->framAddress = address;
} }
/**
* @brief Pack the states of all the pins into a 16 bit integer
*
* @return The packed states
*/
uint16_t DigitalOutputCard::packStates() { uint16_t DigitalOutputCard::packStates() {
uint16_t packed = 0; uint16_t packed = 0;
for(int i = 0; i < 16; i++) { for(int i = 0; i < 16; i++) {
@ -132,12 +241,20 @@ uint16_t DigitalOutputCard::packStates() {
return packed; return packed;
} }
/**
* @brief Unpack the states of all the pins from a 16 bit integer
*
* @param states The packed states
*/
void DigitalOutputCard::unpackStates(uint16_t states) { void DigitalOutputCard::unpackStates(uint16_t states) {
for(int i = 0; i < 16; i++) { for(int i = 0; i < 16; i++) {
this->setState(i, (states >> i) & 1); this->setState(i, (states >> i) & 1);
} }
} }
/**
* @brief Save the states and values of all the pins to the FRAM
*/
void DigitalOutputCard::saveToFRAM() { void DigitalOutputCard::saveToFRAM() {
if(!framBinded) return; if(!framBinded) return;
// Save the state // Save the state
@ -147,6 +264,9 @@ void DigitalOutputCard::saveToFRAM() {
this->fram->write(framAddress+2, (uint8_t*)value_buffer, 32); this->fram->write(framAddress+2, (uint8_t*)value_buffer, 32);
} }
/**
* @brief Load the states and values of all the pins from the FRAM
*/
void DigitalOutputCard::loadFromFRAM() { void DigitalOutputCard::loadFromFRAM() {
if(!framBinded) return; if(!framBinded) return;
// Load the state // Load the state
@ -160,15 +280,28 @@ void DigitalOutputCard::loadFromFRAM() {
} }
} }
/**
* @brief Set the auto save to FRAM
*
* @param autoSave True to enable auto save, false to disable auto save
*/
void DigitalOutputCard::setAutoSaveToFRAM(bool autoSave) { void DigitalOutputCard::setAutoSaveToFRAM(bool autoSave) {
this->framAutoSave = autoSave; this->framAutoSave = autoSave;
} }
/**
* @brief Save a single pin value to FRAM
*
* @param pin The pin to save
*/
void DigitalOutputCard::savePinValueToFRAM(uint8_t pin) { void DigitalOutputCard::savePinValueToFRAM(uint8_t pin) {
if(!framBinded) return; if(!framBinded) return;
this->fram->write(framAddress+2+pin*2, (uint8_t*)&value_buffer[pin], 2); this->fram->write(framAddress+2+pin*2, (uint8_t*)&value_buffer[pin], 2);
} }
/**
* @brief Save the states of all the pins to FRAM
*/
void DigitalOutputCard::saveStateToFRAM() { void DigitalOutputCard::saveStateToFRAM() {
if(!framBinded) return; if(!framBinded) return;
uint16_t packed = packStates(); uint16_t packed = packStates();

View File

@ -13,6 +13,7 @@
// Publish all topic: requeststate payload: N/A // Publish all topic: requeststate payload: N/A
// Enable/disable publish topic: publish_enable payload: 0/1 // Enable/disable publish topic: publish_enable payload: 0/1
// MQTT Topics
#define SET_STATE_TOPIC "/set/state" #define SET_STATE_TOPIC "/set/state"
#define SET_VALUE_TOPIC "/set/value" #define SET_VALUE_TOPIC "/set/value"
#define STATE_TOPIC "/state" #define STATE_TOPIC "/state"
@ -20,8 +21,20 @@
#define REQUEST_STATE_TOPIC "requeststate" #define REQUEST_STATE_TOPIC "requeststate"
#define PUBLISH_ENABLE_TOPIC "publish_enable" #define PUBLISH_ENABLE_TOPIC "publish_enable"
// Card type
#define CARD_TYPE_DIGITAL_OUTPUT 0x00 #define CARD_TYPE_DIGITAL_OUTPUT 0x00
/**
* @brief The DigitalOutputCard class is a class for controlling the Digital Output Card.
*
* This Digital Output Card has 16 digital outputs.
* All outputs are PWM capable.
* ALl outputs are 12V Push-Pull outputs.
* Outputs is grouped in 4 groups of 4 outputs.(0-3, 4-7, 8-11, 12-15)
* Each pin is capable of 0.6A, however each group's total current is limited to 1.2A.
*
* @warning You should not instantiate this class directly, instead use ESPMegaIO's registerCard function.
*/
class DigitalOutputCard : public ExpansionCard class DigitalOutputCard : public ExpansionCard
{ {
public: public:

View File

@ -1,22 +1,42 @@
#include <DigitalOutputIoT.hpp> #include <DigitalOutputIoT.hpp>
DigitalOutputIoT::DigitalOutputIoT() { /**
* @brief Create a new DigitalOutputIoT object
*/
DigitalOutputIoT::DigitalOutputIoT()
{
this->state_report_topic = new char[10]; this->state_report_topic = new char[10];
this->value_report_topic = new char[10]; this->value_report_topic = new char[10];
this->digital_outputs_publish_enabled = true; this->digital_outputs_publish_enabled = true;
} }
DigitalOutputIoT::~DigitalOutputIoT() { /**
* @brief Destroy the DigitalOutputIoT object
*/
DigitalOutputIoT::~DigitalOutputIoT()
{
delete[] this->state_report_topic; delete[] this->state_report_topic;
delete[] this->value_report_topic; delete[] this->value_report_topic;
} }
bool DigitalOutputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) { /**
* @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)
{
ESP_LOGD("DigitalOutputIoT", "Beginning DigitalOutputIoT"); ESP_LOGD("DigitalOutputIoT", "Beginning DigitalOutputIoT");
this->mqtt = mqtt; this->mqtt = mqtt;
this->base_topic = base_topic; this->base_topic = base_topic;
this->card = (DigitalOutputCard *) card; this->card = (DigitalOutputCard *)card;
this-> card_id = card_id; this->card_id = card_id;
this->set_state_length = strlen(SET_STATE_TOPIC); this->set_state_length = strlen(SET_STATE_TOPIC);
this->set_value_length = strlen(SET_VALUE_TOPIC); this->set_value_length = strlen(SET_VALUE_TOPIC);
this->state_length = strlen(STATE_TOPIC); this->state_length = strlen(STATE_TOPIC);
@ -27,51 +47,78 @@ bool DigitalOutputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient
strcpy(this->value_report_topic, "00/value"); strcpy(this->value_report_topic, "00/value");
ESP_LOGV("DigitalOutputIoT", "Registering callbacks inside DigitalOutputIoT::begin"); ESP_LOGV("DigitalOutputIoT", "Registering callbacks inside DigitalOutputIoT::begin");
// Register callbacks // Register callbacks
auto bindedCallback = std::bind(&DigitalOutputIoT::handleValueChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); auto bindedCallback = std::bind(&DigitalOutputIoT::handleValueChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->card->registerChangeCallback(bindedCallback); this->card->registerChangeCallback(bindedCallback);
ESP_LOGV("DigitalOutputIoT", "DigitalOutputIoT::begin complete"); ESP_LOGV("DigitalOutputIoT", "DigitalOutputIoT::begin complete");
return true; return true;
} }
// Protocol for digital output card /**
// Note that pin is always 2 characters long and padded with 0 if necessary * @brief Handle the MQTT messages for the DigitalOutputIoT card
// Set pin state topic: <pin>/set/state payload: 0/1 *
// Set pin pwm topic: <pin>/set/value payload: 0-4095 * @param topic The topic of the message
// Publish pin state topic: <pin>/state payload: 0/1 * @param payload The payload of the message
// Publish pin pwm topic: <pin>/value payload: 0-4095 */
// Publish all topic: requeststate payload: N/A void DigitalOutputIoT::handleMqttMessage(char *topic, char *payload)
// Enable/disable publish topic: publish_enable payload: 0/1 {
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
uint8_t topic_length = strlen(topic); uint8_t topic_length = strlen(topic);
if(this-> processSetStateMessage(topic, payload, topic_length)) return; if (this->processSetStateMessage(topic, payload, topic_length))
if(this-> processSetValueMessage(topic, payload, topic_length)) return; return;
if(this-> processRequestStateMessage(topic, payload, topic_length)) return; if (this->processSetValueMessage(topic, payload, topic_length))
return;
if (this->processRequestStateMessage(topic, payload, topic_length))
return;
} }
bool DigitalOutputIoT::processSetStateMessage(char *topic, char *payload, uint8_t topic_length) { /**
* @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)
{
// Check if the topic is a set state topic // Check if the topic is a set state topic
// The correct format is <pin>/set/state // The correct format is <pin>/set/state
// This mean that the topic must end with /set/state // This mean that the topic must end with /set/state
// Check if the 3rd character is / // Check if the 3rd character is /
if(topic[2] != '/') { if (topic[2] != '/')
{
return false; return false;
} }
// The topic must be set_state_length + 2 characters long // The topic must be set_state_length + 2 characters long
if(topic_length != set_state_length + 2) { if (topic_length != set_state_length + 2)
{
return false; return false;
} }
// Check if the topic ends with /set/state // Check if the topic ends with /set/state
if (!strncmp(topic+2, SET_STATE_TOPIC, set_state_length)) { if (!strncmp(topic + 2, SET_STATE_TOPIC, set_state_length))
{
// Get the pin number // Get the pin number
uint8_t pin = (topic[0] - '0')*10 + (topic[1] - '0'); uint8_t pin = (topic[0] - '0') * 10 + (topic[1] - '0');
// Get the state // Get the state
bool state = false; bool state = false;
char state_char = payload[0]; char state_char = payload[0];
if (state_char == '0') { if (state_char == '0')
{
state = false; state = false;
} else if (state_char == '1') { }
else if (state_char == '1')
{
state = true; state = true;
} else { }
else
{
return false; return false;
} }
// Set the state // Set the state
@ -81,22 +128,34 @@ bool DigitalOutputIoT::processSetStateMessage(char *topic, char *payload, uint8_
return false; return false;
} }
bool DigitalOutputIoT::processSetValueMessage(char *topic, char *payload, uint8_t topic_length) { /**
* @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)
{
// Check if the topic is a set value topic // Check if the topic is a set value topic
// The correct format is <pin>/set/value // The correct format is <pin>/set/value
// This mean that the topic must end with /set/value // This mean that the topic must end with /set/value
// Check if the 3rd character is / // Check if the 3rd character is /
if(topic[2] != '/') { if (topic[2] != '/')
{
return false; return false;
} }
// The topic must be set_value_length + 2 characters long // The topic must be set_value_length + 2 characters long
if(topic_length != set_value_length + 2) { if (topic_length != set_value_length + 2)
{
return false; return false;
} }
// Check if the topic ends with /set/value // Check if the topic ends with /set/value
if (!strncmp(topic+2, SET_VALUE_TOPIC, set_value_length)) { if (!strncmp(topic + 2, SET_VALUE_TOPIC, set_value_length))
{
// Get the pin number // Get the pin number
uint8_t pin = (topic[0] - '0')*10 + (topic[1] - '0'); uint8_t pin = (topic[0] - '0') * 10 + (topic[1] - '0');
// Get the value // Get the value
uint16_t value = atoi(payload); uint16_t value = atoi(payload);
// Set the value // Set the value
@ -106,16 +165,27 @@ bool DigitalOutputIoT::processSetValueMessage(char *topic, char *payload, uint8_
return false; return false;
} }
bool DigitalOutputIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) { /**
* @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)
{
// Check if the topic is a request state topic // Check if the topic is a request state topic
// The correct format is requeststate // The correct format is requeststate
// This mean that the topic must be request_state_length characters long // This mean that the topic must be request_state_length characters long
// The topic must be request_state_length characters long // The topic must be request_state_length characters long
if(topic_length != request_state_length) { if (topic_length != request_state_length)
{
return false; return false;
} }
// Check if the topic is requeststate // Check if the topic is requeststate
if (!strncmp(topic, REQUEST_STATE_TOPIC, request_state_length)) { if (!strncmp(topic, REQUEST_STATE_TOPIC, request_state_length))
{
// Publish the state of all pins // Publish the state of all pins
publishDigitalOutputs(); publishDigitalOutputs();
return true; return true;
@ -123,27 +193,53 @@ bool DigitalOutputIoT::processRequestStateMessage(char *topic, char *payload, ui
return false; return false;
} }
void DigitalOutputIoT::publishDigitalOutputs() { /**
if(!digital_outputs_publish_enabled) return; * @brief Publish the state of all digital outputs
for(int i = 0; i < 16; i++) { */
void DigitalOutputIoT::publishDigitalOutputs()
{
if (!digital_outputs_publish_enabled)
return;
for (int i = 0; i < 16; i++)
{
publishDigitalOutput(i); publishDigitalOutput(i);
} }
} }
void DigitalOutputIoT::publishDigitalOutput(uint8_t pin) { /**
* @brief Publish the state and value of the specified digital output
*
* @param pin The pin to publish
*/
void DigitalOutputIoT::publishDigitalOutput(uint8_t pin)
{
publishDigitalOutputState(pin); publishDigitalOutputState(pin);
publishDigitalOutputValue(pin); publishDigitalOutputValue(pin);
} }
void DigitalOutputIoT::publishDigitalOutputState(uint8_t pin) { /**
if(!digital_outputs_publish_enabled) return; * @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;
state_report_topic[0] = pin / 10 + '0'; state_report_topic[0] = pin / 10 + '0';
state_report_topic[1] = pin % 10 + '0'; state_report_topic[1] = pin % 10 + '0';
publishRelative(state_report_topic, card->getState(pin) ? "1" : "0"); publishRelative(state_report_topic, card->getState(pin) ? "1" : "0");
} }
void DigitalOutputIoT::publishDigitalOutputValue(uint8_t pin) { /**
if(!digital_outputs_publish_enabled) return; * @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;
value_report_topic[0] = pin / 10 + '0'; value_report_topic[0] = pin / 10 + '0';
value_report_topic[1] = pin % 10 + '0'; value_report_topic[1] = pin % 10 + '0';
char payload[5]; char payload[5];
@ -151,42 +247,65 @@ void DigitalOutputIoT::publishDigitalOutputValue(uint8_t pin) {
publishRelative(value_report_topic, payload); publishRelative(value_report_topic, payload);
} }
void DigitalOutputIoT::setDigitalOutputsPublishEnabled(bool enabled) { /**
* @brief Enable/disable publishing of digital outputs
*
* @param enabled True to enable publishing, false to disable publishing
*/
void DigitalOutputIoT::setDigitalOutputsPublishEnabled(bool enabled)
{
digital_outputs_publish_enabled = enabled; digital_outputs_publish_enabled = enabled;
} }
void DigitalOutputIoT::handleValueChange(uint8_t pin, bool state, uint16_t value) { /**
* @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)
{
publishDigitalOutput(pin); publishDigitalOutput(pin);
if(value_change_callback != NULL) {
value_change_callback(pin, state, value);
}
} }
void DigitalOutputIoT::registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback) { /**
value_change_callback = callback; * @brief Publish all digital outputs
} *
* @note This function is called by the ESPMegaIoT object
void DigitalOutputIoT::unregisterChangeCallback() { */
value_change_callback = NULL; void DigitalOutputIoT::publishReport()
} {
void DigitalOutputIoT::publishReport() {
publishDigitalOutputs(); publishDigitalOutputs();
} }
uint8_t DigitalOutputIoT::getType() { /**
* @brief Get the type of the IoT component
*
* @return The type of the IoT component
*/
uint8_t DigitalOutputIoT::getType()
{
return CARD_TYPE_DIGITAL_OUTPUT; return CARD_TYPE_DIGITAL_OUTPUT;
} }
void DigitalOutputIoT::subscribe() { /**
* @brief Subscribe to the MQTT topics used by the DigitalOutputIoT object
*/
void DigitalOutputIoT::subscribe()
{
char topic[20]; char topic[20];
// Subscribe to all set state topics // Subscribe to all set state topics
for(int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++)
{
sprintf(topic, "%02d/set/state", i); sprintf(topic, "%02d/set/state", i);
subscribeRelative(topic); subscribeRelative(topic);
} }
// Subscribe to all set value topics // Subscribe to all set value topics
for(int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++)
{
sprintf(topic, "%02d/set/value", i); sprintf(topic, "%02d/set/value", i);
subscribeRelative(topic); subscribeRelative(topic);
} }
@ -196,5 +315,11 @@ void DigitalOutputIoT::subscribe() {
subscribeRelative(PUBLISH_ENABLE_TOPIC); subscribeRelative(PUBLISH_ENABLE_TOPIC);
} }
void DigitalOutputIoT::loop() { /**
* @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()
{
} }

View File

@ -3,6 +3,11 @@
#include <ExpansionCard.hpp> #include <ExpansionCard.hpp>
#include <DigitalOutputCard.hpp> #include <DigitalOutputCard.hpp>
/**
* @brief The DigitalOutputIoT class is a class interfacing with the Digital Output Card through MQTT.
*
* @warning You should not instantiate this class directly, instead use ESPMegaIO's registerCard function.
*/
class DigitalOutputIoT : public IoTComponent { class DigitalOutputIoT : public IoTComponent {
public: public:
DigitalOutputIoT(); DigitalOutputIoT();
@ -15,8 +20,6 @@ class DigitalOutputIoT : public IoTComponent {
void publishDigitalOutputValue(uint8_t pin); void publishDigitalOutputValue(uint8_t pin);
void setDigitalOutputsPublishEnabled(bool enabled); void setDigitalOutputsPublishEnabled(bool enabled);
void handleValueChange(uint8_t pin, bool state, uint16_t value); void handleValueChange(uint8_t pin, bool state, uint16_t value);
void registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
void unregisterChangeCallback();
void publishReport(); void publishReport();
void subscribe(); void subscribe();
void loop(); void loop();
@ -26,7 +29,6 @@ class DigitalOutputIoT : public IoTComponent {
bool processSetStateMessage(char *topic, char *payload, uint8_t topic_length); bool processSetStateMessage(char *topic, char *payload, uint8_t topic_length);
bool processSetValueMessage(char *topic, char *payload, uint8_t topic_length); bool processSetValueMessage(char *topic, char *payload, uint8_t topic_length);
bool processRequestStateMessage(char *topic, char *payload, uint8_t topic_length); bool processRequestStateMessage(char *topic, char *payload, uint8_t topic_length);
std::function<void(uint8_t, bool, uint16_t)> value_change_callback;
DigitalOutputCard *card; DigitalOutputCard *card;
char *state_report_topic; char *state_report_topic;
char *value_report_topic; char *value_report_topic;

View File

@ -36,6 +36,7 @@ bool ESPMegaDisplay::recieveSerialCommand()
/** /**
* @brief Processes the received serial command. * @brief Processes the received serial command.
* @note This function interacts directly with the rx_buffer.
*/ */
void ESPMegaDisplay::processSerialCommand() void ESPMegaDisplay::processSerialCommand()
{ {
@ -62,6 +63,7 @@ void ESPMegaDisplay::processSerialCommand()
/** /**
* @brief Processes the touch event payload. * @brief Processes the touch event payload.
* @note This function interacts directly with the rx_buffer.
*/ */
void ESPMegaDisplay::processTouchPayload() void ESPMegaDisplay::processTouchPayload()
{ {
@ -83,6 +85,7 @@ void ESPMegaDisplay::processTouchPayload()
/** /**
* @brief Processes the page report event payload. * @brief Processes the page report event payload.
* @note This function interacts directly with the rx_buffer.
*/ */
void ESPMegaDisplay::processPageReportPayload() void ESPMegaDisplay::processPageReportPayload()
{ {
@ -161,6 +164,8 @@ void ESPMegaDisplay::setString(const char *component, const char *value)
/** /**
* @brief Gets the value of a number component from the display. * @brief Gets the value of a number component from the display.
* @warning This function is blocking.
* @warning If the display does not respond or is not connected, this function will block for up to DISPLAY_FETCH_RETRY_COUNT * DISPLAY_FETCH_TIMEOUT milliseconds.
* @param component The component name. * @param component The component name.
* @return The value of the number component. * @return The value of the number component.
*/ */
@ -219,6 +224,8 @@ uint32_t ESPMegaDisplay::getNumber(const char *component)
/** /**
* @brief Gets the value of a string component from the display. * @brief Gets the value of a string component from the display.
* @warning This function is blocking.
* @warning If the display does not respond or is not connected, this function will block for up to DISPLAY_FETCH_RETRY_COUNT * DISPLAY_FETCH_TIMEOUT milliseconds.
* @param component The component name. * @param component The component name.
* @return The value of the string component. * @return The value of the string component.
* @note The returned char array must be freed after use. * @note The returned char array must be freed after use.
@ -275,6 +282,15 @@ const char *ESPMegaDisplay::getString(const char *component)
return value; return value;
} }
/**
* @brief Gets the value of a number component from the display and stores it in a buffer.
* @warning This function is blocking.
* @warning If the display does not respond or is not connected, this function will block for up to DISPLAY_FETCH_RETRY_COUNT * DISPLAY_FETCH_TIMEOUT milliseconds.
* @param component The component name.
* @param buffer The buffer to store the value.
* @param buffer_size The size of the buffer.
* @return True if the value is successfully stored in the buffer, false otherwise.
*/
bool ESPMegaDisplay::getStringToBuffer(const char *component, char *buffer, uint8_t buffer_size) bool ESPMegaDisplay::getStringToBuffer(const char *component, char *buffer, uint8_t buffer_size)
{ {
// We might be in the middle of a serial command // We might be in the middle of a serial command

View File

@ -5,6 +5,12 @@
#define DISPLAY_FETCH_TIMEOUT 100 // ms #define DISPLAY_FETCH_TIMEOUT 100 // ms
#define DISPLAY_FETCH_RETRY_COUNT 5 #define DISPLAY_FETCH_RETRY_COUNT 5
/**
* @brief The ESPMegaDisplay class is a class for controlling the ESPMegaDisplay.
*
* @note The ESPMegaDisplay is a UART controlled display.
* @note Connect the Display's TX pin to the ESPMega's RX pin and the Display's RX pin to the ESPMega's TX pin.
*/
class ESPMegaDisplay class ESPMegaDisplay
{ {
public: public:

View File

@ -1,6 +1,17 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
/**
* @brief The base class for all expansion cards
*
* In order to create a new expansion card, you should create a new class that inherits from this class.
* Your class should implement the following functions:
* - begin() : Initialize the card
* - loop() : A function that is called in the main loop
* - getType() : Get the type of the card, The type should be a unique number between 0 and 255
*
* @warning This class is abstract and should not be instantiated directly.
*/
class ExpansionCard { class ExpansionCard {
public: public:
// Instantiate the card with the specified address // Instantiate the card with the specified address

View File

@ -2,6 +2,25 @@
#include <ExpansionCard.hpp> #include <ExpansionCard.hpp>
#include <PubSubClient.h> #include <PubSubClient.h>
#include <esp_log.h> #include <esp_log.h>
/**
* @brief The IoTComponent class is a base class that is used to interface with an expansion card through MQTT.
*
* In order to create a new IoTComponent, you should create a new class that inherits from this class.
* Your class should implement the following functions:
* - begin() : Initialize the component, record the card id, ExpansionCard object, the PubSubClient object and the base topic
* - handleMqttMessage() : Handle the MQTT messages for the component
* - publishReport() : Publish all the reports for the component
* - getType() : Get the type of the component, This should return the underlying ExpansionCard type
* - subscribe() : Subscribe to the MQTT topics used by the component
* - loop() : A function that is called in the main loop
*
* Additionally, the inherited class will have access to these helper functions:
* - publishRelative() : Publish a message to a topic relative to the base topic and the card id
* - subscribeRelative() : Subscribe to a topic relative to the base topic and the card id
*
* @warning This class is abstract and should not be instantiated directly.
*/
class IoTComponent { class IoTComponent {
public: public:
virtual bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic); virtual bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
@ -10,7 +29,6 @@ class IoTComponent {
virtual void publishReport(); virtual void publishReport();
virtual uint8_t getType(); virtual uint8_t getType();
virtual void subscribe(); virtual void subscribe();
void loop(); void loop();
protected: protected:
char *base_topic; char *base_topic;