rename folders

This commit is contained in:
Siwat Sirichai 2023-12-30 15:03:20 +07:00
parent 1ec8effe90
commit 1815597374
63 changed files with 1 additions and 0 deletions

View file

@ -0,0 +1,138 @@
#include <AnalogCard.hpp>
AnalogCard::AnalogCard() : dac0(DAC0_ADDRESS),
dac1(DAC1_ADDRESS),
dac2(DAC2_ADDRESS),
dac3(DAC3_ADDRESS),
analogInputBankA(),
analogInputBankB()
{
}
void AnalogCard::dacWrite(uint8_t pin, uint16_t value)
{
this->setDACState(pin, value > 0);
this->setDACValue(pin, value);
}
void AnalogCard::setDACState(uint8_t pin, bool state)
{
this->dac_state[pin] = state;
this->sendDataToDAC(pin, this->dac_value[pin]*state);
for (int i = 0; i < this->dac_change_callbacks.size(); i++)
{
this->dac_change_callbacks[i](pin, state, this->dac_value[pin]);
}
}
void AnalogCard::setDACValue(uint8_t pin, uint16_t value)
{
this->dac_value[pin] = value;
this->sendDataToDAC(pin, value*this->dac_state[pin]);
for (int i = 0; i < this->dac_change_callbacks.size(); i++)
{
this->dac_change_callbacks[i](pin, this->dac_state[pin], value);
}
}
uint16_t AnalogCard::getDACValue(uint8_t pin)
{
return this->dac_value[pin];
}
bool AnalogCard::getDACState(uint8_t pin)
{
return this->dac_state[pin];
}
void AnalogCard::sendDataToDAC(uint8_t pin, uint16_t value)
{
switch (pin)
{
case 0:
this->dac0.writeDAC(value);
break;
case 1:
this->dac1.writeDAC(value);
break;
case 2:
this->dac2.writeDAC(value);
break;
case 3:
this->dac3.writeDAC(value);
break;
}
}
uint16_t AnalogCard::analogRead(uint8_t pin)
{
if (pin >= 0 && pin <= 3)
{
return this->analogInputBankA.readADC_SingleEnded(pin);
}
else if (pin >= 4 && pin <= 7)
{
return this->analogInputBankB.readADC_SingleEnded(pin - 4);
}
return 65535;
}
bool AnalogCard::begin()
{
if (!this->dac0.begin())
{
Serial.println("Card Analog ERROR: Failed to install DAC0");
return false;
}
if (!this->dac1.begin())
{
Serial.println("Card Analog ERROR: Failed to install DAC1");
return false;
}
if (!this->dac2.begin())
{
Serial.println("Card Analog ERROR: Failed to install DAC2");
return false;
}
if (!this->dac3.begin())
{
Serial.println("Card Analog ERROR: Failed to install DAC3");
return false;
}
if (!this->analogInputBankA.begin())
{
Serial.println("Card Analog ERROR: Failed to install analog input bank A");
return false;
}
if (!this->analogInputBankB.begin())
{
Serial.println("Card Analog ERROR: Failed to install analog input bank B");
return false;
}
return true;
}
void AnalogCard::loop()
{
}
uint8_t AnalogCard::getType()
{
return CARD_TYPE_ANALOG;
}
void AnalogCard::registerDACChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback)
{
this->dac_change_callbacks.push_back(callback);
}
// void AnalogCard::deregisterDACChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback)
// {
// for (int i = 0; i < this->dac_change_callbacks.size(); i++)
// {
// if (this->dac_change_callbacks[i].target<void(uint8_t, bool, uint16_t)>() == callback.target<void(uint8_t, bool, uint16_t)>())
// {
// this->dac_change_callbacks.erase(this->dac_change_callbacks.begin() + i);
// break;
// }
// }
// }

View file

@ -0,0 +1,41 @@
#pragma once
#include <ExpansionCard.hpp>
#include <Adafruit_ADS1X15.h>
#include <MCP4725.h>
#include <vector>
#define CARD_TYPE_ANALOG 0x02
#define ANALOG_INPUT_BANK_A_ADDRESS 0x48
#define ANALOG_INPUT_BANK_B_ADDRESS 0x49
#define DAC0_ADDRESS 0x60
#define DAC1_ADDRESS 0x61
#define DAC2_ADDRESS 0x62
#define DAC3_ADDRESS 0x63
class AnalogCard : public ExpansionCard {
public:
AnalogCard();
void dacWrite(uint8_t pin, uint16_t value);
void sendDataToDAC(uint8_t pin, uint16_t value);
uint16_t analogRead(uint8_t pin);
bool begin();
void loop();
bool getDACState(uint8_t pin);
uint16_t getDACValue(uint8_t pin);
void setDACState(uint8_t pin, bool state);
void setDACValue(uint8_t pin, uint16_t value);
void registerDACChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
//void deregisterDACChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
uint8_t getType();
private:
std::vector<std::function<void(uint8_t, bool, uint16_t)>> dac_change_callbacks;
bool dac_state[4];
uint16_t dac_value[4];
MCP4725 dac0;
MCP4725 dac1;
MCP4725 dac2;
MCP4725 dac3;
Adafruit_ADS1115 analogInputBankA;
Adafruit_ADS1115 analogInputBankB;
};

View file

@ -0,0 +1,270 @@
#include <AnalogIoT.hpp>
AnalogIoT::AnalogIoT() {
for (uint8_t i = 0; i < 8; i++) {
adc_publish_enabled[i] = false;
adc_conversion_interval[i] = 1000;
}
}
AnalogIoT::~AnalogIoT() {
}
bool AnalogIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->mqtt = mqtt;
this->base_topic = base_topic;
this->card = (AnalogCard*)card;
this-> card_id = card_id;
this->dac_set_state_length = strlen(DAC_SET_STATE_TOPIC);
this->dac_set_value_length = strlen(DAC_SET_VALUE_TOPIC);
this->dac_state_length = strlen(DAC_STATE_TOPIC);
this->dac_value_length = strlen(DAC_VALUE_TOPIC);
this->request_state_length = strlen(REQUEST_STATE_TOPIC);
this->dac_publish_enable_length = strlen(DAC_PUBLISH_ENABLE_TOPIC);
// Register callbacks
auto bindedCallback = std::bind(&AnalogIoT::handleDACChange, this, std::placeholders::_1, std::placeholders::_2);
this->card->registerDACChangeCallback(bindedCallback);
return true;
}
void AnalogIoT::handleMqttMessage(char *topic, char *payload){
uint8_t topic_length = strlen(topic);
if(this-> processDACSetStateMessage(topic, payload, topic_length)) return;
if(this-> processDACSetValueMessage(topic, payload, topic_length)) return;
if(this-> processRequestStateMessage(topic, payload, topic_length)) return;
if(this-> processADCSetConversionIntervalMessage(topic, payload, topic_length)) return;
if(this-> processADCSetConversionEnabledMessage(topic, payload, topic_length)) return;
}
void AnalogIoT::publishADCs() {
for (uint8_t i = 0; i < 8; i++) {
this->publishADC(i);
}
}
void AnalogIoT::publishADC(uint8_t pin) {
if (this->adc_publish_enabled[pin]) {
uint16_t value = this->card->analogRead(pin);
char *topic = new char[15];
sprintf(topic, "adc/%02d/value", pin);
char *payload = new char[10];
sprintf(payload, "%d", value);
this->publishRelative(topic, payload);
delete[] topic;
delete[] payload;
// Call all callbacks
for (int i = 0; i < this->adc_conversion_callbacks.size(); i++) {
this->adc_conversion_callbacks[i](pin, value);
}
}
}
void AnalogIoT::setADCsPublishInterval(uint32_t interval) {
for (uint8_t i = 0; i < 8; i++) {
adc_conversion_interval[i] = interval;
}
}
void AnalogIoT::setADCsPublishEnabled(bool enabled) {
for (uint8_t i = 0; i < 8; i++) {
adc_publish_enabled[i] = enabled;
}
}
void AnalogIoT::registerADCConversionCallback(std::function<void(uint8_t, uint16_t)> callback) {
this->adc_conversion_callbacks.push_back(callback);
}
// void AnalogIoT::deregisterADCConversionCallback(std::function<void(uint8_t, uint16_t)> callback) {
// for (int i = 0; i < this->adc_conversion_callbacks.size(); i++) {
// if (this->adc_conversion_callbacks[i].target<void(uint8_t, uint16_t)>() == callback.target<void(uint8_t, uint16_t)>()) {
// this->adc_conversion_callbacks.erase(this->adc_conversion_callbacks.begin() + i);
// break;
// }
// }
// }
void AnalogIoT::setADCConversionInterval(uint8_t pin, uint16_t interval) {
adc_conversion_interval[pin] = interval;
}
void AnalogIoT::setADCConversionEnabled(uint8_t pin, bool enabled) {
adc_publish_enabled[pin] = enabled;
}
bool AnalogIoT::processADCSetConversionIntervalMessage(char *topic, char *payload, uint8_t topic_length) {
// TODO: Process payload matching the criteria
// Topic: adc/<%02d>/set/conversion_interval
// The first 4 characters are "adc/"
// The length of the topic must be 30 characters
// The last 24 characters must be "/set/conversion_interval"
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 30) {
return false;
}
if (strncmp(topic, "adc/", 4)) {
return false;
}
if (strncmp(topic + 26, "/set/conversion_interval", 24)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
uint16_t interval = atoi(payload);
// Set the interval
this->setADCConversionInterval(pin, interval);
return true;
}
bool AnalogIoT::processADCSetConversionEnabledMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: adc/<%02d>/set/conversion_enabled
// The first 4 characters are "adc/"
// The length of the topic must be 29 characters
// The last 23 characters must be ""/set/conversion_enabled
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 29) {
return false;
}
if (strncmp(topic, "adc/", 4)) {
return false;
}
if (strncmp(topic + 25, "/set/conversion_enabled", 23)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
bool enabled = atoi(payload);
// Set conversion enabled
this->setADCConversionEnabled(pin, enabled);
return true;
}
bool AnalogIoT::processDACSetStateMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: dac/<%02d>/set/state
// The first 4 characters are "dac/"
// The length of the topic must be 16 characters
// The last 10 characters must be "/set/state"
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 16) {
return false;
}
if (strncmp(topic, "dac/", 4)) {
return false;
}
if (strncmp(topic + 12, "/set/state", 10)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
bool state = atoi(payload);
// Set the state
this->card->setDACState(pin, state);
return true;
}
bool AnalogIoT::processDACSetValueMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: dac/<%02d>/set/value
// The first 4 characters are "dac/"
// The length of the topic must be 16 characters
// The last 10 characters must be "/set/value"
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 16) {
return false;
}
if (strncmp(topic, "dac/", 4)) {
return false;
}
if (strncmp(topic + 12, "/set/value", 10)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
uint16_t value = atoi(payload);
// Set the value
this->card->setDACValue(pin, value);
return true;
}
bool AnalogIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: requeststate
// The length of the topic must be 12 characters
// After all these conditions are met, the topic is valid
if (topic_length != 12) {
return false;
}
if (strncmp(topic, REQUEST_STATE_TOPIC, 12)) {
return false;
}
// Publish the state of all DACs
this->publishDACs();
// Publish the state of all ADCs
this->publishADCs();
return false;
}
void AnalogIoT::subscribe() {
// There are 4 DACs and 8 ADCs
// DACs: dac/<%02d>/set/state, dac/<%02d>/set/value, dac/publish_enable
// ADCs: adc/<%02d>/set/conversion_interval, adc/<%02d>/set/conversion_enabled
// Subscribe to all set state topics
char topic[20];
for (uint8_t i = 0; i < 4; i++) {
sprintf(topic, "dac/%02d/set/state", i);
this->subscribeRelative(topic);
}
// Subscribe to all set value topics
for (uint8_t i = 0; i < 4; i++) {
sprintf(topic, "dac/%02d/set/value", i);
this->subscribeRelative(topic);
}
// Subscribe to all set conversion interval topics
for (uint8_t i = 0; i < 8; i++) {
sprintf(topic, "adc/%02d/set/conversion_interval", i);
this->subscribeRelative(topic);
}
// Subscribe to all set conversion enabled topics
for (uint8_t i = 0; i < 8; i++) {
sprintf(topic, "adc/%02d/set/conversion_enabled", i);
this->subscribeRelative(topic);
}
// Subscribe to publish enable topic
this->subscribeRelative("dac/publish_enable");
}
void AnalogIoT::loop() {
// Iterate over all ADCs and publish if enabled and interval has passed
uint32_t now = millis();
for (uint8_t i = 0; i < 8; i++) {
if (this->adc_publish_enabled[i] && now - this->last_adc_publish > this->adc_conversion_interval[i]) {
this->publishADC(i);
this->last_adc_publish = now;
}
}
}
void AnalogIoT::publishReport() {
publishADCs();
publishDACs();
}
uint8_t AnalogIoT::getType() {
return CARD_TYPE_ANALOG;
}
void AnalogIoT::publishDACs() {
for (uint8_t i = 0; i < 4; i++) {
this->publishDAC(i);
}
}
void AnalogIoT::publishDAC(uint8_t pin) {
this->publishDACState(pin);
this->publishDACValue(pin);
}
void AnalogIoT::publishDACState(uint8_t pin) {
char *topic = new char[15];
sprintf(topic, "dac/%02d/state", pin);
char *payload = new char[2];
sprintf(payload, "%d", this->card->getDACState(pin));
this->publishRelative(topic, payload);
delete[] topic;
delete[] payload;
}
void AnalogIoT::publishDACValue(uint8_t pin) {
char *topic = new char[15];
sprintf(topic, "dac/%02d/value", pin);
char *payload = new char[5];
sprintf(payload, "%d", this->card->getDACValue(pin));
this->publishRelative(topic, payload);
delete[] topic;
delete[] payload;
}
void AnalogIoT::handleDACChange(uint8_t pin, uint16_t value) {
this->publishDAC(pin);
}

View file

@ -0,0 +1,54 @@
#pragma once
#include <IoTComponent.hpp>
#include <AnalogCard.hpp>
#include <vector>
#define DAC_SET_STATE_TOPIC "/set/state"
#define DAC_SET_VALUE_TOPIC "/set/value"
#define DAC_STATE_TOPIC "/dac/00/state"
#define DAC_VALUE_TOPIC "/dac/00/value"
#define DAC_PUBLISH_ENABLE_TOPIC "/publish_enable"
#define REQUEST_STATE_TOPIC "requeststate"
class AnalogIoT : public IoTComponent {
public:
AnalogIoT();
~AnalogIoT();
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void handleDACChange(uint8_t pin, uint16_t value);
void publishADCs();
void publishADC(uint8_t pin);
void publishDACs();
void publishDAC(uint8_t pin);
void publishDACState(uint8_t pin);
void publishDACValue(uint8_t pin);
void setADCsPublishInterval(uint32_t interval);
void setADCsPublishEnabled(bool enabled);
void registerADCConversionCallback(std::function<void(uint8_t, uint16_t)> callback);
// void deregisterADCConversionCallback(std::function<void(uint8_t, uint16_t)> callback);
void setADCConversionInterval(uint8_t pin, uint16_t interval);
void setADCConversionEnabled(uint8_t pin, bool enabled);
bool processADCSetConversionIntervalMessage(char *topic, char *payload, uint8_t topic_length);
bool processADCSetConversionEnabledMessage(char *topic, char *payload, uint8_t topic_length);
bool processDACSetStateMessage(char *topic, char *payload, uint8_t topic_length);
bool processDACSetValueMessage(char *topic, char *payload, uint8_t topic_length);
bool processRequestStateMessage(char *topic, char *payload, uint8_t topic_length);
void publishReport();
void subscribe();
void loop();
uint8_t getType();
private:
uint8_t dac_set_state_length;
uint8_t dac_set_value_length;
uint8_t dac_state_length;
uint8_t dac_value_length;
uint8_t request_state_length;
uint8_t dac_publish_enable_length;
uint32_t last_adc_publish = 0;
AnalogCard *card;
bool adc_publish_enabled[8];
uint16_t adc_conversion_interval[8];
uint32_t last_adc_conversion[8];
std::vector<std::function<void(uint8_t, uint16_t)>> adc_conversion_callbacks;
};

View file

@ -0,0 +1,197 @@
#include <ClimateCard.hpp>
ClimateCard::ClimateCard(uint8_t ir_pin) : irsender(ir_pin)
{
this->ir_pin = ir_pin;
irsender.begin(ir_pin);
// Initialize Pointers
this->dht = nullptr;
this->ds18b20 = nullptr;
this->fram = nullptr;
// Initialize Variables
this->fram_address = 0;
this->fram_auto_save = false;
this->state.ac_temperature = 0;
this->state.ac_mode = 0;
this->state.ac_fan_speed = 0;
this->humidity = 0;
this->room_temperature = 0;
// Initialize state
this->state.ac_temperature = 25;
this->state.ac_mode = 0;
this->state.ac_fan_speed = 0;
}
ClimateCard::~ClimateCard()
{
delete dht;
delete ds18b20;
}
bool ClimateCard::begin(AirConditioner ac, uint8_t sensor_type, uint8_t sensor_pin)
{
this->ac = ac;
this->sensor_type = sensor_type;
this->sensor_pin = sensor_pin;
switch (sensor_type)
{
case AC_SENSOR_TYPE_DHT22:
dht = new DHTNEW(sensor_pin);
break;
case AC_SENSOR_TYPE_DS18B20:
OneWire oneWire(sensor_pin);
ds18b20 = new DS18B20(&oneWire);
break;
}
updateAirConditioner();
}
bool ClimateCard::begin(AirConditioner ac)
{
this->begin(ac, AC_SENSOR_TYPE_NONE, 0);
}
void ClimateCard::loop()
{
static uint32_t last_sensor_update = 0;
if (millis() - last_sensor_update >= AC_SENSOR_READ_INTERVAL)
{
last_sensor_update = millis();
updateSensor();
}
}
void ClimateCard::bindFRAM(FRAM *fram, uint16_t fram_address)
{
this->fram = fram;
this->fram_address = fram_address;
}
void ClimateCard::setFRAMAutoSave(bool autoSave)
{
this->fram_auto_save = autoSave;
}
void ClimateCard::saveStateToFRAM()
{
fram->writeObject(fram_address, this->state);
}
void ClimateCard::loadStateFromFRAM()
{
fram->readObject(fram_address, this->state);
}
void ClimateCard::setTemperature(uint8_t temperature)
{
this->state.ac_temperature = temperature;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
void ClimateCard::setMode(uint8_t mode)
{
this->state.ac_mode = mode;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
void ClimateCard::setFanSpeed(uint8_t fan_speed)
{
this->state.ac_fan_speed = fan_speed;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
void ClimateCard::registerChangeCallback(std::function<void(uint8_t, uint8_t, uint8_t)> callback)
{
callbacks.push_back(callback);
}
uint8_t ClimateCard::getType()
{
return CARD_TYPE_CLIMATE;
}
void ClimateCard::updateSensor()
{
if (sensor_type == AC_SENSOR_TYPE_NONE)
return;
// Read sensor data and update variables
switch (sensor_type)
{
case AC_SENSOR_TYPE_DHT22:
if (millis() - dht->lastRead() < AC_SENSOR_READ_INTERVAL)
return;
dht->read();
room_temperature = dht->getTemperature();
humidity = dht->getHumidity();
break;
case AC_SENSOR_TYPE_DS18B20:
ds18b20->requestTemperatures();
uint32_t start = millis();
while (!ds18b20->isConversionComplete())
{
if (millis() - start >= AC_SENSOR_READ_TIMEOUT)
{
return;
}
}
room_temperature = ds18b20->getTempC();
break;
}
for (uint8_t i = 0; i < sensor_callbacks.size(); i++)
{
sensor_callbacks[i](room_temperature, humidity);
}
}
void ClimateCard::updateAirConditioner()
{
irsender.sendRaw(ac.infraredCodes[this->state.ac_mode][this->state.ac_fan_speed][this->state.ac_temperature],
sizeof(ac.infraredCodes[this->state.ac_mode][this->state.ac_fan_speed][this->state.ac_temperature]) / sizeof(uint16_t),
NEC_KHZ);
for (uint8_t i = 0; i < callbacks.size(); i++)
{
callbacks[i](this->state.ac_mode, this->state.ac_fan_speed, this->state.ac_temperature);
}
}
uint8_t ClimateCard::getSensorType()
{
return sensor_type;
}
float ClimateCard::getRoomTemperature()
{
return room_temperature;
}
float ClimateCard::getHumidity()
{
return humidity;
}
uint8_t ClimateCard::getTemperature()
{
return state.ac_temperature;
}
uint8_t ClimateCard::getMode()
{
return state.ac_mode;
}
uint8_t ClimateCard::getFanSpeed()
{
return state.ac_fan_speed;
}
void ClimateCard::registerSensorCallback(std::function<void(float, float)> callback)
{
sensor_callbacks.push_back(callback);
}

View file

@ -0,0 +1,85 @@
#pragma once
#include <ExpansionCard.hpp>
#include <IRremote.h>
#include <FRAM.h>
#include <OneWire.h>
#include <DS18B20.h>
#include <dhtnew.h>
#include <vector>
#define CARD_TYPE_CLIMATE 0x03
#define AC_SENSOR_TYPE_NONE 0x00
#define AC_SENSOR_TYPE_DHT22 0x01
#define AC_SENSOR_TYPE_DS18B20 0x02
#define AC_SENSOR_READ_INTERVAL 5000
#define AC_SENSOR_READ_TIMEOUT 250
struct ClimateCardData {
uint8_t ac_temperature;
uint8_t ac_mode;
uint8_t ac_fan_speed;
};
struct AirConditioner {
uint8_t max_temperature;
uint8_t min_temperature;
uint8_t modes;
char **mode_names;
uint8_t fan_speeds;
char **fan_speed_names;
uint16_t ****infraredCodes;
};
// This requires 3 bytes of FRAM
class ClimateCard : public ExpansionCard {
public:
ClimateCard(uint8_t ir_pin);
~ClimateCard();
bool begin(AirConditioner ac, uint8_t sensor_type, uint8_t sensor_pin);
bool begin(AirConditioner ac);
void loop();
void bindFRAM(FRAM *fram, uint16_t fram_address);
void setFRAMAutoSave(bool autoSave);
void saveStateToFRAM();
void loadStateFromFRAM();
void setTemperature(uint8_t temperature);
uint8_t getTemperature();
void setMode(uint8_t mode);
uint8_t getMode();
void setFanSpeed(uint8_t fan_speed);
uint8_t getFanSpeed();
float getRoomTemperature();
float getHumidity();
uint8_t getSensorType();
void registerChangeCallback(std::function<void(uint8_t, uint8_t, uint8_t)> callback);
void registerSensorCallback(std::function<void(float, float)> callback);
uint8_t getType();
private:
// Sensor objects
// We use pointers here because we don't know which sensor will be used
DHTNEW *dht;
DS18B20 *ds18b20;
// Callbacks
std::vector<std::function<void(uint8_t, uint8_t, uint8_t)>> callbacks;
std::vector<std::function<void(float, float)>> sensor_callbacks;
// Update functions
void updateSensor();
void updateAirConditioner();
// IR variables
IRsend irsender;
uint8_t ir_pin;
// Air conditioner variables
AirConditioner ac;
ClimateCardData state;
// Sensor variables
uint8_t sensor_type;
uint8_t sensor_pin;
float humidity;
float room_temperature;
// FRAM variables
FRAM *fram;
uint8_t fram_address;
bool fram_auto_save;
};

View file

@ -0,0 +1,132 @@
#include <ClimateIoT.hpp>
ClimateIoT::ClimateIoT() {
}
ClimateIoT::~ClimateIoT() {
// Destructor implementation
}
bool ClimateIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->card = (ClimateCard *)card;
// Reister Callbacks
auto bindedSensorCallback = std::bind(&ClimateIoT::handleSensorUpdate, this, std::placeholders::_1, std::placeholders::_2);
this->card->registerSensorCallback(bindedSensorCallback);
auto bindedAirConditionerCallback = std::bind(&ClimateIoT::handleAirConditionerUpdate, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->card->registerChangeCallback(bindedAirConditionerCallback);
return true;
}
void ClimateIoT::handleMqttMessage(char *topic, char *payload) {
uint8_t topic_length = strlen(topic);
if (this->processSetTemperatureMessage(topic, payload, topic_length))
return;
if (this->processSetModeMessage(topic, payload, topic_length))
return;
if (this->processSetFanSpeedMessage(topic, payload, topic_length))
return;
if (this->processRequestStateMessage(topic, payload, topic_length))
return;
}
void ClimateIoT::publishClimateTemperature() {
char payload[5];
itoa(this->card->getTemperature(), payload, 10);
this->publishRelative(AC_TEMPERATURE_REPORT_TOPIC, payload);
}
void ClimateIoT::publishClimateMode() {
char payload[2];
itoa(this->card->getMode(), payload, 10);
this->publishRelative(AC_MODE_REPORT_TOPIC, payload);
}
void ClimateIoT::publishClimateFanSpeed() {
char payload[2];
itoa(this->card->getFanSpeed(), payload, 10);
this->publishRelative(AC_FAN_SPEED_REPORT_TOPIC, payload);
}
void ClimateIoT::publishSensor() {
this->publishRoomTemperature();
this->publishHumidity();
}
void ClimateIoT::publishClimate() {
this->publishClimateTemperature();
this->publishClimateMode();
this->publishClimateFanSpeed();
}
void ClimateIoT::publishRoomTemperature() {
char payload[5];
itoa(this->card->getRoomTemperature(), payload, 10);
this->publishRelative(AC_ROOM_TEMPERATURE_REPORT_TOPIC, payload);
}
void ClimateIoT::publishHumidity() {
if (this->card->getSensorType() == AC_SENSOR_TYPE_DHT22) {
char payload[5];
itoa(this->card->getHumidity(), payload, 10);
this->publishRelative(AC_HUMIDITY_REPORT_TOPIC, payload);
}
}
void ClimateIoT::handleStateChange(uint8_t temperature, uint8_t mode, uint8_t fan_speed) {
this->publishClimate();
}
void ClimateIoT::publishReport() {
this->publishClimate();
this->publishSensor();
}
void ClimateIoT::subscribe() {
this->subscribeRelative(AC_TEMPERATURE_SET_TOPIC);
this->subscribeRelative(AC_MODE_SET_TOPIC);
this->subscribeRelative(AC_FAN_SPEED_SET_TOPIC);
}
void ClimateIoT::loop() {
}
uint8_t ClimateIoT::getType() {
return CARD_TYPE_CLIMATE;
}
bool ClimateIoT::processSetTemperatureMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_TEMPERATURE_SET_TOPIC)) {
uint8_t temperature = atoi(payload);
this->card->setTemperature(temperature);
return true;
}
return false;
}
bool ClimateIoT::processSetModeMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_MODE_SET_TOPIC)) {
uint8_t mode = atoi(payload);
this->card->setMode(mode);
return true;
}
return false;
}
bool ClimateIoT::processSetFanSpeedMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_FAN_SPEED_SET_TOPIC)) {
uint8_t fan_speed = atoi(payload);
this->card->setFanSpeed(fan_speed);
return true;
}
return false;
}
bool ClimateIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_REQUEST_STATE_TOPIC)) {
this->publishReport();
return true;
}
return false;
}

View file

@ -0,0 +1,42 @@
#pragma once
#include <IoTComponent.hpp>
#include <ExpansionCard.hpp>
#include <ClimateCard.hpp>
#define AC_MODE_REPORT_TOPIC "mode"
#define AC_MODE_SET_TOPIC "set/mode"
#define AC_TEMPERATURE_REPORT_TOPIC "temperature"
#define AC_TEMPERATURE_SET_TOPIC "set/temperature"
#define AC_FAN_SPEED_REPORT_TOPIC "fan_speed"
#define AC_FAN_SPEED_SET_TOPIC "set/fan_speed"
#define AC_ROOM_TEMPERATURE_REPORT_TOPIC "room_temperature"
#define AC_HUMIDITY_REPORT_TOPIC "humidity"
#define AC_REQUEST_STATE_TOPIC "request_state"
class ClimateIoT : public IoTComponent {
public:
ClimateIoT();
~ClimateIoT();
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void publishClimate();
void publishClimateTemperature();
void publishClimateMode();
void publishClimateFanSpeed();
void publishSensor();
void publishRoomTemperature();
void publishHumidity();
void handleStateChange(uint8_t temperature, uint8_t mode, uint8_t fan_speed);
void handleSensorUpdate(float temperature, float humidity);
void handleAirConditionerUpdate(uint8_t mode, uint8_t fan_speed, uint8_t temperature);
void publishReport();
void subscribe();
void loop();
uint8_t getType();
private:
ClimateCard *climate_card;
bool processSetTemperatureMessage(char *topic, char *payload, uint8_t topic_length);
bool processSetModeMessage(char *topic, char *payload, uint8_t topic_length);
bool processSetFanSpeedMessage(char *topic, char *payload, uint8_t topic_length);
bool processRequestStateMessage(char *topic, char *payload, uint8_t topic_length);
};

View file

@ -0,0 +1,207 @@
#include <DigitalInputCard.hpp>
// Instantiate the card with the specified address
DigitalInputCard::DigitalInputCard(uint8_t address_a, uint8_t address_b)
{
this->address_a = address_a;
this->address_b = address_b;
}
// 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
DigitalInputCard::DigitalInputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4, bool bit5)
{
this->address_a = 0x20;
this->address_b = 0x20;
this->inputBufferA = 0;
this->inputBufferB = 0;
if (bit0)
this->address_a += 1;
if (bit1)
this->address_a += 2;
if (bit2)
this->address_a += 4;
if (bit3)
this->address_b += 1;
if (bit4)
this->address_b += 2;
if (bit5)
this->address_b += 4;
}
// Initialize the card
bool DigitalInputCard::begin()
{
this->inputBankA = PCF8574(this->address_a);
this->inputBankB = PCF8574(this->address_b);
if (!this->inputBankA.begin()) {
Serial.println("Input Card ERROR: Failed to install input bank A");
return false;
}
if (!this->inputBankB.begin()) {
Serial.println("Input Card ERROR: Failed to install input bank B");
return false;
}
// Set the debounce time for all pins to 50ms
for (int i = 0; i < 16; i++)
{
this->debounceTime[i] = 50;
this->lastDebounceTime[i] = 0;
}
// Initialize the pin map to the default values
for (int i = 0; i < 16; i++)
{
this->pinMap[i] = i;
this->virtualPinMap[i] = i;
}
return true;
}
// Refresh and Read the input from the specified pin, always refresh the input buffers
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
bool DigitalInputCard::digitalRead(uint8_t pin, bool refresh)
{
pin = pinMap[pin];
// First check if the pin is in bank A or B
if (pin >= 0 && pin <= 7)
{
// Refresh the input buffers if refresh is true
if (refresh)
refreshInputBankA();
// Extract the bit from the buffer
return ((inputBufferA >> (7 - pin)) & 1);
}
else if (pin >= 8 && pin <= 15)
{
// Refresh the input buffers if refresh is true
if (refresh)
refreshInputBankB();
// Extract the bit from the buffer
return ((inputBufferB >> (15 - pin)) & 1);
}
return 255;
}
void DigitalInputCard::handlePinChange(int pin, uint8_t &currentBuffer, uint8_t &previousBuffer)
{
// Get the index of the pin in the pin map
uint8_t virtualPin = virtualPinMap[pin];
// Handle Bank A
if (((previousBuffer >> (7 - pin)) & 1) != ((currentBuffer >> (7 - pin)) & 1))
{
if (millis() - lastDebounceTime[pin] > debounceTime[pin])
{
lastDebounceTime[pin] = millis();
previousBuffer ^= (-((currentBuffer >> (7 - pin)) & 1) ^ previousBuffer) & (1UL << (7 - pin));
for (int i = 0; i < callbacks.size(); i++)
callbacks[i](virtualPin, ((currentBuffer >> (7 - pin)) & 1));
}
}
// Handle Bank B
if (((previousBuffer >> (15 - pin)) & 1) != ((currentBuffer >> (15 - pin)) & 1))
{
if (millis() - lastDebounceTime[pin] > debounceTime[pin])
{
lastDebounceTime[pin] = millis();
previousBuffer ^= (-((currentBuffer >> (15 - pin)) & 1) ^ previousBuffer) & (1UL << (15 - pin));
for (int i = 0; i < callbacks.size(); i++)
callbacks[i](virtualPin, ((currentBuffer >> (15 - pin)) & 1));
}
}
}
// Preform a loop to refresh the input buffers
void DigitalInputCard::loop()
{
// Refresh the input buffers
refreshInputBankA();
refreshInputBankB();
// Iterate over all pins and check if they changed
for (int i = 0; i < 16; i++)
{
// Check which bank the pin is in
if (i < 8)
{
handlePinChange(i, inputBufferA, previousInputBufferA);
}
else if (i >= 8 && i <= 15)
{
handlePinChange(i, inputBufferB, previousInputBufferB);
}
}
}
// Get the input buffer for bank A
uint8_t DigitalInputCard::getInputBufferA()
{
// Rearrange the bits to match the pin map
uint8_t inputBufferA_rearranged = 0;
for (int i = 0; i < 8; i++)
{
inputBufferA_rearranged |= ((inputBufferA >> i) & 1) << (7 - i);
}
return inputBufferA_rearranged;
}
// Get the input buffer for bank B
uint8_t DigitalInputCard::getInputBufferB()
{
// Rearrange the bits to match the pin map
uint8_t inputBufferB_rearranged = 0;
for (int i = 0; i < 8; i++)
{
inputBufferB_rearranged |= ((inputBufferB >> i) & 1) << (7 - i);
}
return inputBufferB_rearranged;
}
// Register a callback function to be called when a pin changes
void DigitalInputCard::registerCallback(std::function<void(uint8_t, bool)> callback)
{
callbacks.push_back(callback);
}
// Refresh the input buffer for bank A
void DigitalInputCard::refreshInputBankA()
{
inputBufferA = inputBankA.read8();
}
// Refresh the input buffer for bank B
void DigitalInputCard::refreshInputBankB()
{
inputBufferB = inputBankB.read8();
}
void DigitalInputCard::setDebounceTime(uint8_t pin, uint32_t debounceTime)
{
pin = pinMap[pin];
this->debounceTime[pin] = debounceTime;
}
// void DigitalInputCard::unregisterCallback(std::function<void(uint8_t, bool)> callback)
// {
// for (int i = 0; i < callbacks.size(); i++)
// {
// if (callbacks[i].target<void(uint8_t, bool)>() == callback.target<void(uint8_t, bool)>())
// {
// callbacks.erase(callbacks.begin() + i);
// break;
// }
// }
// }
void DigitalInputCard::loadPinMap(uint8_t pinMap[16])
{
for (int i = 0; i < 16; i++)
{
// Load the pin map (physical pin to virtual pin)
this->pinMap[i] = pinMap[i];
// Load the virtual pin map (virtual pin to physical pin)
this->virtualPinMap[pinMap[i]] = i;
}
}
uint8_t DigitalInputCard::getType()
{
return CARD_TYPE_DIGITAL_INPUT;
}

View file

@ -0,0 +1,55 @@
#pragma once
#include <ExpansionCard.hpp>
#include <PCF8574.h>
#include <vector>
#define CARD_TYPE_DIGITAL_INPUT 0x01
class DigitalInputCard : public ExpansionCard {
public:
// Instantiate the card with the specified address
DigitalInputCard(uint8_t address_a, uint8_t address_b);
// Instantiate the card with the specified position on the dip switch
DigitalInputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4, bool bit5);
// Initialize the card
bool begin();
// Refresh and Read the input from the specified pin, always refresh the input buffers
bool digitalRead(uint8_t pin);
// Read the input from the specified pin, also refresh the input buffers if refresh is true
bool digitalRead(uint8_t pin, bool refresh);
// Preform a loop to refresh the input buffers
void loop();
// Get the input buffer for bank A
uint8_t getInputBufferA();
// Get the input buffer for bank B
uint8_t getInputBufferB();
// Set the debounce time for the specified pin
void setDebounceTime(uint8_t pin, uint32_t debounceTime);
// Register a callback function to be called when a pin changes
void registerCallback(std::function<void(uint8_t, bool)> callback);
// Unregister the callback function
//void unregisterCallback(std::function<void(uint8_t, bool)> callback);
// Load a new pin map
void loadPinMap(uint8_t pinMap[16]);
// Get type of card
uint8_t getType();
private:
PCF8574 inputBankA;
PCF8574 inputBankB;
uint8_t address_a;
uint8_t address_b;
uint8_t inputBufferA;
uint8_t inputBufferB;
uint8_t previousInputBufferA;
uint8_t previousInputBufferB;
uint32_t debounceTime[16];
uint32_t lastDebounceTime[16];
// A map of the physical pin to the virtual pin
uint8_t pinMap[16];
// A map of the virtual pin to the physical pin
uint8_t virtualPinMap[16];
std::vector<std::function<void(uint8_t, bool)>> callbacks;
void refreshInputBankA();
void refreshInputBankB();
void handlePinChange(int pin, uint8_t& currentBuffer, uint8_t& previousBuffer);
};

View file

@ -0,0 +1,68 @@
#include <DigitalInputIoT.hpp>
bool DigitalInputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->card = (DigitalInputCard *)card;
this->card_id = card_id;
this->mqtt = mqtt;
this->base_topic = base_topic;
this->setDigitalInputsPublishEnabled(true);
this->card->registerCallback(std::bind(&DigitalInputIoT::handleValueChange, this, std::placeholders::_1, std::placeholders::_2));
return true;
}
void DigitalInputIoT::subscribe() {
char topic[64];
sprintf(topic, "%s/%d/%s", this->base_topic, this->card_id, PUBLISH_ENABLE_TOPIC);
this->subscribeRelative(topic);
}
void DigitalInputIoT::handleMqttMessage(char *topic, char *payload) {
// payload is char '0' or '1'
if (!strcmp(topic, PUBLISH_ENABLE_TOPIC)) {
if (payload[0] == '1') {
this->setDigitalInputsPublishEnabled(true);
} else {
this->setDigitalInputsPublishEnabled(false);
}
}
}
void DigitalInputIoT::publishDigitalInputs() {
if (!this->digital_inputs_publish_enabled) {
return;
}
for (int i = 0; i < 16; i++) {
this->publishDigitalInput(i);
}
}
void DigitalInputIoT::setDigitalInputsPublishEnabled(bool enabled) {
this->digital_inputs_publish_enabled = enabled;
if (enabled) {
this->publishDigitalInputs();
}
}
void DigitalInputIoT::handleValueChange(uint8_t pin, uint8_t value) {
if (this->digital_inputs_publish_enabled) {
this->publishDigitalInput(pin);
}
}
void DigitalInputIoT::publishReport() {
this->publishDigitalInputs();
}
uint8_t DigitalInputIoT::getType() {
return CARD_TYPE_DIGITAL_INPUT;
}
void DigitalInputIoT::publishDigitalInput(uint8_t pin) {
char topic[20] = {0};
char payload[20] = {0};
topic[0] = pin-pin%10 + '0';
topic[1] = pin%10 + '0';
topic[2] = '\0';
payload[0] = this->card->digitalRead(pin, false) + '0';
payload[1] = '\0';
this->publishRelative(topic, payload);
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <IoTComponent.hpp>
#include <DigitalInputCard.hpp>
#include <FRAM.h>
#define PUBLISH_ENABLE_TOPIC "publish_enable"
class DigitalInputIoT : public IoTComponent {
public:
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void publishDigitalInputs();
void publishDigitalInput(uint8_t pin);
void setDigitalInputsPublishEnabled(bool enabled);
void handleValueChange(uint8_t pin, uint8_t value);
void publishReport();
void subscribe();
uint8_t getType();
private:
bool digital_inputs_publish_enabled = false;
DigitalInputCard *card;
};

View file

@ -0,0 +1,178 @@
#include <DigitalOutputCard.hpp>
DigitalOutputCard::DigitalOutputCard(uint8_t address) {
this->address = address;
// load default pin map
for (int i = 0; i < 16; i++) {
this->pinMap[i] = i;
this->virtualPinMap[i] = i;
}
}
// Instantiate the card with the specified position on the dip switch
DigitalOutputCard::DigitalOutputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4) {
this->address = 0x20;
if (bit0) this->address += 1;
if (bit1) this->address += 2;
if (bit2) this->address += 4;
if (bit3) this->address += 8;
if (bit4) this->address += 16;
}
// Initialize the card
bool DigitalOutputCard::begin() {
this->pwm = Adafruit_PWMServoDriver(this->address);
this->pwm.begin();
pwm.setOutputMode(true);
// Output card don't send ack, we can't check if it's connected
// so we just return true
return true;
}
// Set the output to the specified state
void DigitalOutputCard::digitalWrite(uint8_t pin, bool state) {
this->pwm.setPin(virtualPinMap[pin], state ? 4095 : 0);
this->state_buffer[pin] = state;
this->value_buffer[pin] = state ? 4095 : 0;
if (this->framAutoSave) {
this->saveStateToFRAM();
this->savePinValueToFRAM(pin);
}
for (int i = 0; i < change_callbacks.size(); i++) {
change_callbacks[i](pin, state, state ? 4095 : 0);
}
}
// Set the output to the specified pwm value
void DigitalOutputCard::analogWrite(uint8_t pin, uint16_t value) {
// If value is greater than 4095, set it to 4095
if (value > 4095) value = 4095;
// Set the pwm value
this->pwm.setPin(virtualPinMap[pin], value);
if (this->framAutoSave) {
this->saveStateToFRAM();
this->savePinValueToFRAM(pin);
}
this->state_buffer[pin] = value > 0;
this->value_buffer[pin] = value;
for (int i = 0; i < change_callbacks.size(); i++) {
change_callbacks[i](pin, value > 0, value);
}
}
// Dummy loop function
void DigitalOutputCard::loop() {
}
// Get the state of the specified pin
bool DigitalOutputCard::getState(uint8_t pin) {
return this->state_buffer[pin];
}
// Get the pwm value of the specified pin
uint16_t DigitalOutputCard::getValue(uint8_t pin) {
return this->value_buffer[pin];
}
// Get type of card
uint8_t DigitalOutputCard::getType() {
return CARD_TYPE_DIGITAL_OUTPUT;
}
void DigitalOutputCard::setState(uint8_t pin, bool state) {
this-> state_buffer[pin] = state;
this->pwm.setPin(pin, state*value_buffer[pin]);
if(this->framAutoSave) {
this->saveStateToFRAM();
}
for(int i = 0; i < change_callbacks.size(); i++) {
change_callbacks[i](pin, state, value_buffer[pin]);
}
}
void DigitalOutputCard::setValue(uint8_t pin, uint16_t value) {
// If value is greater than 4095, set it to 4095
if (value > 4095) value = 4095;
this-> value_buffer[pin] = value;
this->pwm.setPin(pin, state_buffer[pin]*value);
if (this->framAutoSave) {
this->savePinValueToFRAM(pin);
}
for (int i = 0; i < change_callbacks.size(); i++) {
change_callbacks[i](pin, state_buffer[pin], value);
}
}
void DigitalOutputCard::registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback) {
this->change_callbacks.push_back(callback);
}
// void DigitalOutputCard::deregisterChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback) {
// for(int i = 0; i < change_callbacks.size(); i++) {
// if(change_callbacks[i].target<void(uint8_t, bool, uint16_t)>() == callback.target<void(uint8_t, bool, uint16_t)>()) {
// change_callbacks.erase(change_callbacks.begin()+i);
// break;
// }
// }
// }
void DigitalOutputCard::loadPinMap(uint8_t pinMap[16]) {
for(int i = 0; i < 16; i++) {
this->pinMap[i] = pinMap[i];
this->virtualPinMap[pinMap[i]] = i;
}
}
void DigitalOutputCard::bindFRAM(FRAM *fram, uint16_t address) {
this->fram = fram;
this->framBinded = true;
this->framAddress = address;
}
uint16_t DigitalOutputCard::packStates() {
uint16_t packed = 0;
for(int i = 0; i < 16; i++) {
packed |= (state_buffer[i] << i);
}
return packed;
}
void DigitalOutputCard::unpackStates(uint16_t states) {
for(int i = 0; i < 16; i++) {
this->setState(i, (states >> i) & 1);
}
}
void DigitalOutputCard::saveToFRAM() {
if(!framBinded) return;
// Save the state
uint16_t packed = packStates();
this->fram->write16(framAddress, packed);
// Save the value
this->fram->write(framAddress+2, (uint8_t*)value_buffer, 32);
}
void DigitalOutputCard::loadFromFRAM() {
if(!framBinded) return;
// Load the state
uint16_t packed = this->fram->read16(framAddress);
unpackStates(packed);
// Load the value
uint16_t value[16];
this->fram->read(framAddress+2, (uint8_t*)value, 32);
for(int i = 0; i < 16; i++) {
this->setValue(i, value[i]);
}
}
void DigitalOutputCard::setAutoSaveToFRAM(bool autoSave) {
this->framAutoSave = autoSave;
}
void DigitalOutputCard::savePinValueToFRAM(uint8_t pin) {
if(!framBinded) return;
this->fram->write(framAddress+2+pin*2, (uint8_t*)&value_buffer[pin], 2);
}
void DigitalOutputCard::saveStateToFRAM() {
if(!framBinded) return;
uint16_t packed = packStates();
this->fram->write16(framAddress, packed);
}

View file

@ -0,0 +1,102 @@
#pragma once
#include <ExpansionCard.hpp>
#include <Adafruit_PWMServoDriver.h>
#include <FRAM.h>
#include <vector>
// 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
#define SET_STATE_TOPIC "/set/state"
#define SET_VALUE_TOPIC "/set/value"
#define STATE_TOPIC "/state"
#define VALUE_TOPIC "/value"
#define REQUEST_STATE_TOPIC "requeststate"
#define PUBLISH_ENABLE_TOPIC "publish_enable"
#define CARD_TYPE_DIGITAL_OUTPUT 0x00
class DigitalOutputCard : public ExpansionCard
{
public:
// Instantiate the card with the specified address
DigitalOutputCard(uint8_t address);
// Instantiate the card with the specified position on the dip switch
DigitalOutputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4);
// Initialize the card
bool begin();
// Dummy loop function
void loop();
// Set the output to the specified state
// This function set both the state and the pwm value
void digitalWrite(uint8_t pin, bool state);
// Set the output to the specified pwm value
// This function set both the state and the pwm value
void analogWrite(uint8_t pin, uint16_t value);
// Set the state of the specified pin
void setState(uint8_t pin, bool state);
// Set the pwm value of the specified pin
void setValue(uint8_t pin, uint16_t value);
// Get the state of the specified pin
bool getState(uint8_t pin);
// Get the pwm value of the specified pin
uint16_t getValue(uint8_t pin);
// Register a callback function that will be called when the state of a pin changes
void registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
// Deregister the callback function
// void deregisterChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
// Load a new pin map
void loadPinMap(uint8_t pinMap[16]);
// Bind the fram object to the card
// The Output card use the fram in this layout:
// [2 bytes] 0-1 : state
// [32 bytes] 2-33 : value
void bindFRAM(FRAM *fram, uint16_t address);
// Save the state and value to the fram
void saveToFRAM();
// Load the state and value from the fram
void loadFromFRAM();
// Set the auto save to fram flag
void setAutoSaveToFRAM(bool autoSave);
// Save a single pin value to fram
void savePinValueToFRAM(uint8_t pin);
// Save state to fram
void saveStateToFRAM();
// Save value to fram
void saveValueToFRAM();
// Get type of card
uint8_t getType();
private:
// FRAM address
uint16_t framAddress;
// FRAM is binded
bool framBinded = false;
// Auto save to fram
bool framAutoSave = false;
// The fram object pointer
FRAM *fram;
// The pwm driver
Adafruit_PWMServoDriver pwm;
// The address of the card
uint8_t address;
// The state of the card
bool state_buffer[16];
// The pwm value of the card
uint16_t value_buffer[16];
// The callback function
std::vector<std::function<void(uint8_t, bool, uint16_t)>> change_callbacks;
// Physical pin to virtual pin map
uint8_t pinMap[16];
// Return 16 bit value representing all 16 channels
uint16_t packStates();
// Unpack the 16 bit value to the state buffer
void unpackStates(uint16_t states);
// Virtual pin to physical pin map
uint8_t virtualPinMap[16];
};

View file

@ -0,0 +1,197 @@
#include <DigitalOutputIoT.hpp>
DigitalOutputIoT::DigitalOutputIoT() {
this->state_report_topic = new char[10];
this->value_report_topic = new char[10];
this->digital_outputs_publish_enabled = true;
}
DigitalOutputIoT::~DigitalOutputIoT() {
delete[] this->state_report_topic;
delete[] this->value_report_topic;
}
bool DigitalOutputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->mqtt = mqtt;
this->base_topic = base_topic;
this->card = (DigitalOutputCard *) card;
this-> card_id = card_id;
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);
this->request_state_length = strlen(REQUEST_STATE_TOPIC);
this->publish_enable_length = strlen(PUBLISH_ENABLE_TOPIC);
strcpy(this->state_report_topic, "00/state");
strcpy(this->value_report_topic, "00/value");
// Register callbacks
auto bindedCallback = std::bind(&DigitalOutputIoT::handleValueChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->card->registerChangeCallback(bindedCallback);
return true;
}
// 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
void DigitalOutputIoT::handleMqttMessage(char *topic, char *payload) {
uint8_t topic_length = strlen(topic);
if(this-> processSetStateMessage(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) {
// 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 /
if(topic[2] != '/') {
return false;
}
// The topic must be set_state_length + 2 characters long
if(topic_length != set_state_length + 2) {
return false;
}
// Check if the topic ends with /set/state
if (!strncmp(topic+2, SET_STATE_TOPIC, set_state_length)) {
// Get the pin number
uint8_t pin = (topic[0] - '0')*10 + (topic[1] - '0');
// Get the state
bool state = false;
char state_char = payload[0];
if (state_char == '0') {
state = false;
} else if (state_char == '1') {
state = true;
} else {
return false;
}
// Set the state
card->setState(pin, state);
return true;
}
return false;
}
bool DigitalOutputIoT::processSetValueMessage(char *topic, char *payload, uint8_t topic_length) {
// 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 /
if(topic[2] != '/') {
return false;
}
// The topic must be set_value_length + 2 characters long
if(topic_length != set_value_length + 2) {
return false;
}
// Check if the topic ends with /set/value
if (!strncmp(topic+2, SET_VALUE_TOPIC, set_value_length)) {
// Get the pin number
uint8_t pin = (topic[0] - '0')*10 + (topic[1] - '0');
// Get the value
uint16_t value = atoi(payload);
// Set the value
card->setValue(pin, value);
return true;
}
return false;
}
bool DigitalOutputIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) {
// 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
if(topic_length != request_state_length) {
return false;
}
// Check if the topic is requeststate
if (!strncmp(topic, REQUEST_STATE_TOPIC, request_state_length)) {
// Publish the state of all pins
publishDigitalOutputs();
return true;
}
return false;
}
void DigitalOutputIoT::publishDigitalOutputs() {
if(!digital_outputs_publish_enabled) return;
for(int i = 0; i < 16; i++) {
publishDigitalOutput(i);
}
}
void DigitalOutputIoT::publishDigitalOutput(uint8_t pin) {
publishDigitalOutputState(pin);
publishDigitalOutputValue(pin);
}
void DigitalOutputIoT::publishDigitalOutputState(uint8_t pin) {
if(!digital_outputs_publish_enabled) return;
state_report_topic[0] = pin / 10 + '0';
state_report_topic[1] = pin % 10 + '0';
publishRelative(state_report_topic, card->getState(pin) ? "1" : "0");
}
void DigitalOutputIoT::publishDigitalOutputValue(uint8_t pin) {
if(!digital_outputs_publish_enabled) return;
value_report_topic[0] = pin / 10 + '0';
value_report_topic[1] = pin % 10 + '0';
char payload[5];
sprintf(payload, "%d", card->getValue(pin));
publishRelative(value_report_topic, payload);
}
void DigitalOutputIoT::setDigitalOutputsPublishEnabled(bool enabled) {
digital_outputs_publish_enabled = enabled;
}
void DigitalOutputIoT::handleValueChange(uint8_t pin, bool state, uint16_t value) {
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;
}
void DigitalOutputIoT::deregisterChangeCallback() {
value_change_callback = NULL;
}
void DigitalOutputIoT::publishReport() {
publishDigitalOutputs();
}
uint8_t DigitalOutputIoT::getType() {
return CARD_TYPE_DIGITAL_OUTPUT;
}
void DigitalOutputIoT::subscribe() {
char topic[20];
// Subscribe to all set state topics
for(int i = 0; i < 16; i++) {
sprintf(topic, "%02d/set/state", i);
subscribeRelative(topic);
}
// Subscribe to all set value topics
for(int i = 0; i < 16; i++) {
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);
}
void DigitalOutputIoT::loop() {
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <IoTComponent.hpp>
#include <ExpansionCard.hpp>
#include <DigitalOutputCard.hpp>
class DigitalOutputIoT : public IoTComponent {
public:
DigitalOutputIoT();
~DigitalOutputIoT();
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void publishDigitalOutputs();
void publishDigitalOutput(uint8_t pin);
void publishDigitalOutputState(uint8_t pin);
void publishDigitalOutputValue(uint8_t pin);
void setDigitalOutputsPublishEnabled(bool enabled);
void handleValueChange(uint8_t pin, bool state, uint16_t value);
void registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
void deregisterChangeCallback();
void publishReport();
void subscribe();
void loop();
uint8_t getType();
private:
bool digital_outputs_publish_enabled = false;
bool processSetStateMessage(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);
std::function<void(uint8_t, bool, uint16_t)> value_change_callback;
DigitalOutputCard *card;
char *state_report_topic;
char *value_report_topic;
uint8_t set_state_length;
uint8_t set_value_length;
uint8_t state_length;
uint8_t value_length;
uint8_t request_state_length;
uint8_t publish_enable_length;
};

View file

@ -0,0 +1,348 @@
#include <ESPMegaDisplay.hpp>
/**
* @brief Receives and processes serial commands from the display adapter.
* @param process Flag indicating whether to process the received commands.
* @return True if data is received, false otherwise.
*/
bool ESPMegaDisplay::recieveSerialCommand(bool process){
bool dataRecieved = false;
// Read the serial buffer if available
while(displayAdapter->available()) {
rx_buffer[rx_buffer_index] = displayAdapter->read();
rx_buffer_index++;
// Check for overflow
if(rx_buffer_index>=256){
rx_buffer_index = 0;
}
if(process) this-> processSerialCommand();
dataRecieved = true;
}
return dataRecieved;
}
/**
* @brief Receives and processes serial commands from the display adapter.
* @return True if data is received, false otherwise.
*/
bool ESPMegaDisplay::recieveSerialCommand(){
return recieveSerialCommand(true);
}
/**
* @brief Processes the received serial command.
*/
void ESPMegaDisplay::processSerialCommand(){
// Check if the rx buffer ended with stop bytes (0xFF 0xFF 0xFF)
if(!payloadIsValid()) return;
// The rx buffer ended with stop bytes
// Check if the rx buffer is a push payload
// The payload type is the first byte of the payload
// 0x00 is invalid instruction
// 0x01 is success
// 0x65 is a touch event
// 0x66 is a page report event
if(rx_buffer[0]==0x65){
processTouchPayload();
}
else if(rx_buffer[0]==0x66){
processPageReportPayload();
}
}
/**
* @brief Processes the touch event payload.
*/
void ESPMegaDisplay::processTouchPayload() {
if (rx_buffer_index != 4) return;
// The second byte of the payload is the page number
uint8_t page = rx_buffer[1];
// The third byte of the payload is the component id
uint8_t component = rx_buffer[2];
// The fourth byte of the payload is the event
uint8_t event = rx_buffer[3];
// 0x01 is press, 0x00 is release
}
/**
* @brief Processes the page report event payload.
*/
void ESPMegaDisplay::processPageReportPayload() {
// The second byte of the payload is the page number
this->currentPage = rx_buffer[1];
if(pageChangeCallback!=NULL){
pageChangeCallback(this->currentPage);
}
rx_buffer_index = 0;
}
/**
* @brief Sends stop bytes to the display adapter.
*/
void ESPMegaDisplay::sendStopBytes() {
displayAdapter->write(0xFF);
displayAdapter->write(0xFF);
displayAdapter->write(0xFF);
}
/**
* @brief Sends a command to the display adapter.
* @param command The command to send.
*/
void ESPMegaDisplay::sendCommand(char* command) {
displayAdapter->print(command);
sendStopBytes();
}
/**
* @brief Jumps to the specified page on the display.
* @param page The page number to jump to.
*/
void ESPMegaDisplay::jumpToPage(int page) {
this->displayAdapter->print("page ");
this->displayAdapter->print(page);
sendStopBytes();
}
/**
* @brief Sets the value of a number component on the display.
* @param component The component name.
* @param value The value to set.
*/
void ESPMegaDisplay::setNumber(const char* component, int value) {
this->displayAdapter->print(component);
this->displayAdapter->print("=");
this->displayAdapter->print(value);
sendStopBytes();
}
/**
* @brief Sets the value of a string component on the display.
* @param component The component name.
* @param value The value to set.
*/
void ESPMegaDisplay::setString(const char* component, const char* value) {
this->displayAdapter->print(component);
this->displayAdapter->print("=\"");
this->displayAdapter->print(value);
this->displayAdapter->print("\"");
sendStopBytes();
}
/**
* @brief Gets the value of a number component from the display.
* @param component The component name.
* @return The value of the number component.
*/
uint32_t ESPMegaDisplay::getNumber(const char* component) {
uint32_t start = millis();
// Send the get command
this->displayAdapter->print("get ");
this->displayAdapter->print(component);
sendStopBytes();
// Wait for the response
if(!waitForValidPayload(DISPLAY_FETCH_TIMEOUT)) return 0;
// The rx buffer is valid
// The expected payload is type 0x71
if(rx_buffer[0]!=0x71) return 0;
// The 2nd to 5th byte of the payload is the value
// It's a 4 byte 32-bit value in little endian order.
// Convert the 4 bytes to a 32-bit value
uint32_t value = 0;
value |= rx_buffer[1];
value |= rx_buffer[2]<<8;
value |= rx_buffer[3]<<16;
value |= rx_buffer[4]<<24;
return value;
}
/**
* @brief Gets the value of a string component from the display.
* @param component The component name.
* @return The value of the string component.
* @note The returned char array must be freed after use.
*/
const char* ESPMegaDisplay::getString(const char* component) {
uint32_t start = millis();
// Send the get command
this->displayAdapter->print("get ");
this->displayAdapter->print(component);
sendStopBytes();
// Wait for the response
if(!waitForValidPayload(DISPLAY_FETCH_TIMEOUT)) return "";
// The rx buffer is valid
// The expected payload is type 0x70
if(rx_buffer[0]!=0x70) return "";
// The 2nd bytes onwards before the stop bytes is the string
// The length of the string is the length of the payload minus 4
uint8_t length = rx_buffer_index-4;
// First we malloc a char array with the length of the string
char* value = (char*)malloc(length+1);
// Copy the string from the rx buffer to the char array
memcpy(value, rx_buffer+1, length);
// Add the null terminator
value[length] = '\0';
// Return the char array
return value;
}
bool ESPMegaDisplay::getStringToBuffer(const char* component, char* buffer, uint8_t buffer_size) {
uint32_t start = millis();
// Send the get command
this->displayAdapter->print("get ");
this->displayAdapter->print(component);
sendStopBytes();
// Wait for the response
if(!waitForValidPayload(DISPLAY_FETCH_TIMEOUT)) return false;
// The rx buffer is valid
// The expected payload is type 0x70
if(rx_buffer[0]!=0x70) return false;
// The 2nd bytes onwards before the stop bytes is the string
// The length of the string is the length of the payload minus 4
uint8_t length = rx_buffer_index-4;
// Check if the buffer is large enough to hold the string
if(length>buffer_size) return false;
// Copy the string from the rx buffer to the char array
memcpy(buffer, rx_buffer+1, length);
// Add the null terminator
buffer[length] = '\0';
return true;
}
/**
* @brief Waits for a valid payload from the display adapter.
* @param timeout The timeout value in milliseconds.
* @return True if a valid payload is received, false otherwise.
*/
bool ESPMegaDisplay::waitForValidPayload(uint32_t timeout) {
uint32_t start = millis();
// If the payload is not valid, keep reading the serial buffer until timeout
while(!this->payloadIsValid()){
if(millis()-start>timeout){
return false;
}
recieveSerialCommand(false);
}
return true;
}
/**
* @brief Checks if the received payload is valid.
* @return True if the payload is valid, false otherwise.
*/
bool ESPMegaDisplay::payloadIsValid() {
// Check if the rx buffer ended with stop bytes (0xFF 0xFF 0xFF)
if(rx_buffer_index<3) return false;
if(rx_buffer[rx_buffer_index-1]!=0xFF) return false;
if(rx_buffer[rx_buffer_index-2]!=0xFF) return false;
if(rx_buffer[rx_buffer_index-3]!=0xFF) return false;
return true;
}
/**
* @brief Sets the brightness of the display.
* @param value The brightness value.
*/
void ESPMegaDisplay::setBrightness(int value) {
this->displayAdapter->print("dim=");
this->displayAdapter->print(value);
sendStopBytes();
}
/**
* @brief Sets the volume of the display.
* @param value The volume value.
*/
void ESPMegaDisplay::setVolume(int value) {
this->displayAdapter->print("vol=");
this->displayAdapter->print(value);
sendStopBytes();
}
/**
* @brief Restarts the display.
*/
void ESPMegaDisplay::reset() {
// First we send a stop bytes to clear the serial buffer
// This ensures that the display is ready to receive the reset command
sendStopBytes();
this->displayAdapter->print("rest");
sendStopBytes();
}
/**
* @brief Registers a callback function that will be called when a push event is received.
* @param callback The callback function.
*/
void ESPMegaDisplay::registerPushCallback(std::function<void(uint8_t, uint8_t)> callback) {
this->pushCallback = callback;
}
/**
* @brief Registers a callback function that will be called when a pop event is received.
* @param callback The callback function.
*/
void ESPMegaDisplay::registerPopCallback(std::function<void(uint8_t, uint8_t)> callback) {
this->popCallback = callback;
}
/**
* @brief Registers a callback function that will be called when a page change event is received.
* @param callback The callback function.
*/
void ESPMegaDisplay::registerPageChangeCallback(std::function<void(uint8_t)> callback) {
this->pageChangeCallback = callback;
}
/**
* @brief Unregisters the push callback function.
*/
void ESPMegaDisplay::unregisterPushCallback() {
this->pushCallback = NULL;
}
/**
* @brief Unregisters the pop callback function.
*/
void ESPMegaDisplay::unregisterPopCallback() {
this->popCallback = NULL;
}
/**
* @brief Unregisters the page change callback function.
*/
void ESPMegaDisplay::unregisterPageChangeCallback() {
this->pageChangeCallback = NULL;
}
/**
* @brief Constructor for the ESPMegaDisplay class.
* @param displayAdapter The serial adapter connected to the display.
*/
ESPMegaDisplay::ESPMegaDisplay(HardwareSerial *displayAdapter) {
this->displayAdapter = displayAdapter;
this->currentPage = 0;
this->rx_buffer_index = 0;
this->pushCallback = NULL;
this->popCallback = NULL;
this->pageChangeCallback = NULL;
}
/**
* @brief Initializes the display.
*/
void ESPMegaDisplay::begin() {
this->displayAdapter->begin(115200);
this->displayAdapter->setTimeout(100);
this->displayAdapter->flush();
this->reset();
}
/**
* @brief The main loop function of the display.
*/
void ESPMegaDisplay::loop() {
// Check if there is data in the serial buffer
// If there is data, process the data
recieveSerialCommand();
}

View file

@ -0,0 +1,47 @@
#pragma once
#include <Arduino.h>
#define DISPLAY_FETCH_TIMEOUT 100 // ms
class ESPMegaDisplay
{
public:
ESPMegaDisplay(HardwareSerial *displayAdapter);
void begin();
void loop();
void reset();
void setBrightness(int value);
void setVolume(int value);
void jumpToPage(int page);
void setString(const char* component, const char* value);
void setNumber(const char* component, int value);
const char* getString(const char* component);
bool getStringToBuffer(const char* component, char* buffer, uint8_t buffer_size);
uint32_t getNumber(const char* component);
void handlePwmStateChange(uint8_t pin, uint16_t value);
void handleInputStateChange(uint8_t pin, bool state);
void registerPushCallback(std::function<void(uint8_t, uint8_t)> callback);
void registerPopCallback(std::function<void(uint8_t, uint8_t)> callback);
void registerPageChangeCallback(std::function<void(uint8_t)> callback);
void unregisterPushCallback();
void unregisterPopCallback();
void unregisterPageChangeCallback();
protected:
uint8_t currentPage;
uint8_t rx_buffer_index;
char rx_buffer[256];
char tx_buffer[256];
bool recieveSerialCommand();
bool recieveSerialCommand(bool process);
void processSerialCommand();
void processTouchPayload();
void processPageReportPayload();
void sendStopBytes();
void sendCommand(char* command);
bool payloadIsValid();
bool waitForValidPayload(uint32_t timeout);
HardwareSerial *displayAdapter;
std::function<void(uint8_t, uint8_t)> pushCallback;
std::function<void(uint8_t, uint8_t)> popCallback;
std::function<void(uint8_t)> pageChangeCallback;
};

View file

@ -0,0 +1,409 @@
#include <ESPMegaIoT.hpp>
#include <ETH.h>
#define NETWORK_CONFIG_ADDRESS 34
#define MQTT_CONFIG_ADDRESS 34 + sizeof(NetworkConfig)
ESPMegaIoT::ESPMegaIoT() : mqtt(tcpClient)
{
tcpClient.setTimeout(1);
// Initialize the components array
for (int i = 0; i < 255; i++)
{
components[i] = NULL;
}
active = false;
mqtt_connected = false;
}
ESPMegaIoT::~ESPMegaIoT()
{
}
void ESPMegaIoT::mqttCallback(char *topic, byte *payload, unsigned int length)
{
// Create a null terminated string from the payload
memcpy(payload_buffer, payload, length);
payload_buffer[length] = '\0';
// Remove the base topic from the topic
char *topic_without_base = topic + strlen(base_topic) + 1;
if (user_relative_mqtt_callback != NULL)
{
user_relative_mqtt_callback(topic_without_base + 3, payload_buffer);
}
if (user_mqtt_callback != NULL)
{
user_mqtt_callback(topic, payload_buffer);
}
// Call the respective card's mqtt callback
// Note that after the base topic, there should be the card id
// /base_topic/card_id/...
// First, get the card id in integer form
char *card_id_str = strtok(topic_without_base, "/");
uint8_t card_id = atoi(card_id_str);
// Check if the card is registered
if (components[card_id] == NULL)
{
return;
}
components[card_id]->handleMqttMessage(topic_without_base + 3, payload_buffer);
}
void ESPMegaIoT::setBaseTopic(char *base_topic)
{
strcpy(this->base_topic, base_topic);
base_topic_length = strlen(base_topic);
}
void ESPMegaIoT::intr_begin(ExpansionCard *cards[])
{
this->cards = cards;
active = true;
}
void ESPMegaIoT::loop()
{
if (!active)
return;
// Call each component's loop function
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->loop();
}
}
mqtt.loop();
sessionKeepAlive();
}
// Register Existing Card for use with IoT
void ESPMegaIoT::registerCard(uint8_t card_id)
{
// Check if the card is already registered
if (components[card_id] != NULL)
{
return;
}
// Get the card type
uint8_t card_type = cards[card_id]->getType();
// Create the respective IoT component
switch (card_type)
{
case CARD_TYPE_ANALOG:
components[card_id] = new AnalogIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
break;
case CARD_TYPE_DIGITAL_INPUT:
components[card_id] = new DigitalInputIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
break;
case CARD_TYPE_DIGITAL_OUTPUT:
components[card_id] = new DigitalOutputIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
break;
// case CARD_TYPE_CLIMATE:
// components[card_id] = new ClimateIoT();
// components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic);
// if (mqtt_connected)
// {
// components[card_id]->subscribe();
// components[card_id]->publishReport();
// }
// break;
default:
Serial.println("Unsupported card type");
return;
}
}
void ESPMegaIoT::deregisterCard(uint8_t card_id)
{
// Check if the card is registered
if (components[card_id] == NULL)
{
return;
}
// Delete the IoT component
delete components[card_id];
components[card_id] = NULL;
}
void ESPMegaIoT::publishCard(uint8_t card_id)
{
// Check if the card is registered
if (components[card_id] == NULL)
{
return;
}
// Publish the card
components[card_id]->publishReport();
}
void ESPMegaIoT::subscribeToTopic(char *topic)
{
mqtt.subscribe(topic);
}
void ESPMegaIoT::unsubscribeFromTopic(char *topic)
{
mqtt.unsubscribe(topic);
}
void ESPMegaIoT::connectToWifi(char *ssid, char *password)
{
WiFi.begin(ssid, password);
}
void ESPMegaIoT::connectToWifi(char *ssid)
{
WiFi.begin(ssid);
}
void ESPMegaIoT::disconnectFromWifi()
{
WiFi.disconnect();
}
bool ESPMegaIoT::wifiConnected()
{
return WiFi.status() == WL_CONNECTED;
}
bool ESPMegaIoT::connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port, char *mqtt_user, char *mqtt_password)
{
mqtt.setServer(mqtt_server, mqtt_port);
auto boundCallback = std::bind(&ESPMegaIoT::mqttCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
mqtt.setCallback(boundCallback);
if (mqtt.connect(client_id, mqtt_user, mqtt_password))
{
sessionKeepAlive();
mqttSubscribe();
// Publish all cards
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->publishReport();
}
}
mqtt_connected = true;
return true;
}
mqtt_connected = false;
return false;
}
bool ESPMegaIoT::connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port)
{
mqtt.setServer(mqtt_server, mqtt_port);
auto boundCallback = std::bind(&ESPMegaIoT::mqttCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
mqtt.setCallback(boundCallback);
if (mqtt.connect(client_id))
{
sessionKeepAlive();
mqttSubscribe();
// Publish all cards
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->publishReport();
}
}
mqtt_connected = true;
return true;
}
Serial.println("Failed to connect to mqtt");
mqtt_connected = false;
return false;
}
void ESPMegaIoT::disconnectFromMqtt()
{
mqtt.disconnect();
}
void ESPMegaIoT::publishToTopic(char *topic, char *payload)
{
mqtt.publish(topic, payload);
}
void ESPMegaIoT::registerMqttCallback(void (*callback)(char *, char *))
{
user_mqtt_callback = callback;
}
void ESPMegaIoT::mqttSubscribe()
{
if (user_subscribe_callback != NULL)
{
user_subscribe_callback();
}
// Subscribe to all topics
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->subscribe();
}
}
}
void ESPMegaIoT::publishRelative(uint8_t card_id, char *topic, char *payload)
{
char absolute_topic[100];
sprintf(absolute_topic, "%s/%d/%s", base_topic, card_id, topic);
mqtt.publish(absolute_topic, payload);
}
bool ESPMegaIoT::mqttReconnect()
{
if (this->mqtt_config.mqtt_useauth)
{
return this->connectToMqtt(this->network_config.hostname, this->mqtt_config.mqtt_server, this->mqtt_config.mqtt_port, this->mqtt_config.mqtt_user, this->mqtt_config.mqtt_password);
}
else
{
return this->connectToMqtt(this->network_config.hostname, this->mqtt_config.mqtt_server, this->mqtt_config.mqtt_port);
}
}
void ESPMegaIoT::sessionKeepAlive()
{
static unsigned long lastSessionKeepAlive = 0;
if (millis() - lastSessionKeepAlive > 30000)
{
lastSessionKeepAlive = millis();
// Check if mqtt is connected
if (!mqtt.connected())
{
// Try to reconnect
mqtt_connected = mqttReconnect();
}
}
}
void ESPMegaIoT::registerRelativeMqttCallback(void (*callback)(char *, char *))
{
user_relative_mqtt_callback = callback;
}
void ESPMegaIoT::publishRelative(char *topic, char *payload)
{
char absolute_topic[100];
sprintf(absolute_topic, "%s/%s", base_topic, topic);
mqtt.publish(absolute_topic, payload);
}
void ESPMegaIoT::subscribeRelative(char *topic)
{
char absolute_topic[100];
sprintf(absolute_topic, "%s/%s", base_topic, topic);
mqtt.subscribe(absolute_topic);
}
void ESPMegaIoT::registerSubscribeCallback(void (*callback)(void))
{
user_subscribe_callback = callback;
}
void ESPMegaIoT::setNetworkConfig(NetworkConfig network_config)
{
this->network_config = network_config;
}
void ESPMegaIoT::loadNetworkConfig()
{
// Load the network config from FRAM
fram->read(0, (uint8_t *)&network_config, sizeof(NetworkConfig));
}
void ESPMegaIoT::saveNetworkConfig()
{
// Save the network config to FRAM
fram->write(NETWORK_CONFIG_ADDRESS, (uint8_t *)&network_config, sizeof(NetworkConfig));
}
void ESPMegaIoT::ethernetBegin()
{
ethernetIface->setHostname(network_config.hostname);
}
void ESPMegaIoT::loadMqttConfig()
{
// Load the mqtt config from FRAM
fram->read(sizeof(NetworkConfig), (uint8_t *)&this->mqtt_config, sizeof(MqttConfig));
// Populate the mqtt connection parameters
}
void ESPMegaIoT::saveMqttConfig()
{
fram->write(MQTT_CONFIG_ADDRESS, (uint8_t *)&mqtt_config, sizeof(MqttConfig));
}
void ESPMegaIoT::connectToMqtt()
{
if (mqtt_config.mqtt_useauth)
this->connectToMqtt(network_config.hostname, mqtt_config.mqtt_server, mqtt_config.mqtt_port, mqtt_config.mqtt_user, mqtt_config.mqtt_password);
else
this->connectToMqtt(network_config.hostname, mqtt_config.mqtt_server, mqtt_config.mqtt_port);
}
void ESPMegaIoT::connectNetwork()
{
if (network_config.useWifi)
{
if (network_config.wifiUseAuth)
this->connectToWifi(network_config.ssid, network_config.password);
else
this->connectToWifi(network_config.ssid);
if (network_config.useStaticIp)
WiFi.config(network_config.ip, network_config.gateway, network_config.subnet);
else
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
else
{
this->ethernetBegin();
if (network_config.useStaticIp)
ethernetIface->config(network_config.ip, network_config.gateway, network_config.subnet, network_config.dns1, network_config.dns2);
}
}
void ESPMegaIoT::setMqttConfig(MqttConfig mqtt_config)
{
this->mqtt_config = mqtt_config;
}
void ESPMegaIoT::bindEthernetInterface(ETHClass *ethernetIface)
{
this->ethernetIface = ethernetIface;
}
IoTComponent *ESPMegaIoT::getComponent(uint8_t card_id)
{
return components[card_id];
}
NetworkConfig* ESPMegaIoT::getNetworkConfig()
{
return &network_config;
}
MqttConfig* ESPMegaIoT::getMqttConfig()
{
return &mqtt_config;
}
bool ESPMegaIoT::mqttConnected()
{
return mqtt_connected;
}
bool ESPMegaIoT::networkConnected()
{
if (network_config.useWifi)
return WiFi.status() == WL_CONNECTED;
else
return ethernetIface->linkUp();
}

View file

@ -0,0 +1,114 @@
#pragma once
#include <ExpansionCard.hpp>
#include <AnalogCard.hpp>
#include <AnalogIoT.hpp>
#include <DigitalInputCard.hpp>
#include <DigitalInputIoT.hpp>
#include <DigitalOutputCard.hpp>
#include <DigitalOutputIoT.hpp>
#include <IoTComponent.hpp>
#include <PubSubClient.h>
#include <ETH.h>
#include <WiFi.h>
#include <FRAM.h>
struct NetworkConfig
{
IPAddress ip;
IPAddress gateway;
IPAddress subnet;
IPAddress dns1;
IPAddress dns2;
char hostname[32];
bool useStaticIp;
bool useWifi;
bool wifiUseAuth;
char ssid[32];
char password[32];
};
struct MqttConfig
{
char mqtt_server[32];
uint16_t mqtt_port;
char mqtt_user[32];
char mqtt_password[32];
bool mqtt_useauth;
char base_topic[32];
};
class ESPMegaIoT
{
public:
ESPMegaIoT();
~ESPMegaIoT();
void intr_begin(ExpansionCard *cards[]);
void loop();
void registerCard(uint8_t card_id);
void deregisterCard(uint8_t card_id);
void publishCard(uint8_t card_id);
// Publish topic appended with base topic
void publishRelative(char *topic, char *payload);
// Subscribe topic appended with base topic
void subscribeRelative(char *topic);
void subscribeToTopic(char *topic);
void unsubscribeFromTopic(char *topic);
void connectToWifi(char *ssid, char *password);
void connectToWifi(char *ssid);
void disconnectFromWifi();
bool wifiConnected();
void ethernetBegin();
void loadNetworkConfig();
void saveNetworkConfig();
NetworkConfig* getNetworkConfig();
MqttConfig* getMqttConfig();
void setMqttConfig(MqttConfig mqtt_config);
void saveMqttConfig();
void loadMqttConfig();
void connectNetwork();
void setNetworkConfig(NetworkConfig network_config);
void connectToMqtt();
bool connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port, char *mqtt_user, char *mqtt_password);
bool connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port);
bool mqttConnected();
void disconnectFromMqtt();
void publishToTopic(char *topic, char *payload);
void registerMqttCallback(void (*callback)(char *, char *));
void registerRelativeMqttCallback(void (*callback)(char *, char *));
void registerSubscribeCallback(void (*callback)(void));
void setBaseTopic(char *base_topic);
void bindEthernetInterface(ETHClass *ethernetIface);
bool networkConnected();
IoTComponent* getComponent(uint8_t card_id);
IPAddress getETHIp();
private:
FRAM *fram;
bool useWifi;
bool WifiUseAuth;
char ssid[32];
char password[32];
WiFiClient tcpClient;
void sessionKeepAlive();
bool mqttReconnect();
void wifiReconnect();
void mqttSubscribe();
void mqttCallback(char *topic, byte *payload, unsigned int length);
void (*user_mqtt_callback)(char *, char *);
void (*user_relative_mqtt_callback)(char *, char *);
void (*user_subscribe_callback)(void);
void publishRelative(uint8_t card_id, char *topic, char *payload);
bool active;
PubSubClient mqtt;
IoTComponent *components[255];
char payload_buffer[200];
char base_topic[100];
uint8_t base_topic_length;
ExpansionCard **cards; // Points to card array in ESPMegaPRO Core
// MQTT Connection Parameters
bool mqtt_connected;
NetworkConfig network_config;
MqttConfig mqtt_config;
ETHClass *ethernetIface;
};

View file

@ -0,0 +1,178 @@
#include <ESPMegaPRO.h>
uint8_t inputBufferA;
uint8_t inputBufferB;
PCF8574 inputBankA(INPUT_BANK_A_ADDRESS);
PCF8574 inputBankB(INPUT_BANK_B_ADDRESS);
Adafruit_PWMServoDriver pwmBank = Adafruit_PWMServoDriver(PWM_BANK_ADDRESS);
FRAM ESPMega_FRAM;
#ifdef ANALOG_CARD_ENABLE
Adafruit_ADS1115 analogInputBankA;
Adafruit_ADS1115 analogInputBankB;
MCP4725 DAC0(DAC0_ADDRESS);
MCP4725 DAC1(DAC1_ADDRESS);
MCP4725 DAC2(DAC2_ADDRESS);
MCP4725 DAC3(DAC3_ADDRESS);
#endif
void ESPMega_begin()
{
Wire.begin(14, 33);
inputBankA.begin();
inputBankB.begin();
pwmBank.begin();
ESPMega_FRAM.begin(FRAM_ADDRESS);
// ESPMegaPRO v3 use the PWMBank to drive Half Bridge
// Push Pull Output is required.
pwmBank.setOutputMode(true);
#ifdef USE_INTERRUPT
pinMode(INPUT_BANK_A_INTERRUPT, INPUT_PULLUP);
pinMode(INPUT_BANK_B_INTERRUPT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(INPUT_BANK_A_INTERRUPT), refreshInputBankA, FALLING);
attachInterrupt(digitalPinToInterrupt(INPUT_BANK_B_INTERRUPT), refreshInputBankB, FALLING);
#endif
#ifdef ANALOG_CARD_ENABLE
analogInputBankA.begin(ANALOG_INPUT_BANK_A_ADDRESS);
analogInputBankB.begin(ANALOG_INPUT_BANK_B_ADDRESS);
DAC0.begin();
DAC1.begin();
DAC2.begin();
DAC3.begin();
#endif
}
void ESPMega_loop()
{
}
bool ESPMega_digitalRead(int id)
{
if (id >= 0 && id <= 7)
{
#ifndef USE_INTERRUPT
refreshInputBankA(); // Only poll if interrupt is not enabled
#endif
return ((inputBufferA >> (7 - id)) & 1); // Extract bit from buffer
}
if (id >= 8 && id <= 15)
{
#ifndef USE_INTERRUPT
refreshInputBankB(); // Only poll if interrupt is not enabled
#endif
if (id >= 8 && id <= 11)
return ((inputBufferB >> (15 - id)) & 1); // Extract bit from buffer
else if (id >= 12 && id <= 15)
return ((inputBufferB >> (id - 12)) & 1);
}
return false;
}
void ESPMega_analogWrite(int id, int value)
{
if (id >= 0 && id <= 7)
id += 8;
else if (id >= 8 && id <= 15)
id -= 8;
pwmBank.setPin(id, value);
}
void ESPMega_digitalWrite(int id, bool value)
{
if (value)
pwmBank.setPin(id, 4095);
else
pwmBank.setPin(id, 0);
}
void IRAM_ATTR refreshInputBankA()
{
inputBufferA = inputBankA.read8();
}
void IRAM_ATTR refreshInputBankB()
{
inputBufferB = inputBankB.read8();
}
rtctime_t ESPMega_getTime()
{
tmElements_t timeElement;
RTC.read(timeElement);
rtctime_t time;
time.hours = timeElement.Hour;
time.minutes = timeElement.Minute;
time.seconds = timeElement.Second;
time.day = timeElement.Day;
time.month = timeElement.Month;
time.year = timeElement.Year + 1970;
return time;
}
void ESPMega_setTime(int hours, int minutes, int seconds, int day, int month, int year)
{
tmElements_t timeElement;
timeElement.Hour = hours;
timeElement.Minute = minutes;
timeElement.Second = seconds;
timeElement.Day = day;
timeElement.Month = month;
timeElement.Year = year - 1970;
RTC.write(timeElement);
}
#ifdef ANALOG_CARD_ENABLE
int16_t ESPMega_analogRead(int id)
{
if (id >= 0 && id <= 3)
return analogInputBankA.readADC_SingleEnded(3 - id);
else if (id >= 4 && id <= 7)
return analogInputBankB.readADC_SingleEnded(7 - id);
return 0;
}
void ESPMega_dacWrite(int id, int value)
{
switch (id)
{
case 0:
DAC0.setValue(value);
break;
case 1:
DAC1.setValue(value);
break;
case 2:
DAC2.setValue(value);
break;
case 3:
DAC3.setValue(value);
break;
default:
break;
}
}
bool ESPMega_updateTimeFromNTP()
{
struct tm timeinfo;
if (getLocalTime(&timeinfo))
{
rtctime_t rtctime = ESPMega_getTime();
if (rtctime.hours != timeinfo.tm_hour || rtctime.minutes != timeinfo.tm_min ||
rtctime.seconds != timeinfo.tm_sec || rtctime.day != timeinfo.tm_mday ||
rtctime.month != timeinfo.tm_mon + 1 || rtctime.year != timeinfo.tm_year + 1900)
{
ESPMega_setTime(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec,
timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900);
}
return true;
}
return false;
}
#endif

View file

@ -0,0 +1,132 @@
#ifndef ESPMEGA
#define ESPMEGA
#define ANALOG_CARD_ENABLE
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <PCF8574.h>
#include <FRAM.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <time.h>
#ifdef ANALOG_CARD_ENABLE
#include <Adafruit_ADS1X15.h>
#include <MCP4725.h>
#endif
#warning "The procedural ESPMega library does not support installing card. If you want to use the card system, please use the OOP version of the library."
#define INPUT_BANK_A_ADDRESS 0x21
#define INPUT_BANK_B_ADDRESS 0x22
#define PWM_BANK_ADDRESS 0x5F
#define RTC_ADDRESS 0x68
#define ANALOG_INPUT_BANK_A_ADDRESS 0x48
#define ANALOG_INPUT_BANK_B_ADDRESS 0x49
#define DAC0_ADDRESS 0x60
#define DAC1_ADDRESS 0x61
#define DAC2_ADDRESS 0x62
#define DAC3_ADDRESS 0x63
#define FRAM_ADDRESS 0x56
//#define USE_INTERRUPT
#define INPUT_BANK_A_INTERRUPT 36
#define INPUT_BANK_B_INTERRUPT 39
extern FRAM ESPMega_FRAM;
#define ESPMega_configNTP configTime
struct rtctime_t {
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
uint8_t day;
uint8_t month;
uint16_t year;
};
/**
* Initiate ESPMega PRO Internal Components
*
* This function will initiate the PWM bank, Input banks, and the EEPROM.
*/
void ESPMega_begin();
/**
* Run ESPMega PRO Internal Routines
*
* This function must be called regularly for the correct
* operation of the ESPMega!
*/
void ESPMega_loop();
/**
* Read one of the ESPMega's Digital Input pins (I0-I15)
*
* @param id The number of the pin to be read
* @return The state of the pin (HIGH/LOW)
*/
bool ESPMega_digitalRead(int id);
/**
* Write a pulse wave modulated signal to one of the ESPMega's
* PWM pins (P0-P15)
*
* @param id The number of the pin to write to
* @param value the "HIGH" duty cycle of the PWM wave (0-4095)
*/
void ESPMega_analogWrite(int id, int value);
/**
* Write a digital signal to one of the ESPMega's
* PWM pins (P0-P15)
*
* @param id The number of the pin to write to
* @param value the new state for the pin (HIGH/LOW)
*/
void ESPMega_digitalWrite(int id, bool value);
void IRAM_ATTR refreshInputBankA();
void IRAM_ATTR refreshInputBankB();
/**
* Get time from the onboard RTC as a struct
*
* @return Time Element Struct
*/
rtctime_t ESPMega_getTime();
/**
* Set the onboard RTC's time
*
* @param hours
* @param minutes
* @param seconds
* @param day Day of the month
* @param month Month in numerical form
* @param year Years in AD
*/
void ESPMega_setTime(int hours,int minutes, int seconds, int day, int month, int year);
/**
* Update the onboard RTC's time
* by using time from the NTP server
* configured with ESPMega_configNTP();
* @return true when updated successfully.
*/
bool ESPMega_updateTimeFromNTP();
#ifdef ANALOG_CARD_ENABLE
/**
* Read one of the ESPMega Analog Card's Analog Input pins (A0-A7)
*
* @param id The number of the pin to be read
* @return The value of the pin (0-4095)
*/
int16_t ESPMega_analogRead(int id);
/**
* Write a True Analog Signal to one of the ESPMega Analog Card's
* Analog Output pins (AO0-AO3)
*
* @param id The number of the pin to write to
* @param value the analog value of the pin (0-4095)
*/
void ESPMega_dacWrite(int id, int value);
#endif
#endif

View file

@ -0,0 +1,98 @@
#include <ESPMegaPRO_OOP.hpp>
ESPMegaPRO::ESPMegaPRO() {
}
bool ESPMegaPRO::begin() {
Wire.begin(14, 33);
fram.begin(FRAM_ADDRESS);
Serial.begin(115200);
this->installCard(1, &outputs);
outputs.bindFRAM(&fram,0);
outputs.loadFromFRAM();
if(!this->installCard(0, &inputs)) {
Serial.println("Failed to initialize inputs");
Serial.println("Is this an ESPMegaPRO device?");
return false;
}
uint8_t pinMap[16] = {0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8};
inputs.loadPinMap(pinMap);
return true;
}
void ESPMegaPRO::loop() {
inputs.loop();
outputs.loop();
for (int i = 0; i < 255; i++) {
if (cardInstalled[i]) {
cards[i]->loop();
}
}
iot.loop();
}
bool ESPMegaPRO::installCard(uint8_t slot, ExpansionCard* card) {
if (slot > 255) return false;
if (cardInstalled[slot]) {
Serial.println("Card already installed");
return false;
}
if (!card->begin()) {
Serial.print("Failed to install card at slot ");
Serial.println(slot);
return false;
}
cards[slot] = card;
cardInstalled[slot] = true;
cardCount++;
return true;
}
bool ESPMegaPRO::updateTimeFromNTP() {
struct tm timeinfo;
if (getLocalTime(&timeinfo))
{
rtctime_t rtctime = this->getTime();
if (rtctime.hours != timeinfo.tm_hour || rtctime.minutes != timeinfo.tm_min ||
rtctime.seconds != timeinfo.tm_sec || rtctime.day != timeinfo.tm_mday ||
rtctime.month != timeinfo.tm_mon + 1 || rtctime.year != timeinfo.tm_year + 1900)
{
this->setTime(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec,
timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900);
}
return true;
}
return false;
}
rtctime_t ESPMegaPRO::getTime() {
tmElements_t timeElement;
RTC.read(timeElement);
rtctime_t time;
time.hours = timeElement.Hour;
time.minutes = timeElement.Minute;
time.seconds = timeElement.Second;
time.day = timeElement.Day;
time.month = timeElement.Month;
time.year = timeElement.Year + 1970;
return time;
}
void ESPMegaPRO::setTime(int hours, int minutes, int seconds, int day, int month, int year)
{
tmElements_t timeElement;
timeElement.Hour = hours;
timeElement.Minute = minutes;
timeElement.Second = seconds;
timeElement.Day = day;
timeElement.Month = month;
timeElement.Year = year - 1970;
RTC.write(timeElement);
}
void ESPMegaPRO::enableIotModule() {
iot.intr_begin(cards);
}
ExpansionCard* ESPMegaPRO::getCard(uint8_t slot) {
if (slot > 255) return nullptr;
if (!cardInstalled[slot]) return nullptr;
return cards[slot];
}

View file

@ -0,0 +1,43 @@
#pragma once
#include <ExpansionCard.hpp>
#include <DigitalInputCard.hpp>
#include <DigitalOutputCard.hpp>
#include <ClimateCard.hpp>
#include <AnalogCard.hpp>
#include <ESPMegaIoT.hpp>
#include <Arduino.h>
#include <Wire.h>
#include <FRAM.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <time.h>
#include <TimeStructure.hpp>
#include <ESPMegaDisplay.hpp>
#include <InternalDisplay.hpp>
#define FRAM_ADDRESS 0x56
#define INPUT_BANK_A_ADDRESS 0x21
#define INPUT_BANK_B_ADDRESS 0x22
#define PWM_BANK_ADDRESS 0x5F
#define RTC_ADDRESS 0x68
class ESPMegaPRO {
public:
ESPMegaPRO();
bool begin();
void loop();
bool installCard(uint8_t slot, ExpansionCard* card);
bool updateTimeFromNTP();
void enableIotModule();
rtctime_t getTime();
void setTime(int hours, int minutes, int seconds, int day, int month, int year);
ExpansionCard* getCard(uint8_t slot);
FRAM fram;
DigitalInputCard inputs = DigitalInputCard(INPUT_BANK_A_ADDRESS, INPUT_BANK_B_ADDRESS);
DigitalOutputCard outputs = DigitalOutputCard(PWM_BANK_ADDRESS);
ESPMegaIoT iot = ESPMegaIoT();
private:
ExpansionCard* cards[255];
bool cardInstalled[255];
uint8_t cardCount = 0;
};

View file

@ -0,0 +1,24 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <ESPMegaIoT.hpp>
#include <Update.h>
class ESPMegaWebServer
{
public:
ESPMegaWebServer(uint16_t port);
~ESPMegaWebServer();
void begin(ESPMegaIoT *iot);
void loop();
private:
// Web Server
AsyncWebServer server;
uint16_t port;
// ESPMegaIoT
ESPMegaIoT *iot;
// Endpoints Handlers
void dashboardHandler(AsyncWebServerRequest *request);
void configHandler(AsyncWebServerRequest *request);
void saveConfigHandler(AsyncWebServerRequest *request);
void otaHandler(AsyncWebServerRequest *request);
};

View file

@ -0,0 +1,13 @@
#pragma once
#include <Arduino.h>
class ExpansionCard {
public:
// Instantiate the card with the specified address
ExpansionCard() {}
virtual bool begin();
// Preform a loop to refresh the input buffers
virtual void loop();
// Get the card type
virtual uint8_t getType();
};

View file

@ -0,0 +1,180 @@
#include <InternalDisplay.hpp>
void InternalDisplay::begin(ESPMegaIoT *iot, std::function<rtctime_t()> getRtcTime) {
this->iot = iot;
this->getRtcTime = getRtcTime;
this->mqttConfig = this->iot->getMqttConfig();
this->networkConfig = this->iot->getNetworkConfig();
// Register callbacks
this->inputCard->registerCallback(std::bind(&InternalDisplay::handleInputStateChange, this, std::placeholders::_1, std::placeholders::_2));
this->outputCard->registerChangeCallback(std::bind(&InternalDisplay::handlePwmStateChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
void InternalDisplay::loop() {
// Keep reading the Serial Adapter
this->recieveSerialCommand();
// Refresh the top bar every 5 seconds
static uint32_t lastTopBarRefresh;
if (millis() - lastTopBarRefresh > INTERNAL_DISPLAY_TOP_BAR_REFRESH_INTERVAL) {
this->updateStatusIcons(this->iot->networkConnected(), this->iot->mqttConnected());
lastTopBarRefresh = millis();
}
// Refresh the clock every 10 seconds
static uint32_t lastClockRefresh;
if (millis() - lastClockRefresh > INTERNAL_DISPLAY_CLOCK_REFRESH_INTERVAL) {
this->updateClock();
lastClockRefresh = millis();
}
}
void InternalDisplay::handleInputStateChange(uint8_t pin, bool state) {
// If the input card is binded to the display and the current page is the input page
// then update the respective input component
if (this->inputCard!=nullptr || this->currentPage != INTERNAL_DISPLAY_INPUT_PAGE) return;
// Update the input state
this->setInputMarker(pin, state);
}
void InternalDisplay::handlePwmStateChange(uint8_t pin, bool state, uint16_t value) {
// If the output card is binded to the display and the current page is the output page
// then update the respective output component
if (this->outputCard!=nullptr || this->currentPage != INTERNAL_DISPLAY_OUTPUT_PAGE) return;
// Update the output state
this->setOutputBar(pin, value);
this->setOutputStateColor(pin, state);
}
void InternalDisplay::handlePageChange(uint8_t page) {
// Refresh the page
this->refreshPage(page);
}
void InternalDisplay::saveNetworkConfig() {
// TODO: implementation
}
void InternalDisplay::saveMQTTConfig() {
// TODO: implementation
}
void InternalDisplay::updateStatusIcons(bool networkStatus, bool mqttStatus) {
this->setNumber("server.pic", mqttStatus ? PIC_MQTT_CONNECTED : PIC_MQTT_DISCONNECTED);
this->setNumber("lan.pic", networkStatus ? PIC_LAN_CONNECTED : PIC_LAN_DISCONNECTED);
}
void InternalDisplay::updateClock() {
rtctime_t time = this->getRtcTime();
this->displayAdapter->print("time.txt=");
this->displayAdapter->print(time.hours%12);
this->displayAdapter->print(":");
this->displayAdapter->print(time.minutes);
this->displayAdapter->print(" ");
this->displayAdapter->print(time.hours/12 ? "PM" : "AM");
this->sendStopBytes();
}
void InternalDisplay::refreshPage() {
this->refreshPage(this->currentPage);
}
void InternalDisplay::refreshPage(uint8_t page) {
switch (page) {
case INTERNAL_DISPLAY_DASHBOARD_PAGE:
this->refreshDashboard();
break;
case INTERNAL_DISPLAY_INPUT_PAGE:
this->refreshInput();
break;
case INTERNAL_DISPLAY_OUTPUT_PAGE:
this->refreshOutput();
break;
case INTERNAL_DISPLAY_AC_PAGE:
this->refreshAC();
break;
}
}
void InternalDisplay::refreshDashboard() {
// The dashboard have the following components:
// 1. Hostname
// 2. IP Address
// 3. MQTT Server with port
// 4. MQTT Connection status
this->setString("hostname.txt", this->networkConfig->hostname);
// Construct the IP address string
static char ip_address[25];
sprintf(ip_address, "%d.%d.%d.%d", this->networkConfig->ip[0], this->networkConfig->ip[1], this->networkConfig->ip[2], this->networkConfig->ip[3]);
this->setString("ip_address.txt", ip_address);
// Send the MQTT server and port
this->displayAdapter->print("server_address.txt=");
this->displayAdapter->print(this->mqttConfig->mqtt_server);
this->displayAdapter->print(":");
this->displayAdapter->print(this->mqttConfig->mqtt_port);
this->sendStopBytes();
// Send the MQTT connection status
this->setString("status_txt.txt", this->iot->mqttConnected() ? MSG_MQTT_CONNECTED : MSG_MQTT_DISCONNECTED);
}
void InternalDisplay::refreshInput() {
for (uint8_t i=0; i<16; i++) {
this->setInputMarker(i, this->inputCard->digitalRead(i));
}
}
void InternalDisplay::refreshOutput() {
for (uint8_t i=0; i<16; i++) {
this->setOutputBar(i, this->outputCard->getValue(i));
this->setOutputStateColor(i, this->outputCard->getState(i));
}
}
void InternalDisplay::refreshAC() {
// TODO: implementation
}
void InternalDisplay::setPWMAdjustmentSlider(uint16_t value) {
// TODO: implementation
}
void InternalDisplay::setPWMAdjustmentPin(uint8_t pin) {
// TODO: implementation
}
void InternalDisplay::setPWMAdjustmentButton(bool state) {
// TODO: implementation
}
void InternalDisplay::setOutputBar(uint8_t pin, uint16_t value) {
// Write the value to the output bar
this->displayAdapter->print("j");
this->displayAdapter->print(pin);
this->displayAdapter->print(".val=");
this->displayAdapter->print((int)(value*100/4095));
this->sendStopBytes();
}
void InternalDisplay::setOutputStateColor(uint8_t pin, bool state) {
this->displayAdapter->print("j");
this->displayAdapter->print(pin);
this->displayAdapter->print(".ppic=");
this->displayAdapter->print(state ? PIC_PWM_BAR_ON : PIC_PWM_BAR_OFF);
this->sendStopBytes();
}
void InternalDisplay::setInputMarker(uint8_t pin, bool state) {
this->displayAdapter->print("I");
this->displayAdapter->print(pin);
this->displayAdapter->print(".val=");
this->displayAdapter->print(state ? 0:1);
this->sendStopBytes();
}
InternalDisplay::InternalDisplay(HardwareSerial *displayAdapter) : ESPMegaDisplay(displayAdapter) {
this->currentPage = INTERNAL_DISPLAY_DASHBOARD_PAGE;
}

View file

@ -0,0 +1,68 @@
#pragma once
#include <ESPMegaDisplay.hpp>
#include <TimeStructure.hpp>
#include <ESPMegaIoT.hpp>
#include <DigitalInputCard.hpp>
#include <DigitalOutputCard.hpp>
// Page IDs
#define INTERNAL_DISPLAY_DASHBOARD_PAGE 0
#define INTERNAL_DISPLAY_INPUT_PAGE 1
#define INTERNAL_DISPLAY_OUTPUT_PAGE 2
#define INTERNAL_DISPLAY_AC_PAGE 3
// Picture IDs
#define PIC_LAN_DISCONNECTED 2
#define PIC_LAN_CONNECTED 3
#define PIC_MQTT_DISCONNECTED 4
#define PIC_MQTT_CONNECTED 5
#define PIC_PWM_BAR_ON 33
#define PIC_PWM_BAR_OFF 48
// Messages
#define MSG_MQTT_CONNECTED "BMS Managed"
#define MSG_MQTT_DISCONNECTED "Standalone"
// Refresh Interval
#define INTERNAL_DISPLAY_CLOCK_REFRESH_INTERVAL 15000
#define INTERNAL_DISPLAY_TOP_BAR_REFRESH_INTERVAL 5000
class InternalDisplay : public ESPMegaDisplay {
public:
InternalDisplay(HardwareSerial *displayAdapter);
void begin(ESPMegaIoT *iot, std::function<rtctime_t()> getRtcTime);
void loop();
void bindInputCard(DigitalInputCard *inputCard);
void bindOutputCard(DigitalOutputCard *outputCard);
private:
DigitalInputCard *inputCard;
DigitalOutputCard *outputCard;
// Previously registered callbacks of input and output cards
// We need to call them when the respective card is binded to the display
// Because we will replace the callbacks with the display's own callbacks
void handleInputStateChange(uint8_t pin, bool state);
void handlePwmStateChange(uint8_t pin, bool state, uint16_t value);
void handlePageChange(uint8_t page);
void setOutputBar(uint8_t pin, uint16_t value);
void setOutputStateColor(uint8_t pin, bool state);
void setInputMarker(uint8_t pin, bool state);
void setPWMAdjustmentSlider(uint16_t value);
void setPWMAdjustmentPin(uint8_t pin);
void setPWMAdjustmentButton(bool state);
void saveNetworkConfig();
void saveMQTTConfig();
void updateStatusIcons(bool networkStatus, bool mqttStatus);
void updateClock();
void refreshPage();
void refreshPage(uint8_t page);
void refreshDashboard();
void refreshInput();
void refreshOutput();
void refreshAC();
MqttConfig *mqttConfig;
NetworkConfig *networkConfig;
// Pointers to various data
ESPMegaIoT *iot;
std::function<rtctime_t()> getRtcTime;
};

View file

@ -0,0 +1,21 @@
#include <IoTComponent.hpp>
void IoTComponent::setMqttClient(PubSubClient *mqtt) {
this->mqtt = mqtt;
}
void IoTComponent::publishRelative(const char *topic, const char *payload) {
char absolute_topic[100];
sprintf(absolute_topic, "%s/%02d/%s", base_topic, card_id, topic);
mqtt->publish(absolute_topic, payload);
}
void IoTComponent::subscribeRelative(const char *topic) {
char absolute_topic[50];
sprintf(absolute_topic, "%s/%02d/%s", base_topic, card_id, topic);
mqtt->subscribe(absolute_topic);
}
void IoTComponent::loop() {
// Placeholder, Do nothing
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <ExpansionCard.hpp>
#include <PubSubClient.h>
class IoTComponent {
public:
virtual bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
virtual void handleMqttMessage(char *topic, char *payload);
void setMqttClient(PubSubClient *mqtt);
virtual void publishReport();
virtual uint8_t getType();
virtual void subscribe();
void loop();
protected:
char *base_topic;
void publishRelative(const char *topic, const char *payload);
void subscribeRelative(const char *topic);
PubSubClient *mqtt;
uint8_t card_id;
};

View file

@ -0,0 +1,11 @@
#pragma once
#include <stdint.h>
struct rtctime_t {
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
uint8_t day;
uint8_t month;
uint16_t year;
};