rename folders
This commit is contained in:
parent
1ec8effe90
commit
1815597374
63 changed files with 1 additions and 0 deletions
138
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogCard.cpp
Normal file
138
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogCard.cpp
Normal 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
41
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogCard.hpp
Normal file
41
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogCard.hpp
Normal 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;
|
||||
};
|
270
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogIoT.cpp
Normal file
270
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogIoT.cpp
Normal 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);
|
||||
}
|
54
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogIoT.hpp
Normal file
54
ESPMegaPRO-firmware/lib/ESPMegaPRO/AnalogIoT.hpp
Normal 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;
|
||||
};
|
197
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateCard.cpp
Normal file
197
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateCard.cpp
Normal 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);
|
||||
}
|
85
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateCard.hpp
Normal file
85
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateCard.hpp
Normal 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;
|
||||
};
|
132
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.cpp
Normal file
132
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.cpp
Normal 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;
|
||||
}
|
42
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.hpp
Normal file
42
ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.hpp
Normal 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);
|
||||
};
|
207
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputCard.cpp
Normal file
207
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputCard.cpp
Normal 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 ¤tBuffer, 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;
|
||||
}
|
55
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputCard.hpp
Normal file
55
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputCard.hpp
Normal 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);
|
||||
};
|
68
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.cpp
Normal file
68
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.cpp
Normal 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);
|
||||
}
|
23
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.hpp
Normal file
23
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.hpp
Normal 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;
|
||||
};
|
178
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputCard.cpp
Normal file
178
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputCard.cpp
Normal 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);
|
||||
}
|
102
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputCard.hpp
Normal file
102
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputCard.hpp
Normal 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];
|
||||
};
|
197
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputIoT.cpp
Normal file
197
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputIoT.cpp
Normal 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() {
|
||||
}
|
39
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputIoT.hpp
Normal file
39
ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalOutputIoT.hpp
Normal 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;
|
||||
};
|
348
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaDisplay.cpp
Normal file
348
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaDisplay.cpp
Normal 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();
|
||||
}
|
47
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaDisplay.hpp
Normal file
47
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaDisplay.hpp
Normal 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;
|
||||
};
|
409
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.cpp
Normal file
409
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.cpp
Normal 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();
|
||||
}
|
114
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.hpp
Normal file
114
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.hpp
Normal 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;
|
||||
};
|
178
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO.cpp
Normal file
178
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO.cpp
Normal 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
|
132
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO.h
Normal file
132
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO.h
Normal 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
|
98
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO_OOP.cpp
Normal file
98
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO_OOP.cpp
Normal 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];
|
||||
}
|
43
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO_OOP.hpp
Normal file
43
ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaPRO_OOP.hpp
Normal 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;
|
||||
};
|
|
@ -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);
|
||||
};
|
13
ESPMegaPRO-firmware/lib/ESPMegaPRO/ExpansionCard.hpp
Normal file
13
ESPMegaPRO-firmware/lib/ESPMegaPRO/ExpansionCard.hpp
Normal 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();
|
||||
};
|
180
ESPMegaPRO-firmware/lib/ESPMegaPRO/InternalDisplay.cpp
Normal file
180
ESPMegaPRO-firmware/lib/ESPMegaPRO/InternalDisplay.cpp
Normal 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;
|
||||
}
|
68
ESPMegaPRO-firmware/lib/ESPMegaPRO/InternalDisplay.hpp
Normal file
68
ESPMegaPRO-firmware/lib/ESPMegaPRO/InternalDisplay.hpp
Normal 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;
|
||||
};
|
21
ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.cpp
Normal file
21
ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.cpp
Normal 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
|
||||
}
|
20
ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.hpp
Normal file
20
ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.hpp
Normal 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;
|
||||
};
|
11
ESPMegaPRO-firmware/lib/ESPMegaPRO/TimeStructure.hpp
Normal file
11
ESPMegaPRO-firmware/lib/ESPMegaPRO/TimeStructure.hpp
Normal 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;
|
||||
};
|
46
ESPMegaPRO-firmware/lib/README
Normal file
46
ESPMegaPRO-firmware/lib/README
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue