diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b0a4f7e --- /dev/null +++ b/readme.md @@ -0,0 +1,35 @@ +# IoT Core OS V3 +This is an OS for the ESPMega PRO R3 Programable Logic Controller + +## **Compatibility** +1. **CPU** + - ESPMega PRO R3.0a/b + - ESPMega PRO R3.1a + - ESPMega PRO R3.2a/b/c +2. **CPU Add-ons** + - ESPMega PRO Internal Display Module + - ESPMega PRO External Touch Display Module +3. **Add-on Cards** + - ESPMega I/O Analog Expansion Card + - ESPMega I/O IR Expansion Kit + - ESPMega I/O Card Hub + - ESPMega I/O UART Multiplexer [WIP] + - ESPMega I/O Digital Expansion Card [WIP] + +## Features +- Internal Touch Display support for diagnostics and configuration +- WebUI for Configuration and OTA Update +- Allowing for reading and writing to registers from MQTT +- Provides abstraction layer to the MQTT protocol and internal components + +## User Code and 3rd Party Extension +This OS allows the user to write custom program for the device to run in the OS
+### *usercode.hpp* and *user_code.cpp* +### I/O Abstraction Layer +### MQTT Abstraction Layer +### RTC and Clock Abstraction Layer +### Persistent Storage Abstraction Layer +### Climate Abstraction Layer +### Energy Monitoring Abstraction Layer +### Timer Abstraction Layer +### LCD Abstraction Layer diff --git a/src/espmega_iot_core.cpp b/src/espmega_iot_core.cpp index af1b163..64a7450 100644 --- a/src/espmega_iot_core.cpp +++ b/src/espmega_iot_core.cpp @@ -78,7 +78,6 @@ 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; @@ -92,23 +91,43 @@ 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_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]; @@ -125,7 +144,8 @@ Thread mqtt_reconnector = Thread(); Thread environment_reporter = Thread(); Thread eeprom_pwm_updater = Thread(); Thread user_timer_tick = Thread(); -StaticThreadController<4> thread_controller(&mqtt_reconnector, &environment_reporter, &eeprom_pwm_updater, &user_timer_tick); +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(); @@ -144,12 +164,12 @@ void setup() #endif Serial.println("ESPMega R3 Initializing"); ESPMega_begin(); - #ifdef OVERCLOCK_FM2 +#ifdef OVERCLOCK_FM2 Wire.setClock(1000000); - #endif - #ifdef OVERCLOCK_FM +#endif +#ifdef OVERCLOCK_FM Wire.setClock(400000); - #endif +#endif io_begin(); eeprom_retrieve_init(); user_pre_init(); @@ -297,6 +317,30 @@ void eeprom_retrieve_init() 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 @@ -534,6 +578,12 @@ void mqtt_connect() publish_ac_state(); #endif mqtt_connected_user_callback(); +#ifdef ENABLE_ANALOG_MODULE + publish_dac_states(); + publish_dac_values(); + publish_adc_values(); + publish_adc_states(); +#endif standalone = false; ESPMega_updateTimeFromNTP(); } @@ -572,6 +622,23 @@ void mqtt_subscribe() mqtt.subscribe(AC_SET_MODE_TOPIC); #endif mqtt.subscribe(STATE_REQUEST_TOPIC); +#ifdef ENABLE_ANALOG_MODULE + for (int i = 0; i < ADC_COUNT; i++) + { + ADC_COMMAND_TOPIC[base_topic_length + 4] = ((i - i % 10) / 10) + '0'; + ADC_COMMAND_TOPIC[base_topic_length + 5] = (i % 10) + '0'; + mqtt.subscribe(ADC_COMMAND_TOPIC); + } + for (int i = 0; i < DAC_COUNT; i++) + { + DAC_SET_STATE_TOPIC[base_topic_length + 4] = ((i - i % 10) / 10) + '0'; + DAC_SET_STATE_TOPIC[base_topic_length + 5] = (i % 10) + '0'; + DAC_SET_VALUE_TOPIC[base_topic_length + 4] = ((i - i % 10) / 10) + '0'; + DAC_SET_VALUE_TOPIC[base_topic_length + 5] = (i % 10) + '0'; + mqtt.subscribe(DAC_SET_STATE_TOPIC); + mqtt.subscribe(DAC_SET_VALUE_TOPIC); + } +#endif } /** @@ -597,6 +664,20 @@ void mqtt_callback(char *topic, byte *payload, unsigned int length) { pwm_value_callback(topic_trim, topic_length, payload_nt, length); } +#ifdef ENABLE_ANALOG_MODULE + else if ((!strncmp(topic_trim, "/adc/", 5)) && !strncmp(topic_trim + 7, "/set/state", 10)) + { + adc_set_state_callback(topic_trim, topic_length, payload_nt, length); + } + else if ((!strncmp(topic_trim, "/dac/", 5)) && !strncmp(topic_trim + 7, "/set/state", 10)) + { + dac_set_state_callback(topic_trim, topic_length, payload_nt, length); + } + else if ((!strncmp(topic_trim, "/dac/", 5)) && !strncmp(topic_trim + 7, "/set/value", 10)) + { + dac_set_value_callback(topic_trim, topic_length, payload_nt, length); + } +#endif else if (!strcmp(topic, STATE_REQUEST_TOPIC)) { state_request_callback(); @@ -628,6 +709,10 @@ void thread_initialization() eeprom_pwm_updater.setInterval(1000); user_timer_tick.onRun(timer_tick_callback); user_timer_tick.setInterval(15000); +#ifdef ENABLE_ANALOG_MODULE + analog_handler.onRun(adc_loop); + analog_handler.setInterval(ANALOG_REPORTING_INTERVAL); +#endif } /** @@ -924,6 +1009,12 @@ void state_request_callback() publish_env_state(); #endif user_state_request_callback(); +#ifdef ENABLE_ANALOG_MODULE + publish_adc_states(); + publish_adc_values(); + publish_dac_states(); + publish_dac_values(); +#endif } #ifdef ENABLE_IR_MODULE @@ -1747,4 +1838,275 @@ void check_boot_reset() { factory_reset(); } -} \ No newline at end of file +} + +#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); + publish_adc_state(id); +} +/** + * @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); + publish_adc_state(id); +} + +void publish_adc_state(int id) +{ + ADC_STATE_TOPIC[base_topic_length + 4] = ((id - id % 10) / 10) + '0'; + ADC_STATE_TOPIC[base_topic_length + 5] = (id % 10) + '0'; + mqtt.publish(ADC_STATE_TOPIC, adc_report_enable[id] ? "on" : "off"); +} + +void publish_adc_states() +{ + for (int i = 0; i < ADC_COUNT; i++) + { + publish_adc_state(i); + } +} + +/** + * @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_adc_values(); +} + +/** + * Publishes the ADC value to the MQTT broker. + * + * @param id The ID of the ADC channel. + */ +void publish_adc_value(int id) +{ + ADC_REPORT_TOPIC[base_topic_length + 4] = ((id - id % 10) / 10) + '0'; + ADC_REPORT_TOPIC[base_topic_length + 5] = (id % 10) + '0'; + char temp[8]; + itoa(adc_values[id], temp, DEC); + mqtt.publish(ADC_REPORT_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_adc_values() +{ + for (int i = 0; i < ADC_COUNT; i++) + { + if (adc_report_enable[i]) + publish_adc_value(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[5] - '0'; + int b = topic[6] - '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[5] - '0'; + int b = topic[6] - '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[5] - '0'; + int b = topic[6] - '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 diff --git a/src/espmega_iot_core.hpp b/src/espmega_iot_core.hpp index f542632..531735a 100644 --- a/src/espmega_iot_core.hpp +++ b/src/espmega_iot_core.hpp @@ -131,4 +131,25 @@ void eeprom_mqtt_useauth_retrieve(); void set_mqtt_useauth(bool use_auth); void factory_reset(); -void check_boot_reset(); \ No newline at end of file +void check_boot_reset(); + +void enable_adc(int id); +void disable_adc(int id); +void adc_update(int id); +void adc_update_force(int id); +void adc_update_all(); +void adc_loop(); +void publish_adc_value(int id); +void publish_adc_values(); +uint16_t get_adc_value(int id); +void adc_set_state_callback(char *topic, uint8_t topic_length, char *payload, unsigned int payload_length); +void dac_set_value(int id, int value); +void dac_set_state(int id, bool state); +void dac_set_value_callback(char *topic, uint8_t topic_length, char *payload, unsigned int payload_length); +void dac_set_state_callback(char *topic, uint8_t topic_length, char *payload, unsigned int payload_length); +void publish_dac_value(int id); +void publish_dac_state(int id); +void publish_dac_values(); +void publish_dac_states(); +void publish_adc_state(int id); +void publish_adc_states(); \ No newline at end of file diff --git a/src/user_code.hpp b/src/user_code.hpp index f14d89b..395a6e4 100644 --- a/src/user_code.hpp +++ b/src/user_code.hpp @@ -16,15 +16,17 @@ #define ENABLE_INTERNAL_LCD #define ENABLE_IR_MODULE #define ENABLE_CLIMATE_MODULE // Require IR Module +#define ENABLE_ANALOG_MODULE #define ENABLE_WEBUI -// Infrared Transciever +// IR Kit Configuration #define IR_RECIEVE_PIN 35 #define IR_SEND_PIN 17 #define MARK_EXCESS_MICROS 20 #define RAW_BUFFER_LENGTH 750 #define AC_MAX_TEMPERATURE 30 #define AC_MIN_TEMPERATURE 15 +#define DHT22_PIN 32 // External LCD Configuration #define ENABLE_EXTERNAL_LCD @@ -35,6 +37,9 @@ #define ESPMega_EXTLCD Serial2 #endif +// Analog Module Configuration +#define ANALOG_REPORTING_INTERVAL 500 + // User Defined Functions void user_pre_init(); void user_init();