half comment done

This commit is contained in:
Siwat Sirichai 2024-01-01 01:38:25 +07:00
parent d62ffa3606
commit 039e65e6df
12 changed files with 736 additions and 38 deletions

View File

@ -1,6 +1,14 @@
/**
* @file AnalogCard.cpp
* @brief Implementation of the AnalogCard class.
*/
#include <AnalogCard.hpp>
#include "esp_log.h"
/**
* @brief Default constructor for the AnalogCard class.
*/
AnalogCard::AnalogCard() : dac0(DAC0_ADDRESS),
dac1(DAC1_ADDRESS),
dac2(DAC2_ADDRESS),
@ -12,6 +20,11 @@ AnalogCard::AnalogCard() : dac0(DAC0_ADDRESS),
this->handler_count = 0;
}
/**
* @brief Writes a value to the specified DAC pin.
* @param pin The DAC pin to write to.
* @param value The value to write.
*/
void AnalogCard::dacWrite(uint8_t pin, uint16_t value)
{
ESP_LOGV("AnalogCard", "DAC Write: %d, %d", pin, value);
@ -19,6 +32,11 @@ void AnalogCard::dacWrite(uint8_t pin, uint16_t value)
this->setDACValue(pin, value);
}
/**
* @brief Sets the state of the specified DAC pin.
* @param pin The DAC pin to set the state of.
* @param state The state to set (true = on, false = off).
*/
void AnalogCard::setDACState(uint8_t pin, bool state)
{
ESP_LOGD("AnalogCard", "Setting DAC state: %d, %d", pin, state);
@ -30,6 +48,11 @@ void AnalogCard::setDACState(uint8_t pin, bool state)
}
}
/**
* @brief Sets the value of the specified DAC pin.
* @param pin The DAC pin to set the value of.
* @param value The value to set.
*/
void AnalogCard::setDACValue(uint8_t pin, uint16_t value)
{
ESP_LOGD("AnalogCard", "Setting DAC value: %d, %d", pin, value);
@ -40,18 +63,33 @@ void AnalogCard::setDACValue(uint8_t pin, uint16_t value)
callback.second(pin, this->dac_state[pin], value);
}
}
/**
* @brief Gets the value of the specified DAC pin.
* @param pin The DAC pin to get the value of.
* @return The value of the DAC pin.
*/
uint16_t AnalogCard::getDACValue(uint8_t pin)
{
return this->dac_value[pin];
}
/**
* @brief Gets the state of the specified DAC pin.
* @param pin The DAC pin to get the state of.
* @return The state of the DAC pin (true = on, false = off).
*/
bool AnalogCard::getDACState(uint8_t pin)
{
return this->dac_state[pin];
}
/**
* @brief Sends data to the specified DAC pin.
* @param pin The DAC pin to send data to.
* @param value The data to send.
* @note This function does not call the DAC change callbacks.
*/
void AnalogCard::sendDataToDAC(uint8_t pin, uint16_t value)
{
switch (pin)
@ -70,6 +108,12 @@ void AnalogCard::sendDataToDAC(uint8_t pin, uint16_t value)
break;
}
}
/**
* @brief Reads the value from the specified analog pin.
* @param pin The analog pin to read from.
* @return The value read from the analog pin.
*/
uint16_t AnalogCard::analogRead(uint8_t pin)
{
if (pin >= 0 && pin <= 3)
@ -82,6 +126,11 @@ uint16_t AnalogCard::analogRead(uint8_t pin)
}
return 65535;
}
/**
* @brief Initializes the AnalogCard.
* @return True if initialization is successful, false otherwise.
*/
bool AnalogCard::begin()
{
if (!this->dac0.begin())
@ -117,21 +166,38 @@ bool AnalogCard::begin()
return true;
}
/**
* @brief The main loop of the AnalogCard.
* @note This function does nothing.
*/
void AnalogCard::loop()
{
}
/**
* @brief Gets the type of the AnalogCard.
* @return The type of the AnalogCard.
*/
uint8_t AnalogCard::getType()
{
return CARD_TYPE_ANALOG;
}
/**
* @brief Registers a callback function to be called when the state or value of a DAC pin changes.
* @param callback The callback function to register.
* @return The handler ID of the registered callback.
*/
uint8_t AnalogCard::registerDACChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback)
{
this->dac_change_callbacks[this->handler_count] = callback;
return this->handler_count++;
}
/**
* @brief Unregisters a previously registered DAC change callback.
* @param handler The handler ID of the callback to unregister.
*/
void AnalogCard::unregisterDACChangeCallback(uint8_t handler)
{
this->dac_change_callbacks.erase(handler);

View File

@ -5,8 +5,10 @@
#include <vector>
#include <map>
// Analog Card
#define CARD_TYPE_ANALOG 0x02
// Analog Card FRAM Address
#define ANALOG_INPUT_BANK_A_ADDRESS 0x48
#define ANALOG_INPUT_BANK_B_ADDRESS 0x49
#define DAC0_ADDRESS 0x60
@ -31,6 +33,7 @@ class AnalogCard : public ExpansionCard {
uint8_t getType();
private:
uint8_t handler_count;
// Map of handler IDs to callback functions
std::map<uint8_t, std::function<void(uint8_t, bool, uint16_t)>> dac_change_callbacks;
bool dac_state[4];
uint16_t dac_value[4];

View File

@ -1,5 +1,19 @@
/**
* @file AnalogIoT.cpp
* @brief Implementation of the AnalogIoT class.
*
* This file contains the implementation of the AnalogIoT class, which provides functionality for handling analog input and output operations in an IoT system.
* The class allows for setting the state and value of digital-to-analog converters (DACs), as well as reading the value of analog-to-digital converters (ADCs).
* It also supports publishing the state and value of DACs and ADCs over MQTT.
*/
#include <AnalogIoT.hpp>
/**
* @brief Default constructor for the AnalogIoT class.
*
* This constructor initializes the AnalogIoT object and sets up the ADC conversion callbacks.
*/
AnalogIoT::AnalogIoT() : adc_conversion_callbacks() {
for (uint8_t i = 0; i < 8; i++) {
adc_publish_enabled[i] = false;
@ -8,8 +22,23 @@ AnalogIoT::AnalogIoT() : adc_conversion_callbacks() {
this->adc_conversion_callback_index = 0;
}
/**
* @brief Default destructor for the AnalogIoT class.
*/
AnalogIoT::~AnalogIoT() {
this->adc_conversion_callbacks.clear();
}
/**
* @brief Initializes the AnalogIoT object.
* @param card_id The ID of the card.
* @param card A pointer to the card object.
* @param mqtt A pointer to the MQTT client object.
* @param base_topic The base MQTT topic.
* @return True if the initialization was successful, false otherwise.
* @note This function can be called from the main program but it is recommended to use ESPMegaIoT to initialize the IoT Components.
* This function initializes the AnalogIoT object and registers the callbacks for handling DAC changes.
*/
bool AnalogIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->mqtt = mqtt;
this->base_topic = base_topic;
@ -26,6 +55,11 @@ bool AnalogIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt,
this->card->registerDACChangeCallback(bindedCallback);
return true;
}
/**
* @brief Publishes the state of all DACs.
* @note This function is called when a request state message is received.
*/
void AnalogIoT::handleMqttMessage(char *topic, char *payload){
uint8_t topic_length = strlen(topic);
if(this-> processDACSetStateMessage(topic, payload, topic_length)) return;
@ -34,11 +68,20 @@ void AnalogIoT::handleMqttMessage(char *topic, char *payload){
if(this-> processADCSetConversionIntervalMessage(topic, payload, topic_length)) return;
if(this-> processADCSetConversionEnabledMessage(topic, payload, topic_length)) return;
}
/**
* @brief Publishes the state of all DACs.
*/
void AnalogIoT::publishADCs() {
for (uint8_t i = 0; i < 8; i++) {
this->publishADC(i);
}
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishADC(uint8_t pin) {
if (this->adc_publish_enabled[pin]) {
uint16_t value = this->card->analogRead(pin);
@ -55,29 +98,71 @@ void AnalogIoT::publishADC(uint8_t pin) {
}
}
}
/**
* @brief Sets the interval at which the state of all DACs is published.
* @param interval The interval in milliseconds.
*/
void AnalogIoT::setADCsPublishInterval(uint32_t interval) {
for (uint8_t i = 0; i < 8; i++) {
adc_conversion_interval[i] = interval;
}
}
/**
* @brief Sets whether the state of all DACs is published.
* @param enabled True if the state of all DACs should be published, false otherwise.
*/
void AnalogIoT::setADCsPublishEnabled(bool enabled) {
for (uint8_t i = 0; i < 8; i++) {
adc_publish_enabled[i] = enabled;
}
}
/**
* @brief Registers a callback for handling ADC conversions.
* @param callback The callback function.
* @return The handler of the callback.
*/
uint8_t AnalogIoT::registerADCConversionCallback(std::function<void(uint8_t, uint16_t)> callback) {
this->adc_conversion_callbacks[this->adc_conversion_callback_index] = callback;
return this->adc_conversion_callback_index++;
}
/**
* @brief Unregisters a callback for handling ADC conversions.
* @param handler The handler of the callback.
*/
void AnalogIoT::unregisterADCConversionCallback(uint8_t handler) {
this->adc_conversion_callbacks.erase(handler);
}
/**
* @brief Sets the interval at which the value of an ADC channel is read.
* @param pin The pin of the ADC channel.
* @param interval The interval in milliseconds.
*/
void AnalogIoT::setADCConversionInterval(uint8_t pin, uint16_t interval) {
adc_conversion_interval[pin] = interval;
}
/**
* @brief Enables or disables the periodic reading of the value of an ADC channel.
* @param pin The pin of the ADC channel.
* @param enabled True if the value of the ADC channel should be read, false otherwise.
*/
void AnalogIoT::setADCConversionEnabled(uint8_t pin, bool enabled) {
adc_publish_enabled[pin] = enabled;
}
/**
* @brief Processes a message received on the MQTT topic for setting the state of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processADCSetConversionIntervalMessage(char *topic, char *payload, uint8_t topic_length) {
// TODO: Process payload matching the criteria
// Topic: adc/<%02d>/set/conversion_interval
@ -102,6 +187,15 @@ bool AnalogIoT::processADCSetConversionIntervalMessage(char *topic, char *payloa
this->setADCConversionInterval(pin, interval);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for setting the value of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processADCSetConversionEnabledMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: adc/<%02d>/set/conversion_enabled
// The first 4 characters are "adc/"
@ -125,6 +219,15 @@ bool AnalogIoT::processADCSetConversionEnabledMessage(char *topic, char *payload
this->setADCConversionEnabled(pin, enabled);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for setting the state of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processDACSetStateMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: dac/<%02d>/set/state
// The first 4 characters are "dac/"
@ -148,6 +251,15 @@ bool AnalogIoT::processDACSetStateMessage(char *topic, char *payload, uint8_t to
this->card->setDACState(pin, state);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for setting the value of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processDACSetValueMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: dac/<%02d>/set/value
// The first 4 characters are "dac/"
@ -171,6 +283,15 @@ bool AnalogIoT::processDACSetValueMessage(char *topic, char *payload, uint8_t to
this->card->setDACValue(pin, value);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for requesting the state of all DACs.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: requeststate
// The length of the topic must be 12 characters
@ -187,6 +308,11 @@ bool AnalogIoT::processRequestStateMessage(char *topic, char *payload, uint8_t t
this->publishADCs();
return false;
}
/**
* @brief Subscribes to all MQTT topics used by the AnalogIoT object.
* @note This function is called when the MQTT client connects.
*/
void AnalogIoT::subscribe() {
// There are 4 DACs and 8 ADCs
// DACs: dac/<%02d>/set/state, dac/<%02d>/set/value, dac/publish_enable
@ -226,23 +352,45 @@ void AnalogIoT::loop() {
}
}
}
/**
* @brief Publishes the state of all DACs.
*/
void AnalogIoT::publishReport() {
publishADCs();
publishDACs();
}
/**
* @brief Gets the type of the card.
* @return The type of the card.
*/
uint8_t AnalogIoT::getType() {
return CARD_TYPE_ANALOG;
}
/**
* @brief Publishes the state of all DACs.
*/
void AnalogIoT::publishDACs() {
for (uint8_t i = 0; i < 4; i++) {
this->publishDAC(i);
}
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishDAC(uint8_t pin) {
this->publishDACState(pin);
this->publishDACValue(pin);
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishDACState(uint8_t pin) {
char *topic = new char[15];
sprintf(topic, "dac/%02d/state", pin);
@ -252,6 +400,11 @@ void AnalogIoT::publishDACState(uint8_t pin) {
delete[] topic;
delete[] payload;
}
/**
* @brief Publishes the value of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishDACValue(uint8_t pin) {
char *topic = new char[15];
sprintf(topic, "dac/%02d/value", pin);
@ -262,6 +415,10 @@ void AnalogIoT::publishDACValue(uint8_t pin) {
delete[] payload;
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::handleDACChange(uint8_t pin, uint16_t value) {
this->publishDAC(pin);
}

View File

@ -3,6 +3,7 @@
#include <AnalogCard.hpp>
#include <map>
// MQTT Topics
#define DAC_SET_STATE_TOPIC "/set/state"
#define DAC_SET_VALUE_TOPIC "/set/value"
#define DAC_STATE_TOPIC "/dac/00/state"
@ -39,7 +40,9 @@ class AnalogIoT : public IoTComponent {
void loop();
uint8_t getType();
private:
// The index of the next callback to be registered
uint8_t adc_conversion_callback_index = 0;
// We keep track of the length of the topics so we don't have to calculate it every time
uint8_t dac_set_state_length;
uint8_t dac_set_value_length;
uint8_t dac_state_length;

View File

@ -1,5 +1,21 @@
/**
* @file ClimateCard.cpp
* @brief Implementation file for the ClimateCard class.
*
* This file contains the implementation of the ClimateCard class, which represents a climate control card.
* The ClimateCard class provides methods for controlling an air conditioner, reading temperature and humidity
* from sensors, and saving and loading the state to/from FRAM memory.
*/
#include <ClimateCard.hpp>
/**
* @brief Construct a new ClimateCard object.
*
* @param ir_pin The GPIO pin number of the IR transmitter.
* @param ac The AirConditioner object that represents the air conditioner.
* @param sensor_type The type of the sensor connected to the card.
* @param sensor_pin The GPIO pin number of the sensor.
*/
ClimateCard::ClimateCard(uint8_t ir_pin, AirConditioner ac, uint8_t sensor_type, uint8_t sensor_pin)
{
this->ir_pin = ir_pin;
@ -24,11 +40,21 @@ ClimateCard::ClimateCard(uint8_t ir_pin, AirConditioner ac, uint8_t sensor_type,
this->state.ac_fan_speed = 0;
}
/**
* @brief Construct a new ClimateCard object.
*
* @param ir_pin The GPIO pin number of the IR transmitter.
* @param ac The AirConditioner object that represents the air conditioner.
*
* @note This constructor can be used when no sensor is connected to the card.
*/
ClimateCard::ClimateCard(uint8_t ir_pin, AirConditioner ac) : ClimateCard(ir_pin, ac, AC_SENSOR_TYPE_NONE, 0)
{
}
/**
* @brief The destructor of the ClimateCard class.
*/
ClimateCard::~ClimateCard()
{
delete dht;
@ -36,6 +62,12 @@ ClimateCard::~ClimateCard()
rmt_driver_uninstall(RMT_TX_CHANNEL);
}
/**
* @brief Initialize the ClimateCard object.
*
* @return true if initialization was successful.
* @return false if initialization failed.
*/
bool ClimateCard::begin()
{
switch (sensor_type)
@ -49,6 +81,7 @@ bool ClimateCard::begin()
break;
}
updateAirConditioner();
// We are returning here because sending IR signals is not working yet
return true;
if (sensor_pin != 0)
{
@ -61,6 +94,11 @@ bool ClimateCard::begin()
}
}
/**
* @brief Loop function of the ClimateCard class.
*
* @note When this card is installed in an ESPMega, this function is called automatically by the ESPMega class.
*/
void ClimateCard::loop()
{
static uint32_t last_sensor_update = 0;
@ -71,26 +109,54 @@ void ClimateCard::loop()
}
}
/**
* @brief bind FRAM memory to the ClimateCard object at the specified address.
*
* @note This function must be called before calling loadStateFromFRAM() or saveStateToFRAM().
* @note This card takes up 3 bytes of FRAM memory.
*
* @param fram The FRAM object.
* @param fram_address The starting address of the card in FRAM memory.
*/
void ClimateCard::bindFRAM(FRAM *fram, uint16_t fram_address)
{
this->fram = fram;
this->fram_address = fram_address;
}
/**
* @brief Set whether the state should be automatically saved to FRAM memory.
*
* @note This function has no effect if bindFRAM() has not been called.
* @param autoSave Whether the state should be automatically saved to FRAM memory.
*/
void ClimateCard::setFRAMAutoSave(bool autoSave)
{
this->fram_auto_save = autoSave;
}
/**
* @brief Save the state to FRAM memory.
* @note This function has no effect if bindFRAM() has not been called.
*/
void ClimateCard::saveStateToFRAM()
{
if (fram == nullptr)
return;
fram->write8(fram_address, state.ac_temperature);
fram->write8(fram_address + 1, state.ac_mode);
fram->write8(fram_address + 2, state.ac_fan_speed);
}
/**
* @brief Load the state from FRAM memory.
*
* @note This function has no effect if bindFRAM() has not been called.
*/
void ClimateCard::loadStateFromFRAM()
{
if (fram == nullptr)
return;
if (state.ac_temperature > ac.max_temperature)
state.ac_temperature = ac.max_temperature;
else if (state.ac_temperature < ac.min_temperature)
@ -102,12 +168,18 @@ void ClimateCard::loadStateFromFRAM()
if (state.ac_fan_speed > ac.fan_speeds)
state.ac_fan_speed = 0;
updateAirConditioner();
for (const auto& callback : callbacks)
for (const auto &callback : callbacks)
{
callback.second(this->state.ac_mode, this->state.ac_fan_speed, this->state.ac_temperature);
}
}
/**
* @brief Set the temperature of the air conditioner.
*
* @param temperature The temperature to set.
* @note If the temperature is out of range, it will be set to its respective maximum or minimum.
*/
void ClimateCard::setTemperature(uint8_t temperature)
{
// If temperature is out of range, set to its respective maximum or minimum
@ -121,33 +193,64 @@ void ClimateCard::setTemperature(uint8_t temperature)
saveStateToFRAM();
}
/**
* @brief Set the mode of the air conditioner.
*
* @note If the mode is out of range, it will be set to 0.
* @param mode The mode to set.
*/
void ClimateCard::setMode(uint8_t mode)
{
if (mode > ac.modes)
mode = 0;
this->state.ac_mode = mode;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
char* ClimateCard::getModeName()
/**
* @brief Get the name of the current mode.
* @return The name of the current mode.
*/
char *ClimateCard::getModeName()
{
return (char*)ac.mode_names[state.ac_mode];
return (char *)ac.mode_names[state.ac_mode];
}
char* ClimateCard::getFanSpeedName()
/**
* @brief Get the name of the current fan speed.
*
* @return The name of the current fan speed.
*/
char *ClimateCard::getFanSpeedName()
{
return (char*)ac.fan_speed_names[state.ac_fan_speed];
return (char *)ac.fan_speed_names[state.ac_fan_speed];
}
/**
* @brief Set the fan speed of the air conditioner.
*
* @note If the fan speed is out of range, it will be set to 0.
* @param fan_speed The fan speed to set.
*/
void ClimateCard::setFanSpeed(uint8_t fan_speed)
{
if (fan_speed > ac.fan_speeds)
fan_speed = 0;
this->state.ac_fan_speed = fan_speed;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
void ClimateCard::setFanSpeedByName(const char* fan_speed_name)
/**
* @brief Set fan speed by name.
*
* @param fan_speed_name The name of the fan speed to set.
* @note If the fan speed is not found, the function will not do anything.
*/
void ClimateCard::setFanSpeedByName(const char *fan_speed_name)
{
for (uint8_t i = 0; i < ac.fan_speeds; i++)
{
@ -159,7 +262,13 @@ void ClimateCard::setFanSpeedByName(const char* fan_speed_name)
}
}
void ClimateCard::setModeByName(const char* mode_name)
/**
* @brief Set mode by name.
*
* @param mode_name The name of the mode to set.
* @note If the mode is not found, the function will not do anything.
*/
void ClimateCard::setModeByName(const char *mode_name)
{
for (uint8_t i = 0; i < ac.modes; i++)
{
@ -171,17 +280,36 @@ void ClimateCard::setModeByName(const char* mode_name)
}
}
/**
* @brief Register a callback function that will be called when the state of the air conditioner changes.
*
* @param callback The callback function to register.
*
* @return uint8_t The handler of the callback function.
*/
uint8_t ClimateCard::registerChangeCallback(std::function<void(uint8_t, uint8_t, uint8_t)> callback)
{
callbacks[callbacks_handler_count] = callback;
return callbacks_handler_count++;
}
/**
* @brief Get the type of the card.
*
* @return The handler of the callback function.
*/
uint8_t ClimateCard::getType()
{
return CARD_TYPE_CLIMATE;
}
/**
* @brief update environmental sensor data.
*
* @note This function is called automatically by the loop() function.
* @note This function has no effect if no sensor is connected to the card.
* @note This function also calls the sensor callbacks.
*/
void ClimateCard::updateSensor()
{
if (sensor_type == AC_SENSOR_TYPE_NONE)
@ -209,17 +337,23 @@ void ClimateCard::updateSensor()
room_temperature = ds18b20->getTempC();
break;
}
for (const auto& callback : sensor_callbacks)
for (const auto &callback : sensor_callbacks)
{
callback.second(room_temperature, humidity);
}
}
/**
* @brief Update the air conditioner state to match the state of the card.
*
* @warning This function is not working yet.
*/
void ClimateCard::updateAirConditioner()
{
// // The IR Transmissions are not working yet so we just return
// const uint16_t* ir_code_ptr = nullptr;
// size_t itemCount = (*(this->ac.getInfraredCode))(this->state.ac_mode, this->state.ac_fan_speed, this->state.ac_temperature, &ir_code_ptr);
// if (ir_code_ptr == nullptr)
// return;
@ -236,53 +370,100 @@ void ClimateCard::updateAirConditioner()
// rmt_write_items(RMT_TX_CHANNEL, items, itemCount, true);
// rmt_wait_tx_done(RMT_TX_CHANNEL, portMAX_DELAY);
// // Publish state
for (const auto& callback : callbacks)
for (const auto &callback : callbacks)
{
callback.second(this->state.ac_mode, this->state.ac_fan_speed, this->state.ac_temperature);
}
}
/**
* @brief Get the type of the sensor connected to the card.
*
* @return The type of the sensor connected to the card.
*/
uint8_t ClimateCard::getSensorType()
{
return sensor_type;
}
/**
* @brief Get the room temperature in degrees Celsius.
*
* @return The room temperature.
*/
float ClimateCard::getRoomTemperature()
{
return room_temperature;
}
/**
* @brief Get the humidity in percent.
*
* @return The humidity.
*/
float ClimateCard::getHumidity()
{
return humidity;
}
/**
* @brief Get the temperature of the air conditioner.
*
* @return The temperature of the air conditioner.
*/
uint8_t ClimateCard::getTemperature()
{
return state.ac_temperature;
}
/**
* @brief Get the mode of the air conditioner.
*
* @return The mode of the air conditioner.
*/
uint8_t ClimateCard::getMode()
{
return state.ac_mode;
}
/**
* @brief Get the fan speed of the air conditioner.
*
* @return The fan speed of the air conditioner.
*/
uint8_t ClimateCard::getFanSpeed()
{
return state.ac_fan_speed;
}
/**
* @brief Register a callback function that will be called when the sensor data changes.
*
* @param callback The callback function to register.
*
* @return The handler of the callback function
*/
uint8_t ClimateCard::registerSensorCallback(std::function<void(float, float)> callback)
{
sensor_callbacks[sensor_callbacks_handler_count] = callback;
return sensor_callbacks_handler_count++;
}
/**
* @brief Unregister a callback function.
*
* @param handler The handler of the callback function to unregister.
*/
void ClimateCard::unregisterChangeCallback(uint8_t handler)
{
callbacks.erase(handler);
}
/**
* @brief Unregister a sensor callback function.
*
* @param handler The handler of the callback function to unregister.
*/
void ClimateCard::unregisterSensorCallback(uint8_t handler)
{
sensor_callbacks.erase(handler);

View File

@ -18,22 +18,38 @@
#define AC_SENSOR_READ_INTERVAL 5000
#define AC_SENSOR_READ_TIMEOUT 250
/**
* @brief The struct is used to store the state of the air conditioner
*
* @note This struct is stored in FRAM if it is used
* @note This struct is 3 bytes long
*/
struct ClimateCardData {
uint8_t ac_temperature;
uint8_t ac_mode;
uint8_t ac_fan_speed;
uint8_t ac_temperature; ///< Temperature of the air conditioner
uint8_t ac_mode; ///< Mode of the air conditioner
uint8_t ac_fan_speed;///< Fan speed of the air conditioner
};
/**
* @brief This struct is used to store information about an air conditioner
*/
struct AirConditioner {
uint8_t max_temperature;
uint8_t min_temperature;
uint8_t modes;
const char **mode_names;
uint8_t fan_speeds;
const char **fan_speed_names;
// function to get IR code
// takes 3 arguments: mode, fan speed, temperature, all uint8_t
// return size of IR code array
uint8_t max_temperature; ///< Maximum temperature
uint8_t min_temperature; ///< Minimum temperature
uint8_t modes; ///< Number of modes
const char **mode_names; ///< Names of modes in the form of an array of strings
uint8_t fan_speeds; ///< Number of fan speeds
const char **fan_speed_names; ///< Names of fan speeds in the form of an array of strings
/**
* @brief Function to get IR code
*
* @param mode Mode of the air conditioner
* @param fan_speed Fan speed of the air conditioner
* @param temperature Temperature of the air conditioner
* @param code Pointer to the IR code array
*
* @return Size of the IR code array
*/
size_t (*getInfraredCode)(uint8_t, uint8_t, uint8_t, const uint16_t**);
};
// This requires 3 bytes of FRAM

View File

@ -1,13 +1,29 @@
#include <ClimateIoT.hpp>
ClimateIoT::ClimateIoT() {
}
/**
* @brief Destructor for the ClimateIoT class.
*/
ClimateIoT::~ClimateIoT() {
// Destructor implementation
}
/**
* @brief Initializes the ClimateIoT component.
*
* This function sets the MQTT client, base topic, card ID, and card pointer.
* It also registers the sensor and air conditioner update callbacks.
*
* @param card_id The ID of the expansion card.
* @param card A pointer to the ExpansionCard object.
* @param mqtt A pointer to the PubSubClient object.
* @param base_topic The base topic for MQTT communication.
* @return True if the initialization is successful, false otherwise.
*/
bool ClimateIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->mqtt = mqtt;
this->base_topic = base_topic;
@ -22,6 +38,12 @@ bool ClimateIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt,
return true;
}
/**
* @brief Handles MQTT messages for the ClimateIoT component.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
*/
void ClimateIoT::handleMqttMessage(char *topic, char *payload) {
uint8_t topic_length = strlen(topic);
if (this->processSetTemperatureMessage(topic, payload, topic_length))
@ -34,31 +56,49 @@ void ClimateIoT::handleMqttMessage(char *topic, char *payload) {
return;
}
/**
* @brief Publishes the temperature of the air conditioner to the MQTT broker.
*/
void ClimateIoT::publishClimateTemperature() {
char payload[5];
itoa(this->card->getTemperature(), payload, 10);
this->publishRelative(AC_TEMPERATURE_REPORT_TOPIC, payload);
}
/**
* @brief Publishes the mode of the air conditioner to the MQTT broker.
*/
void ClimateIoT::publishClimateMode() {
this->publishRelative(AC_MODE_REPORT_TOPIC, this->card->getModeName());
}
/**
* @brief Publishes the fan speed of the air conditioner to the MQTT broker.
*/
void ClimateIoT::publishClimateFanSpeed() {
this->publishRelative(AC_FAN_SPEED_REPORT_TOPIC, this->card->getFanSpeedName());
}
/**
* @brief Publishes the temperature and humidity of the room to the MQTT broker.
*/
void ClimateIoT::publishSensor() {
this->publishRoomTemperature();
this->publishHumidity();
}
/**
* @brief Publishes the climate data (temperature, mode, fan speed) to the MQTT broker.
*/
void ClimateIoT::publishClimate() {
this->publishClimateTemperature();
this->publishClimateMode();
this->publishClimateFanSpeed();
}
/**
* @brief Publishes the room temperature to the MQTT broker.
*/
void ClimateIoT::publishRoomTemperature() {
if (this->card->getSensorType() == AC_SENSOR_TYPE_NONE ) {
return;
@ -68,6 +108,9 @@ void ClimateIoT::publishRoomTemperature() {
this->publishRelative(AC_ROOM_TEMPERATURE_REPORT_TOPIC, payload);
}
/**
* @brief Publishes the humidity of the room to the MQTT broker.
*/
void ClimateIoT::publishHumidity() {
if (this->card->getSensorType() == AC_SENSOR_TYPE_DHT22) {
char payload[5];
@ -76,15 +119,30 @@ void ClimateIoT::publishHumidity() {
}
}
/**
* @brief Handle Air Conditioner state change.
*
* @note This function is called by the underlying ClimateCard object and is not meant to be called manually.
*
* @param temperature Temperature of the air conditioner
* @param mode Mode of the air conditioner
* @param fan_speed Fan speed of the air conditioner
*/
void ClimateIoT::handleStateChange(uint8_t temperature, uint8_t mode, uint8_t fan_speed) {
this->publishClimate();
}
/**
* @brief Publishes the climate and sensor data to the MQTT broker.
*/
void ClimateIoT::publishReport() {
this->publishClimate();
this->publishSensor();
}
/**
* @brief Subscribes to MQTT topics.
*/
void ClimateIoT::subscribe() {
ESP_LOGD("ClimateIoT", " topics");
this->subscribeRelative(AC_TEMPERATURE_SET_TOPIC);
@ -93,14 +151,32 @@ void ClimateIoT::subscribe() {
ESP_LOGD("ClimateIoT", "Subscribed to topics");
}
/**
* @brief The loop function for the ClimateIoT component.
*
* @note This function does nothing.
*/
void ClimateIoT::loop() {
}
/**
* @brief Returns the type of the expansion card.
*
* @return The type of the expansion card.
*/
uint8_t ClimateIoT::getType() {
return CARD_TYPE_CLIMATE;
}
/**
* @brief Processes the set temperature MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processSetTemperatureMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_TEMPERATURE_SET_TOPIC)) {
uint8_t temperature = atoi(payload);
@ -110,6 +186,14 @@ bool ClimateIoT::processSetTemperatureMessage(char *topic, char *payload, uint8_
return false;
}
/**
* @brief Processes the set mode MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processSetModeMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_MODE_SET_TOPIC)) {
this->card->setModeByName(payload);
@ -118,6 +202,14 @@ bool ClimateIoT::processSetModeMessage(char *topic, char *payload, uint8_t topic
return false;
}
/**
* @brief Processes the set fan speed MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processSetFanSpeedMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_FAN_SPEED_SET_TOPIC)) {
this->card->setFanSpeedByName(payload);
@ -125,6 +217,14 @@ bool ClimateIoT::processSetFanSpeedMessage(char *topic, char *payload, uint8_t t
return false;
}
/**
* @brief Processes the request state MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_REQUEST_STATE_TOPIC)) {
this->publishReport();
@ -133,10 +233,25 @@ bool ClimateIoT::processRequestStateMessage(char *topic, char *payload, uint8_t
return false;
}
/**
* @brief This function is a callback function registered with the Climate card to be called when the sensor data is updated.
*
* @param temperature The room temperature.
* @param humidity The room humidity.
*
* @note The temperature and humidity are not used in this function but are required by the ClimateCard class to match the signature of the callback function.
*/
void ClimateIoT::handleSensorUpdate(float temperature, float humidity) {
this->publishSensor();
}
/**
* @brief This function is a callback function registered with the Climate card to be called when the air conditioner state is updated.
*
* @param mode The mode of the air conditioner.
* @param fan_speed The fan speed of the air conditioner.
* @param temperature The temperature of the air conditioner.
*/
void ClimateIoT::handleAirConditionerUpdate(uint8_t mode, uint8_t fan_speed, uint8_t temperature) {
this->publishClimate();
}

View File

@ -3,6 +3,7 @@
#include <ExpansionCard.hpp>
#include <ClimateCard.hpp>
// MQTT Topics
#define AC_MODE_REPORT_TOPIC "mode"
#define AC_MODE_SET_TOPIC "set/mode"
#define AC_TEMPERATURE_REPORT_TOPIC "temperature"

View File

@ -1,15 +1,33 @@
#include <DigitalInputCard.hpp>
// Instantiate the card with the specified address
/**
* @brief Create a new Digital Input Card object with the specified address
* @note If you are using the ESPMegaI/O board, you should use the dip switch constructor
*
* @param address_a The ESPMegaI/O address of bank A
* @param address_b The ESPMegaI/O address of bank B
*/
DigitalInputCard::DigitalInputCard(uint8_t address_a, uint8_t address_b) : callbacks()
{
this->address_a = address_a;
this->address_b = address_b;
this->callbacks_handler_index = 0;
}
// Instantiate the card with the specified position on the dip switch
// Bit 0,1,2 are for bank A
// Bit 3,4,5 are for bank B
/**
* @brief Create a new Digital Input 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 6 switches on the dip switch, 3 for bank A and 3 for bank B, They should be unique for each bank 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
* @param bit5 The position of the sixth switch on the dip switch
*/
DigitalInputCard::DigitalInputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4, bool bit5)
{
this->address_a = 0x20;
@ -29,7 +47,12 @@ DigitalInputCard::DigitalInputCard(bool bit0, bool bit1, bool bit2, bool bit3, b
if (bit5)
this->address_b += 4;
}
// Initialize the card
/**
* @brief Initialize the Digital Input Card
*
* @return True if the initialization is successful, false otherwise
*/
bool DigitalInputCard::begin()
{
this->inputBankA = PCF8574(this->address_a);
@ -56,12 +79,25 @@ bool DigitalInputCard::begin()
}
return true;
}
// Refresh and Read the input from the specified pin, always refresh the input buffers
/**
* @brief Read the input from the specified pin, always refresh the input buffers
*
* @param pin The pin to read from
* @return True if the pin is HIGH, false if the pin is LOW
*/
bool DigitalInputCard::digitalRead(uint8_t pin)
{
return this->digitalRead(pin, true);
}
// Read the input from the specified pin, also refresh the input buffers if refresh is true
/**
* @brief Read the input from the specified pin, also refresh the input buffers if refresh is true
*
* @param pin The pin to read from
* @param refresh If true, the input buffers will be refreshed before reading the pin
* @return True if the pin is HIGH, false if the pin is LOW
*/
bool DigitalInputCard::digitalRead(uint8_t pin, bool refresh)
{
pin = pinMap[pin];
@ -85,6 +121,15 @@ bool DigitalInputCard::digitalRead(uint8_t pin, bool refresh)
return 255;
}
/**
* @brief Check if the specified pin changed since the last call to this function
*
* @note This function compares the current input buffer with the previous input buffer to detect changes
*
* @param pin The pin to check
* @param currentBuffer The current input buffer
* @param previousBuffer The previous input buffer
*/
void DigitalInputCard::handlePinChange(int pin, uint8_t &currentBuffer, uint8_t &previousBuffer)
{
// Get the index of the pin in the pin map
@ -113,7 +158,11 @@ void DigitalInputCard::handlePinChange(int pin, uint8_t &currentBuffer, uint8_t
}
}
/**
* @brief A loop to refresh the input buffers and check for pin changes
*
* @note Although this function can be called in the main loop, it is recommended install the card in ESPMega to automatically manage the loop
*/
// Preform a loop to refresh the input buffers
void DigitalInputCard::loop()
{
@ -134,7 +183,12 @@ void DigitalInputCard::loop()
}
}
}
// Get the input buffer for bank A
/**
* @brief Get the input buffer for bank A (the first 8 pins)
*
* @return The input buffer for bank A where the first bit is the first pin and the last bit is the last pin
*/
uint8_t DigitalInputCard::getInputBufferA()
{
// Rearrange the bits to match the pin map
@ -145,7 +199,12 @@ uint8_t DigitalInputCard::getInputBufferA()
}
return inputBufferA_rearranged;
}
// Get the input buffer for bank B
/**
* @brief Get the input buffer for bank B (the last 8 pins)
*
* @return The input buffer for bank B where the first bit is the first pin and the last bit is the last pin
*/
uint8_t DigitalInputCard::getInputBufferB()
{
// Rearrange the bits to match the pin map
@ -156,35 +215,71 @@ uint8_t DigitalInputCard::getInputBufferB()
}
return inputBufferB_rearranged;
}
// Register a callback function to be called when a pin changes
/**
* @brief Register a callback function to be called when a pin changes
*
* @param callback The callback function to be called
* @return The handler of the callback function
*/
uint8_t DigitalInputCard::registerCallback(std::function<void(uint8_t, bool)> callback)
{
callbacks[this->callbacks_handler_index] = callback;
return this->callbacks_handler_index++;
}
// Refresh the input buffer for bank A
/**
* @brief Read the input state from the input ic and store it in the input buffer for bank A
*/
void DigitalInputCard::refreshInputBankA()
{
inputBufferA = inputBankA.read8();
}
// Refresh the input buffer for bank B
/**
* @brief Read the input state from the input ic and store it in the input buffer for bank B
*/
void DigitalInputCard::refreshInputBankB()
{
inputBufferB = inputBankB.read8();
}
/**
* @brief Set the debounce time for the specified pin
*
* Debounce is the time in milliseconds that the pin should be stable before the callback function is called
* This is useful to prevent false triggers when the input is noisy
* An example of this is when the input is connected to a mechanical switch
*
* @param pin The pin to set the debounce time for
* @param debounceTime The debounce time in milliseconds
*/
void DigitalInputCard::setDebounceTime(uint8_t pin, uint32_t debounceTime)
{
pin = pinMap[pin];
this->debounceTime[pin] = debounceTime;
}
/**
* @brief Unregister a callback function
*
* @param handler The handler of the callback function to unregister
*/
void DigitalInputCard::unregisterCallback(uint8_t handler)
{
callbacks.erase(handler);
}
/**
* @brief Load the pin map for the card
*
* 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 Input 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 DigitalInputCard::loadPinMap(uint8_t pinMap[16])
{
for (int i = 0; i < 16; i++)
@ -196,6 +291,11 @@ void DigitalInputCard::loadPinMap(uint8_t pinMap[16])
}
}
/**
* @brief Get the type of the card
*
* @return The type of the card
*/
uint8_t DigitalInputCard::getType()
{
return CARD_TYPE_DIGITAL_INPUT;

View File

@ -3,6 +3,7 @@
#include <PCF8574.h>
#include <map>
// Card Type
#define CARD_TYPE_DIGITAL_INPUT 0x01
class DigitalInputCard : public ExpansionCard {

View File

@ -1,6 +1,20 @@
#include <DigitalInputIoT.hpp>
/**
* @brief Initializes the DigitalInputIoT object.
*
* This function sets the necessary parameters for the DigitalInputIoT object, such as the card ID, expansion card, MQTT client, and base topic.
* It also enables the publishing of digital input values and registers a callback function for handling value changes.
*
* @note Although this function can be called in the main program, it is recommended to use ESPMegaIoT::registerCard() to automatically manage the instantiation and initialization of this component.
*
* @param card_id The ID of the card.
* @param card Pointer to the DigitalInputCard object.
* @param mqtt Pointer to the PubSubClient object.
* @param base_topic The base topic for MQTT communication.
* @return True if the initialization is successful, false otherwise.
*/
bool DigitalInputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->card = (DigitalInputCard *)card;
this->card_id = card_id;
@ -12,10 +26,19 @@ bool DigitalInputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *
}
/**
* @brief Subscribes to the MQTT topics for the DigitalInputIoT component.
*/
void DigitalInputIoT::subscribe() {
this->subscribeRelative(PUBLISH_ENABLE_TOPIC);
}
/**
* @brief Handles MQTT messages for the DigitalInputIoT component.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
*/
void DigitalInputIoT::handleMqttMessage(char *topic, char *payload) {
// payload is char '0' or '1'
if (!strcmp(topic, PUBLISH_ENABLE_TOPIC)) {
@ -26,6 +49,10 @@ void DigitalInputIoT::handleMqttMessage(char *topic, char *payload) {
}
}
}
/**
* @brief Publish all digital inputs to the MQTT broker.
*/
void DigitalInputIoT::publishDigitalInputs() {
if (!this->digital_inputs_publish_enabled) {
return;
@ -34,18 +61,41 @@ void DigitalInputIoT::publishDigitalInputs() {
this->publishDigitalInput(i);
}
}
/**
* @brief Set if the digital inputs should be published to the MQTT broker.
*
* @param enabled True if the digital inputs should be published, false otherwise.
*/
void DigitalInputIoT::setDigitalInputsPublishEnabled(bool enabled) {
this->digital_inputs_publish_enabled = enabled;
if (enabled) {
this->publishDigitalInputs();
}
}
/**
* @brief Handles a value change for a digital input.
*
* @note This function is registered as a callback function for the DigitalInputCard object.
*
* @param pin The pin that changed.
* @param value The new value of the pin.
*/
void DigitalInputIoT::handleValueChange(uint8_t pin, uint8_t value) {
if (this->digital_inputs_publish_enabled) {
this->publishDigitalInput(pin);
}
}
/**
* @brief Publish all inputs to the MQTT Broker
*
* @note This function is overriden from the IoTComponent class and is called by ESPMegaIoT.
*
* @param pin The pin to publish.
*/
void DigitalInputIoT::publishReport() {
this->publishDigitalInputs();
}
@ -53,7 +103,11 @@ uint8_t DigitalInputIoT::getType() {
return CARD_TYPE_DIGITAL_INPUT;
}
/**
* @brief Publish a digital input to the MQTT broker.
*
* @param pin The pin to publish.
*/
void DigitalInputIoT::publishDigitalInput(uint8_t pin) {
char topic[20] = {0};
char payload[20] = {0};

View File

@ -4,6 +4,7 @@
#include <DigitalInputCard.hpp>
#include <FRAM.h>
// MQTT Topics
#define PUBLISH_ENABLE_TOPIC "publish_enable"
class DigitalInputIoT : public IoTComponent {