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;
};

View file

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html