/** * @file espmega_iot_core.cpp * @brief Implementation of the ESPMega IoT Core library. * * This library provides the core functionality for the ESPMega IoT platform, including network connectivity, * input/output control, and MQTT communication. It also includes support for an internal LCD display and * air conditioner control. * * @author Siwat Sirichai * @date 15 Nov 2023 */ #include // OS Configuration // #define FASTBOOT #ifndef ESPMEGA_REV #define ESPMEGA_REV "ESPMega PRO R3.3b" #endif // Network Connectivity char HOSTNAME[15]; IPAddress IP(0, 0, 0, 0); IPAddress SUBNET(0, 0, 0, 0); IPAddress GATEWAY(0, 0, 0, 0); IPAddress DNS(0, 0, 0, 0); IPAddress MQTT_SERVER(0, 0, 0, 0); uint16_t MQTT_PORT = 0; #ifdef ENABLE_WEBUI WebServer otaserver(80); #endif bool standalone = true; char MQTT_BASE_TOPIC[20]; uint8_t base_topic_length = 0; char STATE_REQUEST_TOPIC[40]; bool MQTT_USE_AUTH = false; char MQTT_USERNAME[32]; char MQTT_PASSWORD[32]; uint8_t utc_offset = 7; #ifdef ENABLE_CLIMATE_MODULE float current_room_temp = 0; float current_room_humid = 0; #endif // Inputs #define VINT_COUNT 16 const int DEBOUNCE_TIME_MS = 50; const int virtual_interrupt_pins[VINT_COUNT] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; int virtual_interupt_state[VINT_COUNT]; unsigned long virtual_interupt_timer[VINT_COUNT]; // Outputs #define PWM_COUNT 16 const uint8_t pwm_pins[PWM_COUNT] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; bool pwm_states[PWM_COUNT]; uint8_t pwm_states_eeprom[PWM_COUNT]; uint16_t pwm_values[PWM_COUNT]; uint8_t pwm_values_eeprom[PWM_COUNT * 2]; // output = m*input+c const float pwm_linear_scaling_m[PWM_COUNT] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; const float pwm_linear_scaling_c[PWM_COUNT] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; #define PWM_CYCLE_VALUES_COUNT 3 const int PWM_CYCLE_VALUES[PWM_CYCLE_VALUES_COUNT] = {50, 125, 255}; char PWM_SET_STATE_TOPIC[70]; char PWM_SET_VALUE_TOPIC[70]; #ifdef ENABLE_INTERNAL_LCD // LCD int lcd_current_page = 1; int lcd_pwmAdj_id = 0; EasyNex panel(Serial); #endif // Air Conditioner Control /* Mode 0: Off, 1: Cool, 2: Fan Fan Speed 0: Auto, 1: High, 2: Mid, 3: Low */ #ifdef ENABLE_CLIMATE_MODULE #define DHT22_PIN 32 uint8_t ac_mode = 0; uint8_t ac_fan_speed = 0; uint8_t ac_temperature = 25; char AC_SET_MODE_TOPIC[75]; char AC_SET_FAN_TOPIC[75]; char AC_SET_TEMPERATURE_TOPIC[75]; char AC_MODE_TOPIC[75]; char AC_FAN_TOPIC[75]; char AC_TEMPERATURE_TOPIC[75]; char AC_ROOM_TEMPERATURE_TOPIC[75]; char AC_HUMIDITY_TOPIC[75]; #endif #ifdef ENABLE_ANALOG_MODULE #define DAC_COUNT 4 #define ADC_COUNT 8 bool dac_states[DAC_COUNT]; uint16_t dac_values[DAC_COUNT]; uint16_t adc_values[ADC_COUNT]; bool adc_report_enable[ADC_COUNT]; char ADC_COMMAND_TOPIC[75]; char ADC_STATE_TOPIC[75]; char ADC_REPORT_TOPIC[75]; char DAC_SET_STATE_TOPIC[75]; char DAC_SET_VALUE_TOPIC[75]; char DAC_STATE_TOPIC[75]; char DAC_VALUE_TOPIC[75]; #endif // EEPROM ADDRESS #define EEPROM_ADDRESS_AC_MODE 0 // 01bytes #define EEPROM_ADDRESS_AC_TEMPERATURE 1 // 01bytes #define EEPROM_ADDRESS_AC_FAN_SPEED 2 // 01bytes #define EEPROM_ADDRESS_PWM_STATE 3 // 16bytes, thru 18 #define EEPROM_ADDRESS_PWM_VALUE 19 // 32bytes, thru 50 #define EEPROM_ADDRESS_HOSTNAME 65 // 15bytes, thru 79 #define EEPROM_ADDRESS_TOPIC 80 // 20bytes, thru 99 #define EEPROM_ADDRESS_IP 100 // 04bytes, thru 103 #define EEPROM_ADDRESS_SUBNET 104 // 04bytes, thru 107 #define EEPROM_ADDRESS_GATEWAY 108 // 04bytes, thru 111 #define EEPROM_ADDRESS_DNS 112 // 04bytes, thru 115 #define EEPROM_ADDRESS_MQTT_SERVER 116 // 04bytes, thru 119 #define EEPROM_ADDRESS_MQTT_PORT 120 // 02bytes, thru 121 #define EEPROM_ADDRESS_MQTT_USERNAME 122 // 32bytes, thru 153 #define EEPROM_ADDRESS_MQTT_PASSWORD 154 // 32bytes, thru 185 #define EEPROM_ADDRESS_MQTT_USEAUTH 186 // 1bytes #define EEPROM_ADDRESS_ADC_REPORT_STATE 187 // 8bytes, thru 194 #define EEPROM_ADDRESS_DAC_STATE 195 // 4bytes, thru 198 #define EEPROM_ADDRESS_DAC_VALUE 199 // 8bytes, thru 206 char PWM_STATE_TOPIC[75]; char PWM_VALUE_TOPIC[75]; char INPUTS_TOPIC[75]; WiFiClient eth; PubSubClient mqtt(MQTT_SERVER, 1883, eth); #ifdef ENABLE_CLIMATE_MODULE DHTNEW env_sensor(DHT22_PIN); #endif Thread mqtt_reconnector = Thread(); Thread environment_reporter = Thread(); Thread eeprom_pwm_updater = Thread(); Thread user_timer_tick = Thread(); Thread analog_handler = Thread(); StaticThreadController<5> thread_controller(&mqtt_reconnector, &environment_reporter, &eeprom_pwm_updater, &user_timer_tick, &analog_handler); #ifdef ENABLE_INTERNAL_LCD Thread top_bar_updater = Thread(); Thread page_updater = Thread(); StaticThreadController<2> lcd_thread_controller(&top_bar_updater, &page_updater); #endif void setup() { Serial.begin(115200); #ifdef ENABLE_EXTERNAL_LCD Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); #endif #ifdef ENABLE_INTERNAL_LCD panel.begin(115200); #endif Serial.println("ESPMega R3 Initializing"); ESPMega_begin(); #ifdef OVERCLOCK_FM2 Wire.setClock(1000000); #endif #ifdef OVERCLOCK_FM Wire.setClock(400000); #endif io_begin(); eeprom_retrieve_init(); user_pre_init(); #ifdef ENABLE_INTERNAL_LCD lcd_send_stop_bit(); lcd_init(); lcd_begin(); #endif check_boot_reset(); #ifdef ENABLE_EXTERNAL_LCD Serial2.print("rest"); Serial2.write(0xFF); Serial2.write(0xFF); Serial2.write(0xFF); #endif lcd_send_command("boot_state.txt=\"Core Initializing . . .\""); Serial.println("Initializing Infrared . . ."); #ifdef ENABLE_IR_MODULE lcd_send_command("boot_state.txt=\"Infrared Initializing . . .\""); IrReceiver.begin(IR_RECIEVE_PIN); IrSender.begin(IR_SEND_PIN); #endif lcd_send_command("boot_state.txt=\"Network Initializing . . .\""); network_begin(); lcd_send_command("boot_state.txt=\"IoT Core Initializing . . .\""); mqtt.setSocketTimeout(1000); eth.setTimeout(1); mqtt.setCallback(&mqtt_callback); mqtt_connect(); lcd_send_command("boot_state.txt=\"Threads Initializing . . .\""); thread_initialization(); #ifdef ENABLE_WEBUI ota_begin(); #endif Serial.println("Initialization Completed."); Serial.println("Jumping to User Code."); user_init(); lcd_send_command("page dashboard"); } void loop() { virtual_interrupt_loop(); mqtt.loop(); ESPMega_loop(); #ifdef ENABLE_IR_MODULE ir_loop(); #endif thread_controller.run(); #ifdef ENABLE_INTERNAL_LCD lcd_loop(); #endif user_loop(); #ifdef ENABLE_WEBUI otaserver.handleClient(); #endif } /** * @brief Retrieves data from EEPROM and initializes various variables and topics. * */ void eeprom_retrieve_init() { // EEPROM Data Retrival #ifdef ENABLE_CLIMATE_MODULE ac_mode = ESPMega_FRAM.read8(EEPROM_ADDRESS_AC_MODE); ac_temperature = ESPMega_FRAM.read8(EEPROM_ADDRESS_AC_TEMPERATURE); ac_fan_speed = ESPMega_FRAM.read8(EEPROM_ADDRESS_AC_FAN_SPEED); #endif // EEPROM Data Retrival Validation #ifdef ENABLE_CLIMATE_MODULE if (ac_mode > 2) { ac_mode = 0; ESPMega_FRAM.write8(EEPROM_ADDRESS_AC_MODE, ac_mode); } if (ac_temperature > AC_MAX_TEMPERATURE || ac_temperature < AC_MIN_TEMPERATURE) { ac_temperature = AC_MAX_TEMPERATURE; ESPMega_FRAM.write8(EEPROM_ADDRESS_AC_TEMPERATURE, ac_temperature); } if (ac_fan_speed > 3) { ac_fan_speed = 0; ESPMega_FRAM.write8(EEPROM_ADDRESS_AC_TEMPERATURE, ac_fan_speed); } ac_set_state(ac_mode, ac_temperature, ac_fan_speed); #endif ESPMega_FRAM.read(EEPROM_ADDRESS_PWM_STATE, pwm_states_eeprom, 16); memcpy(pwm_states, pwm_states_eeprom, 16); ESPMega_FRAM.read(EEPROM_ADDRESS_PWM_VALUE, pwm_values_eeprom, 32); memcpy(pwm_values, pwm_values_eeprom, 32); for (int i = 0; i < 15; i++) { if (pwm_states[i] <= 1) pwm_set_state(i, pwm_states[i]); else pwm_set_state(i, 0); if (pwm_values[i] <= 4095) pwm_set_value(i, pwm_values[i]); else pwm_set_value(i, 0); } IP = eeprom_ip_retrieve(EEPROM_ADDRESS_IP); SUBNET = eeprom_ip_retrieve(EEPROM_ADDRESS_SUBNET); GATEWAY = eeprom_ip_retrieve(EEPROM_ADDRESS_GATEWAY); DNS = eeprom_ip_retrieve(EEPROM_ADDRESS_DNS); MQTT_SERVER = eeprom_ip_retrieve(EEPROM_ADDRESS_MQTT_SERVER); eeprom_hostname_retrieve(); eeprom_mqtt_port_retrieve(); eeprom_mqtt_useauth_retrieve(); eeprom_mqtt_username_retrieve(); eeprom_mqtt_password_retrieve(); mqtt.setServer(MQTT_SERVER, MQTT_PORT); eeprom_basetopic_retrieve(); base_topic_length = strlen(MQTT_BASE_TOPIC) + 1; memcpy(STATE_REQUEST_TOPIC, MQTT_BASE_TOPIC, 20); strcat(STATE_REQUEST_TOPIC, "/requeststate"); memcpy(PWM_SET_STATE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(PWM_SET_STATE_TOPIC, "/pwm/00/set/state"); memcpy(PWM_SET_VALUE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(PWM_SET_VALUE_TOPIC, "/pwm/00/set/value"); #ifdef ENABLE_CLIMATE_MODULE memcpy(AC_SET_MODE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_SET_MODE_TOPIC, "/ac/set/mode"); memcpy(AC_SET_FAN_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_SET_FAN_TOPIC, "/ac/set/fan_speed"); memcpy(AC_SET_TEMPERATURE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_SET_TEMPERATURE_TOPIC, "/ac/set/temperature"); memcpy(AC_MODE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_MODE_TOPIC, "/ac/mode"); memcpy(AC_FAN_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_FAN_TOPIC, "/ac/fan_speed"); memcpy(AC_TEMPERATURE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_TEMPERATURE_TOPIC, "/ac/temperature"); memcpy(AC_ROOM_TEMPERATURE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_ROOM_TEMPERATURE_TOPIC, "/ac/room_temperature"); memcpy(AC_HUMIDITY_TOPIC, MQTT_BASE_TOPIC, 20); strcat(AC_HUMIDITY_TOPIC, "/ac/humidity"); #endif memcpy(PWM_STATE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(PWM_STATE_TOPIC, "/pwm/00/state"); memcpy(PWM_VALUE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(PWM_VALUE_TOPIC, "/pwm/00/value"); memcpy(INPUTS_TOPIC, MQTT_BASE_TOPIC, 20); strcat(INPUTS_TOPIC, "/input/00"); #ifdef ENABLE_ANALOG_MODULE memcpy(ADC_COMMAND_TOPIC, MQTT_BASE_TOPIC, 20); strcat(ADC_COMMAND_TOPIC, "/adc/00/set/state"); memcpy(ADC_STATE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(ADC_STATE_TOPIC, "/adc/00/state"); memcpy(ADC_REPORT_TOPIC, MQTT_BASE_TOPIC, 20); strcat(ADC_REPORT_TOPIC, "/adc/00/report"); memcpy(DAC_SET_STATE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(DAC_SET_STATE_TOPIC, "/dac/00/set/state"); memcpy(DAC_SET_VALUE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(DAC_SET_VALUE_TOPIC, "/dac/00/set/value"); memcpy(DAC_STATE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(DAC_STATE_TOPIC, "/dac/00/state"); memcpy(DAC_VALUE_TOPIC, MQTT_BASE_TOPIC, 20); strcat(DAC_VALUE_TOPIC, "/dac/00/value"); ESPMega_FRAM.read(EEPROM_ADDRESS_ADC_REPORT_STATE, (uint8_t *)adc_report_enable, 8); ESPMega_FRAM.read(EEPROM_ADDRESS_DAC_STATE, (uint8_t *)dac_states, 4); ESPMega_FRAM.read(EEPROM_ADDRESS_DAC_VALUE, (uint8_t *)dac_values, 8); for (int i = 0; i < DAC_COUNT; i++) { dac_set_state(i, dac_states[i]); dac_set_value(i, dac_values[i]); } #endif } #ifdef ENABLE_WEBUI /** * Initializes the OTA (Over-The-Air) update server and sets up the necessary routes for configuration and firmware updates. * * The OTA server can be accessed through the root URL ("/") and the configuration page can be accessed through "/config". * The firmware update can be initiated through the "/update" route using HTTP POST method. * * The configuration page allows the user to set the device IP address, network mask, gateway, DNS server, hostname, BMS server IP address, BMS server port, BMS server endpoint, and BMS server authentication credentials. * * @return void */ void ota_begin() { otaserver.on("/", HTTP_GET, []() { otaserver.sendHeader("Connection", "close"); String otabuffer = ota_part1; otabuffer+=ota_part2_1+"Hostname"+ota_part2_2+String(HOSTNAME)+ota_part2_3; otabuffer+=ota_part2_1+"IP Address"+ota_part2_2+IP.toString()+ota_part2_3; otabuffer+=ota_part2_1+"MAC Address"+ota_part2_2+ETH.macAddress()+ota_part2_3; otabuffer+=ota_part2_1+"Device"+ota_part2_2+ESPMEGA_REV+ota_part2_3; otabuffer+=ota_part2_1+"BMS Server"+ota_part2_2+MQTT_SERVER.toString()+ota_part2_3; otabuffer+=ota_part2_1+"BMS Endpoint"+ota_part2_2+String(MQTT_BASE_TOPIC)+ota_part2_3; otabuffer+=ota_part2_1+"Centrally Managed"+ota_part2_2; if(standalone) otabuffer+=String("No"); else otabuffer+=String("Yes"); otabuffer+=ota_part2_3+ota_part3; otaserver.send(200, "text/html", otabuffer); }); otaserver.on("/config", HTTP_GET, []() { otaserver.sendHeader("Connection", "close"); String configbuffer = config_part1; configbuffer+=config_txt_part1+"IP Address"+config_txt_part2+"text"+config_txt_part3+"dev_ip"+config_txt_part4+"dev_ip"+config_txt_part5+IP.toString()+config_txt_part6; configbuffer+=config_txt_part1+"Network Mask"+config_txt_part2+"text"+config_txt_part3+"netmask"+config_txt_part4+"netmask"+config_txt_part5+SUBNET.toString()+config_txt_part6; configbuffer+=config_txt_part1+"Gateway"+config_txt_part2+"text"+config_txt_part3+"gateway"+config_txt_part4+"gateway"+config_txt_part5+GATEWAY.toString()+config_txt_part6; configbuffer+=config_txt_part1+"DNS Server"+config_txt_part2+"text"+config_txt_part3+"dns"+config_txt_part4+"dns"+config_txt_part5+DNS.toString()+config_txt_part6; configbuffer+=config_txt_part1+"Hostname"+config_txt_part2+"text"+config_txt_part3+"hostname"+config_txt_part4+"hostname"+config_txt_part5+String(HOSTNAME)+config_txt_part6; configbuffer+=config_txt_part1+"BMS Server - IP Address"+config_txt_part2+"text"+config_txt_part3+"bms_ip"+config_txt_part4+"bms_ip"+config_txt_part5+MQTT_SERVER.toString()+config_txt_part6; configbuffer+=config_txt_part1+"BMS Server - Port"+config_txt_part2+"text"+config_txt_part3+"bms_port"+config_txt_part4+"bms_port"+config_txt_part5+String(MQTT_PORT)+config_txt_part6; configbuffer+=config_auth_part1+(MQTT_USE_AUTH?"checked=\"checked\"":"")+config_auth_part2; configbuffer+=config_txt_part1+"BMS Server - Username"+config_txt_part2+"text"+config_txt_part3+"bms_username"+config_txt_part4+"bms_username"+config_txt_part5+String(MQTT_USERNAME)+config_txt_part6; configbuffer+=config_txt_part1+"BMS Server - Password"+config_txt_part2+"password"+config_txt_part3+"bms_password"+config_txt_part4+"bms_password"+config_txt_part5+String(MQTT_PASSWORD)+config_txt_part6; configbuffer+=config_txt_part1+"BMS Server - Endpoint"+config_txt_part2+"text"+config_txt_part3+"bms_endpoint"+config_txt_part4+"bms_endpoint"+config_txt_part5+String(MQTT_BASE_TOPIC)+config_txt_part6; configbuffer+=config_part2; otaserver.send(200, "text/html", configbuffer); }); otaserver.on("/save_config", HTTP_GET, []() { otaserver.sendHeader("Connection", "close"); String configbuffer = "Configuration Saved. Rebooting . . ."; otaserver.send(200, "text/html", configbuffer); bool use_auth = false; for(int i=0;i DEBOUNCE_TIME_MS) { virtual_interupt_state[i] = current_pin_value; virtual_interrupt_callback(i, current_pin_value); } } else { virtual_interupt_timer[i] = millis(); } yield(); } } /** * @brief Publishes the current state of all PWM channels. * */ void publish_pwm_states() { for (int i = 0; i < PWM_COUNT; i++) { publish_pwm_state(i); } } /** * @brief Publishes the state and value of a PWM signal to MQTT broker. * * @param id The ID of the PWM signal. */ void publish_pwm_state(int id) { int state = pwm_states[id]; int value = pwm_values[id]; PWM_STATE_TOPIC[base_topic_length + 4] = ((id - id % 10) / 10) + '0'; PWM_STATE_TOPIC[base_topic_length + 5] = (id % 10) + '0'; PWM_VALUE_TOPIC[base_topic_length + 4] = ((id - id % 10) / 10) + '0'; PWM_VALUE_TOPIC[base_topic_length + 5] = (id % 10) + '0'; if (state == 1) { mqtt.publish(PWM_STATE_TOPIC, "on"); } else if (state == 0) { mqtt.publish(PWM_STATE_TOPIC, "off"); } char temp[6]; itoa(value, temp, DEC); mqtt.publish(PWM_VALUE_TOPIC, temp); } /** * @brief Sets the state of a PWM pin and publishes the new state. * * @param id The ID of the PWM pin. * @param state The new state of the PWM pin. */ void pwm_set_state(int id, int state) { if (state != pwm_states[id]) { pwm_states[id] = state; int pwm_value = pwm_values[id]; ESPMega_analogWrite(pwm_pins[id], state * (int)(pwm_linear_scaling_m[id] * pwm_value + pwm_linear_scaling_c[id])); #ifdef ENABLE_INTERNAL_LCD if (lcd_current_page == 3) panel.writeNum("j" + String(id) + ".ppic", pwm_states[id] ? 33 : 48); else if (lcd_current_page == 5 && id == lcd_pwmAdj_id) panel.writeStr("pwm_state.txt", pwm_states[lcd_pwmAdj_id] ? "ON" : "OFF"); #endif publish_pwm_state(id); pwm_changed_user_callback(id); } } /** * @brief Sets the PWM value for a given id. * * @param id The id of the PWM pin. * @param value The value to set the PWM pin to. */ void pwm_set_value(int id, int value) { pwm_values[id] = value; int pwm_state = pwm_states[id]; ESPMega_analogWrite(pwm_pins[id], pwm_state * (int)(pwm_linear_scaling_m[id] * value + pwm_linear_scaling_c[id])); #ifdef ENABLE_INTERNAL_LCD if (lcd_current_page == 3) panel.writeNum("j" + String(id) + ".val", int(value / 4095.0 * 100.0)); else if (lcd_current_page == 5 && id == lcd_pwmAdj_id) panel.writeNum("pwm_value.val", pwm_values[lcd_pwmAdj_id]); #endif publish_pwm_state(id); pwm_changed_user_callback(id); } /** * @brief Toggles the state of a PWM pin. * * @param id The ID of the PWM pin to toggle. */ void pwm_toggle(int id) { int state = !pwm_states[id]; pwm_set_state(id, state); } /** * Toggles the state of two PWM pins. * @param id1 The first PWM pin ID. * @param id2 The second PWM pin ID. */ void pwm_toggle(int id1, int id2) { boolean state = pwm_group_state(id1, id2); if (state) { pwm_set_state(id1, 0); pwm_set_state(id2, 0); } else { pwm_set_state(id1, 1); pwm_set_state(id2, 1); } } /** * @brief Returns a boolean value indicating whether either of the two specified PWM states is true. * * @param id1 The first PWM state ID. * @param id2 The second PWM state ID. * @return true if either of the two specified PWM states is true, false otherwise. */ boolean pwm_group_state(int id1, int id2) { int state1 = pwm_states[id1 - 1], state2 = pwm_states[id2 - 1]; if (state1 || state2) return true; return false; } /** * @brief Cycles the PWM value for the given id. * * @param id The id of the PWM pin. */ void pwm_cycle_value(int id) { int state = pwm_states[id]; int value = pwm_values[id]; if (state == 1) for (int i = 0; i < PWM_CYCLE_VALUES_COUNT; i++) { if (PWM_CYCLE_VALUES[i] == value) { if (i > 0) { pwm_set_value(id, PWM_CYCLE_VALUES[i - 1]); return; } else { pwm_set_state(id, 0); return; } } } pwm_set_state(id, 1); pwm_set_value(id, PWM_CYCLE_VALUES[PWM_CYCLE_VALUES_COUNT - 1]); } /** * @brief Publishes the input states for all virtual input pins. * */ void publish_input_states() { for (int i = 0; i < VINT_COUNT; i++) { publish_input_state(i); } } /** * @brief Publishes the input state of a specific pin. * * @param id The ID of the pin to publish the state of. */ void publish_input_state(int id) { int state = ESPMega_digitalRead(virtual_interrupt_pins[id]); publish_input_state(id, state); } /** * @brief Publishes the state of an input to the MQTT broker. * * @param id The ID of the input. * @param state The state of the input (0 or 1). */ void publish_input_state(int id, int state) { INPUTS_TOPIC[base_topic_length + 6] = ((id - id % 10) / 10) + '0'; INPUTS_TOPIC[base_topic_length + 7] = (id % 10) + '0'; mqtt.publish(INPUTS_TOPIC, state ? "1" : "0"); } /** * @brief Callback function to request the current state of the device. * * This function publishes the input and PWM states of the device. If the climate module is enabled, it also publishes the AC and environment states. * Finally, it calls the user-defined state request callback function. */ void state_request_callback() { publish_input_states(); publish_pwm_states(); #ifdef ENABLE_CLIMATE_MODULE publish_ac_state(); publish_env_state(); #endif user_state_request_callback(); publish_all_adc(); publish_dac_states(); publish_dac_values(); } #ifdef ENABLE_IR_MODULE void ir_loop() { if (IrReceiver.decode()) { IrReceiver.resume(); } } #endif #ifdef ENABLE_CLIMATE_MODULE /** * @brief Publishes the current state of the air conditioner to MQTT topics. * */ void publish_ac_state() { switch (ac_mode) { case 0: mqtt.publish(AC_MODE_TOPIC, "off"); break; case 1: mqtt.publish(AC_MODE_TOPIC, "cool"); break; case 2: mqtt.publish(AC_MODE_TOPIC, "fan_only"); default: break; } char temp[5]; itoa(ac_temperature, temp, DEC); mqtt.publish(AC_TEMPERATURE_TOPIC, temp); switch (ac_fan_speed) { case 0: mqtt.publish(AC_FAN_TOPIC, "auto"); break; case 1: mqtt.publish(AC_FAN_TOPIC, "high"); break; case 2: mqtt.publish(AC_FAN_TOPIC, "medium"); break; case 3: mqtt.publish(AC_FAN_TOPIC, "low"); break; } } /** * @brief Callback function for handling AC state changes. * * This function is called whenever a message is received on the AC_SET_TEMPERATURE_TOPIC, AC_SET_MODE_TOPIC, or AC_SET_FAN_TOPIC. * It parses the payload of the message and updates the AC state accordingly. * * @param topic The topic of the message. * @param topic_length The length of the topic. * @param payload The payload of the message. * @param payload_length The length of the payload. */ void ac_state_callback(char *topic, uint8_t topic_length, char *payload, unsigned int payload_length) { if (!strcmp(topic, AC_SET_TEMPERATURE_TOPIC)) { int new_temp = atoi(payload); if (new_temp >= AC_MIN_TEMPERATURE && new_temp <= AC_MAX_TEMPERATURE) { ac_set_state(ac_mode, new_temp, ac_fan_speed); } } else if (!strcmp(topic, AC_SET_MODE_TOPIC)) { if (!strcmp(payload, "off")) { ac_set_state(0, ac_temperature, ac_fan_speed); } else if (!strcmp(payload, "cool")) { ac_set_state(1, ac_temperature, ac_fan_speed); } else if (!strcmp(payload, "fan_only")) { ac_set_state(2, ac_temperature, ac_fan_speed); } } else if (!strcmp(topic, AC_SET_FAN_TOPIC)) { if (!strcmp(payload, "auto")) { ac_set_state(ac_mode, ac_temperature, 0); } else if (!strcmp(payload, "high")) { ac_set_state(ac_mode, ac_temperature, 1); } else if (!strcmp(payload, "medium")) { ac_set_state(ac_mode, ac_temperature, 2); } else if (!strcmp(payload, "low")) { ac_set_state(ac_mode, ac_temperature, 3); } } } /** * @brief Sets the state of the air conditioner. * * @param mode The mode of the air conditioner (0 = off, 1 = cool, 2 = fan). * @param temperature The temperature to set the air conditioner to (in Celsius). * @param fan_speed The fan speed of the air conditioner (0 = low, 1 = medium, 2 = high). */ void ac_set_state(int mode, int temperature, int fan_speed) { ac_mode = mode; ac_temperature = temperature; if (ac_temperature < AC_MIN_TEMPERATURE) ac_temperature = AC_MIN_TEMPERATURE; else if (ac_temperature > AC_MAX_TEMPERATURE) ac_temperature = AC_MAX_TEMPERATURE; ac_fan_speed = fan_speed; temperature -= AC_MIN_TEMPERATURE; #ifdef ENABLE_INTERNAL_LCD if (lcd_current_page == 4) { lcd_ac_refresh_fan(); lcd_ac_refresh_mode(); if (ac_mode != 2) panel.writeStr("temp.txt", String(ac_temperature) + "C"); else panel.writeStr("temp.txt", "--C"); } #endif publish_ac_state(); uint8_t ac_datablock[3] = {ac_mode, ac_temperature, ac_fan_speed}; ESPMega_FRAM.write(0, ac_datablock, 3); switch (mode) { case 0: IrSender.sendRaw(ir_code_off, sizeof(ir_code_off) / sizeof(ir_code_off[0]), NEC_KHZ); break; case 1: IrSender.sendRaw(ir_code_cool[fan_speed][temperature], sizeof(ir_code_cool[fan_speed][temperature]) / sizeof(ir_code_cool[fan_speed][temperature][0]), NEC_KHZ); break; case 2: IrSender.sendRaw(ir_code_fan[fan_speed], sizeof(ir_code_fan[fan_speed]) / sizeof(ir_code_fan[fan_speed][0]), NEC_KHZ); break; } ac_changed_user_callback(mode, ac_temperature, fan_speed); } /** * @brief Reads the environment sensor and publishes the temperature and humidity to MQTT topics. * * @details Reads the environment sensor and publishes the temperature and humidity to MQTT topics. * If the internal LCD is enabled, it also updates the temperature and humidity values on the LCD screen. * * @return void */ void publish_env_state() { int errorCode = env_sensor.read(); yield(); switch (errorCode) { case DHTLIB_OK: current_room_humid = env_sensor.getHumidity(); current_room_temp = env_sensor.getTemperature(); char temp[5]; dtostrf(current_room_temp, 0, 2, temp); mqtt.publish(AC_ROOM_TEMPERATURE_TOPIC, temp); dtostrf(current_room_humid, 0, 2, temp); mqtt.publish(AC_HUMIDITY_TOPIC, temp); mqtt.loop(); #ifdef ENABLE_INTERNAL_LCD if (lcd_current_page == 4) { Serial.printf("roomtemp.txt=\"%.01fC\"", current_room_temp); lcd_send_stop_bit(); Serial.printf("roomhumid.txt=\"%d%%\"", (int)current_room_humid); lcd_send_stop_bit(); } #endif break; default: mqtt.publish(AC_ROOM_TEMPERATURE_TOPIC, "ERROR"); mqtt.loop(); mqtt.publish(AC_HUMIDITY_TOPIC, "ERROR"); mqtt.loop(); } } #endif #ifdef ENABLE_INTERNAL_LCD /** * @brief Initializes the LCD display and sets up the refresh intervals for the top bar and page. * */ void lcd_begin() { top_bar_updater.onRun(lcd_top_bar_update); top_bar_updater.setInterval(10000); page_updater.onRun(lcd_refresh_pd); page_updater.setInterval(5000); lcd_refresh(); } /** * @brief Runs the LCD loop, updating the display and listening for input. * */ void lcd_loop() { lcd_thread_controller.run(); panel.NextionListen(); if (panel.currentPageId != lcd_current_page) { lcd_current_page = panel.currentPageId; lcd_refresh(); lcd_top_bar_update(); } } /** * @brief Refreshes the LCD display if the current page is 1. * */ void lcd_refresh_pd() { if (lcd_current_page == 1) { lcd_refresh(); } } /** * @brief Refreshes the LCD display based on the current page. * * @details The function writes the appropriate values to the LCD display based on the current page. * * @note This function assumes that the LCD panel has already been initialized. * */ void lcd_refresh() { switch (lcd_current_page) { case 1: panel.writeStr("hostname.txt", HOSTNAME); panel.writeStr("server_address.txt", MQTT_SERVER.toString()); panel.writeStr("ip_address.txt", IP.toString()); panel.writeStr("status_txt.txt", standalone ? "Standalone" : "BMS Managed"); break; case 2: for (int i = 0; i <= 15; i++) { panel.writeNum("I" + String(i) + ".val", virtual_interupt_state[i]); } break; case 3: for (int i = 0; i <= 15; i++) { panel.writeNum("j" + String(i) + ".val", int(pwm_values[i] / 4095.0 * 100.0)); panel.writeNum("j" + String(i) + ".ppic", pwm_states[i] ? 33 : 48); } break; case 4: #ifdef ENABLE_CLIMATE_MODULE Serial.printf("roomtemp.txt=\"%.01fC\"", current_room_temp); lcd_send_stop_bit(); Serial.printf("roomhumid.txt=\"%d%%\"", (int)current_room_humid); lcd_send_stop_bit(); if (ac_mode != 2) panel.writeStr("temp.txt", String(ac_temperature) + "C"); else panel.writeStr("temp.txt", "--C"); lcd_ac_refresh_fan(); lcd_ac_refresh_mode(); #else lcd_send_command("page climate_nomod"); #endif case 5: panel.writeStr("pwm_id.txt", String("P") + String(lcd_pwmAdj_id)); panel.writeStr("pwm_state.txt", pwm_states[lcd_pwmAdj_id] ? "ON" : "OFF"); panel.writeNum("pwm_value.val", pwm_values[lcd_pwmAdj_id]); break; case 6: panel.writeStr("ip_set.txt", IP.toString()); panel.writeStr("netmask_set.txt", SUBNET.toString()); panel.writeStr("gateway_set.txt", GATEWAY.toString()); panel.writeStr("dns_set.txt", DNS.toString()); panel.writeStr("host_set.txt", HOSTNAME); break; case 11: panel.writeStr("mqttsv_set.txt", MQTT_SERVER.toString()); panel.writeNum("port_set.val", MQTT_PORT); panel.writeStr("topic_set.txt", MQTT_BASE_TOPIC); panel.writeStr("user_set.txt", MQTT_USERNAME); panel.writeStr("password_set.txt", MQTT_PASSWORD); panel.writeNum("use_auth.val", MQTT_USE_AUTH); break; default: break; } } /** * @brief Updates the top bar of the LCD display with the current time, server status, and LAN status. * * @details This function gets the current time using ESPMega_getTime() function and formats it to display on the LCD. * It also updates the server and LAN status icons based on the standalone and ETH.linkUp() values respectively. * * @return void */ void lcd_top_bar_update() { char time_buffer[15]; rtctime_t time = ESPMega_getTime(); sprintf(time_buffer, "%02d:%02d", time.hours, time.minutes); panel.writeStr("time.txt", time_buffer); panel.writeNum("server.pic", standalone ? 4 : 5); panel.writeNum("lan.pic", ETH.linkUp() ? 3 : 2); } #ifdef ENABLE_CLIMATE_MODULE /** * @brief Refreshes the LCD display with the current AC mode. * * This function updates the LCD display with the current AC mode, which can be one of the following: * - 0: Off * - 1: Cool * - 2: Fan * * The function uses the panel object to write the appropriate image to the display for each mode. */ void lcd_ac_refresh_mode() { // auto high mid low panel.writeNum("mode_cool.pic", ac_mode == 1 ? 12 : 13); panel.writeNum("mode_fan.pic", ac_mode == 2 ? 22 : 23); panel.writeNum("mode_off.pic", ac_mode == 0 ? 24 : 25); } /** * @brief Refreshes the fan speed icons on the LCD panel based on the current AC fan speed. * * This function updates the fan speed icons on the LCD panel based on the current AC fan speed. * It uses the panel.writeNum() function to write the appropriate fan speed icon to the panel. * */ void lcd_ac_refresh_fan() { panel.writeNum("fan_auto.pic", ac_fan_speed == 0 ? 14 : 15); panel.writeNum("fan_low.pic", ac_fan_speed == 3 ? 18 : 19); panel.writeNum("fan_mid.pic", ac_fan_speed == 2 ? 20 : 21); panel.writeNum("fan_high.pic", ac_fan_speed == 1 ? 16 : 17); } #endif void trigger0() { if (lcd_pwmAdj_id >= 15) lcd_pwmAdj_id = 0; else lcd_pwmAdj_id++; lcd_refresh(); } void trigger1() { if (lcd_pwmAdj_id <= 0) lcd_pwmAdj_id = 15; else lcd_pwmAdj_id--; lcd_refresh(); } void trigger2() { pwm_toggle(lcd_pwmAdj_id); } void trigger3() { int value = panel.readNumber("pwm_value.val"); lcd_send_stop_bit(); pwm_set_value(lcd_pwmAdj_id, value); } void trigger4() { #ifdef ENABLE_CLIMATE_MODULE if (ac_temperature < AC_MAX_TEMPERATURE && ac_mode != 2) ac_set_state(ac_mode, ac_temperature + 1, ac_fan_speed); #endif } void trigger5() { #ifdef ENABLE_CLIMATE_MODULE if (ac_temperature > AC_MIN_TEMPERATURE && ac_mode != 2) ac_set_state(ac_mode, ac_temperature - 1, ac_fan_speed); #endif } void trigger6() { #ifdef ENABLE_CLIMATE_MODULE ac_set_state(ac_mode, ac_temperature, 0); #endif } void trigger7() { #ifdef ENABLE_CLIMATE_MODULE ac_set_state(ac_mode, ac_temperature, 3); #endif } void trigger8() { #ifdef ENABLE_CLIMATE_MODULE ac_set_state(ac_mode, ac_temperature, 2); #endif } void trigger9() { #ifdef ENABLE_CLIMATE_MODULE ac_set_state(ac_mode, ac_temperature, 1); #endif } void trigger10() { #ifdef ENABLE_CLIMATE_MODULE ac_set_state(1, ac_temperature, ac_fan_speed); #endif } void trigger11() { #ifdef ENABLE_CLIMATE_MODULE ac_set_state(2, ac_temperature, ac_fan_speed); #endif } void trigger12() { #ifdef ENABLE_CLIMATE_MODULE ac_set_state(0, ac_temperature, ac_fan_speed); #endif } void trigger13() { set_ip(panel.readStr("ip_set.txt")); set_netmask(panel.readStr("netmask_set.txt")); set_gw(panel.readStr("gateway_set.txt")); set_dns(panel.readStr("dns_set.txt")); set_hostname(panel.readStr("host_set.txt")); delay(100); ESP.restart(); } void trigger14() { Serial.print("page dashboard"); lcd_send_stop_bit(); } void trigger15() { set_mqtt_server(panel.readStr("mqttsv_set.txt")); set_basetopic(panel.readStr("topic_set.txt")); uint16_t port = panel.readNumber("port_set.val"); mqtt_port_set(port); set_mqtt_useauth(panel.readNumber("use_auth.val")); set_mqtt_username(panel.readStr("user_set.txt")); set_mqtt_password(panel.readStr("password_set.txt")); delay(100); ESP.restart(); } #endif // End Internal LCD Code Block /** * @brief Updates the PWM states and values in EEPROM if they have changed. * * If the current PWM states or values are different from the ones stored in EEPROM, * this function updates the EEPROM with the current values. */ void eeprom_pwm_update() { if (memcmp(pwm_states, pwm_states_eeprom, 16)) { memcpy(pwm_states_eeprom, pwm_states, 16); ESPMega_FRAM.write(3, pwm_states_eeprom, 16); } if (memcmp(pwm_values, pwm_values_eeprom, 32)) { memcpy(pwm_values_eeprom, pwm_values, 32); ESPMega_FRAM.write(19, pwm_values_eeprom, 32); } } /** * @brief Sets the IP address and updates it in EEPROM. * * @param address The IP address to set. */ void set_ip(String address) { IP.fromString(address); eeprom_ip_update(EEPROM_ADDRESS_IP, IP[0], IP[1], IP[2], IP[3]); } /** * @brief Sets the subnet mask for the device. * * @param address The subnet mask address to set. */ void set_netmask(String address) { SUBNET.fromString(address); eeprom_ip_update(EEPROM_ADDRESS_SUBNET, SUBNET[0], SUBNET[1], SUBNET[2], SUBNET[3]); } /** * Sets the DNS server address. * @param address The IP address of the DNS server. */ void set_dns(String address) { DNS.fromString(address); eeprom_ip_update(EEPROM_ADDRESS_DNS, DNS[0], DNS[1], DNS[2], DNS[3]); } /** * @brief Sets the gateway IP address and updates the EEPROM. * * @param address The gateway IP address as a String. */ void set_gw(String address) { GATEWAY.fromString(address); eeprom_ip_update(EEPROM_ADDRESS_GATEWAY, GATEWAY[0], GATEWAY[1], GATEWAY[2], GATEWAY[3]); } /** * @brief Sets the MQTT server address and updates the EEPROM. * * @param address The MQTT server address as a String. */ void set_mqtt_server(String address) { MQTT_SERVER.fromString(address); eeprom_ip_update(EEPROM_ADDRESS_MQTT_SERVER, MQTT_SERVER[0], MQTT_SERVER[1], MQTT_SERVER[2], MQTT_SERVER[3]); } /** * @brief Updates the IP address in EEPROM at the specified ROM address. * * @param rom_address The ROM address where the IP address is stored. * @param byte1 The first byte of the IP address. * @param byte2 The second byte of the IP address. * @param byte3 The third byte of the IP address. * @param byte4 The fourth byte of the IP address. */ void eeprom_ip_update(uint16_t rom_address, uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4) { uint8_t addressblock[4] = {byte1, byte2, byte3, byte4}; ESPMega_FRAM.write(rom_address, addressblock, 4); } /** * @brief Retrieves an IP address from EEPROM memory. * * @param rom_address The address in EEPROM memory where the IP address is stored. * @return The retrieved IP address. */ IPAddress eeprom_ip_retrieve(uint16_t rom_address) { uint8_t addressblock[4]; ESPMega_FRAM.read(rom_address, addressblock, 4); return IPAddress(addressblock[0], addressblock[1], addressblock[2], addressblock[3]); } /** * @brief Sets the hostname for the device and writes it to non-volatile memory. * * @param hostname The hostname to set. */ void set_hostname(String hostname) { hostname.toCharArray(HOSTNAME, 15); ESPMega_FRAM.write(EEPROM_ADDRESS_HOSTNAME, (uint8_t *)HOSTNAME, 15); } /** * @brief Retrieves the hostname from EEPROM and stores it in the HOSTNAME variable. * */ void eeprom_hostname_retrieve() { ESPMega_FRAM.read(EEPROM_ADDRESS_HOSTNAME, (uint8_t *)HOSTNAME, 15); } /** * @brief Sets the base topic for MQTT communication and writes it to EEPROM. * * @param topic The base topic to set. */ void set_basetopic(String topic) { topic.toCharArray(MQTT_BASE_TOPIC, 20); ESPMega_FRAM.write(EEPROM_ADDRESS_TOPIC, (uint8_t *)MQTT_BASE_TOPIC, 20); } /** * @brief Retrieves the MQTT base topic from EEPROM and stores it in MQTT_BASE_TOPIC array. * */ void eeprom_basetopic_retrieve() { ESPMega_FRAM.read(EEPROM_ADDRESS_TOPIC, (uint8_t *)MQTT_BASE_TOPIC, 20); } /** * @brief Sets the MQTT port in the EEPROM. * * @param port The MQTT port to be set. */ void mqtt_port_set(uint16_t port) { uint8_t port_arr[2]; memcpy(port_arr, &port, 2); ESPMega_FRAM.write(EEPROM_ADDRESS_MQTT_PORT, port_arr, 2); } /** * @brief Retrieves the MQTT port from EEPROM and stores it in the MQTT_PORT variable. * */ void eeprom_mqtt_port_retrieve() { uint8_t port_arr[2]; ESPMega_FRAM.read(EEPROM_ADDRESS_MQTT_PORT, port_arr, 2); memcpy(&MQTT_PORT, port_arr, 2); } /** * @brief Returns the state of the PWM with the given ID. * * @param id The ID of the PWM. * @return true if the PWM is on, false otherwise. */ boolean pwm_get_state(int id) { return pwm_states[id]; } /** * Returns the current PWM value for the specified ID. * * @param id The ID of the PWM channel to get the value for. * @return The current PWM value for the specified ID. */ uint16_t pwm_get_value(int id) { return pwm_values[id]; } /** * @brief Returns the state of the virtual interrupt with the given ID. * * @param id The ID of the virtual interrupt. * @return The state of the virtual interrupt. */ boolean input_get_state(int id) { return virtual_interupt_state[id]; } #ifdef ENABLE_CLIMATE_MODULE /** * @brief Returns the current temperature value. * * @return The current temperature value. */ uint8_t ac_get_temperature() { return ac_temperature; } /** * @brief Get the current mode of the AC system. * * @return uint8_t The current mode of the AC system. */ uint8_t ac_get_mode() { return ac_mode; } /** * @brief Get the current fan speed. * * @return uint8_t The current fan speed. */ uint8_t ac_get_fan_speed() { return ac_fan_speed; } #endif /** * @brief Retrieves the MQTT username from EEPROM and stores it in the MQTT_USERNAME global variable. * */ void eeprom_mqtt_username_retrieve() { ESPMega_FRAM.read(EEPROM_ADDRESS_MQTT_USERNAME, (uint8_t *)MQTT_USERNAME, 32); } /** * @brief Retrieves the MQTT password from EEPROM and stores it in the MQTT_PASSWORD global variable. * */ void eeprom_mqtt_password_retrieve() { ESPMega_FRAM.read(EEPROM_ADDRESS_MQTT_PASSWORD, (uint8_t *)MQTT_PASSWORD, 32); } /** * @brief Sets the MQTT username and writes it to the EEPROM. * * @param username The MQTT username to set. */ void set_mqtt_username(String username) { username.toCharArray(MQTT_USERNAME, 32); ESPMega_FRAM.write(EEPROM_ADDRESS_MQTT_USERNAME, (uint8_t *)MQTT_USERNAME, 20); } /** * @brief Sets the MQTT password and writes it to the EEPROM. * * @param password The MQTT password to set. */ void set_mqtt_password(String password) { password.toCharArray(MQTT_PASSWORD, 32); ESPMega_FRAM.write(EEPROM_ADDRESS_MQTT_PASSWORD, (uint8_t *)MQTT_PASSWORD, 20); } void eeprom_mqtt_useauth_retrieve() { MQTT_USE_AUTH = ESPMega_FRAM.read8(EEPROM_ADDRESS_MQTT_USEAUTH); } /** * @brief Sets the MQTT_USE_AUTH flag and writes it to the EEPROM. * * @param use_auth A boolean value indicating whether to use authentication for MQTT. */ void set_mqtt_useauth(bool use_auth) { MQTT_USE_AUTH = use_auth; ESPMega_FRAM.write8(EEPROM_ADDRESS_MQTT_USEAUTH, MQTT_USE_AUTH); } /** * @brief Resets the device to factory default settings. * * This function resets the device to its factory default settings by formatting the FRAM, * setting default IP address, gateway, and netmask values, and restarting the device. * */ void factory_reset() { for (int i = 5; i > 0; i--) { if (digitalRead(2) == HIGH) { lcd_send_command("boot_state.txt=\"Factory Reset Canceled, Restarting\""); delay(5000); ESP.restart(); } #ifdef ENABLE_INTERNAL_LCD Serial.printf("boot_state.txt=\"Factory Reset in %d\"", i); lcd_send_stop_bit(); #endif delay(1000); } #ifdef ENABLE_INTERNAL_LCD lcd_send_command("boot_state.txt=\"Factory Reseting . . .\""); #endif // Format FRAM for (int i = 0; i < 32768; i++) { ESPMega_FRAM.write8(i, 0); } // Load Default Values set_ip("192.168.0.10"); set_gw("192.168.0.1"); set_netmask("255.255.255.0"); // Reboot #ifdef ENABLE_INTERNAL_LCD lcd_send_stop_bit(); lcd_send_command("boot_state.txt=\"Factory Reset OK. Release Button.\""); delay(3000); #endif ESP.restart(); } /** * @brief Checks if the device should perform a factory reset on boot. * * This function checks if pin 2 is pulled low on boot. If it is, the device will perform a factory reset. * */ void check_boot_reset() { pinMode(2, INPUT_PULLUP); if (digitalRead(2) == LOW) { factory_reset(); } } #ifdef ENABLE_ANALOG_MODULE /** * Enables the ADC reporting for the specified ID. * * @param id The ID of the ADC to enable reporting for. */ void enable_adc(int id) { adc_report_enable[id] = true; ESPMega_FRAM.write8(EEPROM_ADDRESS_ADC_REPORT_STATE + id, 1); } /** * @brief Disables the ADC reporting for the specified ID. * * This function sets the adc_report_enable flag to false for the specified ID and writes the state to the EEPROM. * * @param id The ID of the ADC to disable reporting for. */ void disable_adc(int id) { adc_report_enable[id] = false; ESPMega_FRAM.write8(EEPROM_ADDRESS_ADC_REPORT_STATE + id, 0); } /** * @brief Updates the ADC value for the specified ID if ADC reporting is enabled. * * @param id The ID of the ADC channel. */ void adc_update(int id) { if (adc_report_enable[id]) { adc_values[id] = ESPMega_analogRead(id); } } /** * @brief Updates the ADC value for the specified ID, do so even if reporting is disabled.. * * @param id The ID of the ADC pin. */ void adc_update_force(int id) { adc_values[id] = ESPMega_analogRead(id); } /** * @brief Updates all ADC channels. * * This function updates all ADC channels by calling the `adc_update` function for each channel. * * @return void */ void adc_update_all() { for (int i = 0; i < ADC_COUNT; i++) { adc_update(i); } } /** * @brief Performs ADC loop operations. * * This function updates all ADC values and publishes them. */ void adc_loop() { adc_update_all(); publish_all_adc(); } /** * Publishes the ADC value to the MQTT broker. * * @param id The ID of the ADC channel. */ void publish_adc(int id) { ADC_STATE_TOPIC[base_topic_length + 4] = ((id - id % 10) / 10) + '0'; ADC_STATE_TOPIC[base_topic_length + 5] = (id % 10) + '0'; char temp[6]; itoa(adc_values[id], temp, DEC); mqtt.publish(ADC_STATE_TOPIC, temp); } /** * Publishes the values of all enabled ADC channels. * This function iterates through all ADC channels and publishes the values * of the enabled channels using the publish_adc() function. */ void publish_all_adc() { for (int i = 0; i < ADC_COUNT; i++) { if (adc_report_enable[i]) publish_adc(i); } } void publish_dac_state(int id) { DAC_STATE_TOPIC[base_topic_length + 4] = ((id - id % 10) / 10) + '0'; DAC_STATE_TOPIC[base_topic_length + 5] = (id % 10) + '0'; mqtt.publish(DAC_STATE_TOPIC, dac_states[id] ? "on" : "off"); } void publish_dac_value(int id) { DAC_VALUE_TOPIC[base_topic_length + 4] = ((id - id % 10) / 10) + '0'; DAC_VALUE_TOPIC[base_topic_length + 5] = (id % 10) + '0'; char temp[6]; itoa(dac_values[id], temp, DEC); mqtt.publish(DAC_VALUE_TOPIC, temp); } /** * @brief Retrieves the ADC value for the specified ID. * * This function checks if the ADC report is enabled for the given ID. If not, it forces an update. * It then returns the ADC value for the specified ID. * * @param id The ID of the ADC channel. * @return The ADC value for the specified ID. */ uint16_t get_adc_value(int id) { if (!adc_report_enable[id]) adc_update_force(id); return adc_values[id]; } /** * @brief Sets the state of an ADC based on the received MQTT message. * * This function is called when an MQTT message is received to set the state of an ADC (Analog-to-Digital Converter). * The function extracts the ADC ID from the topic and enables or disables the ADC based on the payload value. * * @param topic The topic of the MQTT message. * @param topic_length The length of the topic. * @param payload The payload of the MQTT message. * @param payload_length The length of the payload. */ void adc_set_state_callback(char *topic, uint8_t topic_length, char *payload, unsigned int payload_length) { int a = topic[4] - '0'; int b = topic[5] - '0'; int id = 10 * a + b; if (!strcmp(payload, "on")) { enable_adc(id); } else if (!strcmp(payload, "off")) { disable_adc(id); } } /** * @brief Sets the value of a DAC channel. * * This function sets the value of a DAC channel specified by the `id` parameter. * The `value` parameter represents the desired value for the DAC channel. * The function updates the internal DAC value array, writes the value to the DAC, * and also stores the value in the FRAM memory. * * @param id The ID of the DAC channel. * @param value The desired value for the DAC channel. */ void dac_set_value(int id, int value) { dac_values[id] = value; ESPMega_dacWrite(id, dac_values[id] * dac_states[id]); ESPMega_FRAM.write16(EEPROM_ADDRESS_DAC_VALUE + id * 2, dac_values[id]); publish_dac_value(id); } /** * @brief Sets the state of a DAC channel. * * This function updates the state of a DAC channel and writes the new state to the DAC output. * It also saves the state to the EEPROM for persistence across power cycles. * * @param id The ID of the DAC channel. * @param state The new state of the DAC channel. */ void dac_set_state(int id, bool state) { dac_states[id] = state; ESPMega_dacWrite(id, dac_values[id] * dac_states[id]); ESPMega_FRAM.write8(EEPROM_ADDRESS_DAC_STATE + id, dac_states[id]); publish_dac_state(id); } void publish_dac_states() { for (int i = 0; i < DAC_COUNT; i++) { publish_dac_state(i); } } void publish_dac_values() { for (int i = 0; i < DAC_COUNT; i++) { publish_dac_value(i); } } /** * @brief Sets the value of the DAC with a callback function. * * @param topic The topic of the message. * @param topic_length The length of the topic string. * @param payload The payload of the message. * @param payload_length The length of the payload string. */ void dac_set_value_callback(char *topic, uint8_t topic_length, char *payload, unsigned int payload_length) { int a = topic[4] - '0'; int b = topic[5] - '0'; int id = 10 * a + b; int value = atoi(payload); dac_set_value(id, value); } /** * @brief Callback function for setting the state of the DAC. * * This function is called when a message is received on the specified topic. * It takes the topic, topic length, payload, and payload length as parameters. * * @param topic The topic of the received message. * @param topic_length The length of the topic string. * @param payload The payload of the received message. * @param payload_length The length of the payload string. */ void dac_set_state_callback(char *topic, uint8_t topic_length, char *payload, unsigned int payload_length) { int a = topic[4] - '0'; int b = topic[5] - '0'; int id = 10 * a + b; if (!strcmp(payload, "on")) { dac_set_state(id, true); } else if (!strcmp(payload, "off")) { dac_set_state(id, false); } } #endif