diff --git a/software/CMakeLists.txt b/software/CMakeLists.txt new file mode 100644 index 0000000..561307f --- /dev/null +++ b/software/CMakeLists.txt @@ -0,0 +1,9 @@ + +set(EXTRA_COMPONENT_DIRS $ENV{ESP_IDF_LIB_PATH}/components) + +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(smart-oil-heating-control-system) diff --git a/software/main/CMakeLists.txt b/software/main/CMakeLists.txt new file mode 100644 index 0000000..4991960 --- /dev/null +++ b/software/main/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register(SRCS "main.c" + "http_metrics.c" + "inputs.c" + "outputs.c" + "control.c" + "safety.c" + INCLUDE_DIRS ".") diff --git a/software/main/Kconfig.projbuild b/software/main/Kconfig.projbuild new file mode 100644 index 0000000..990fa8e --- /dev/null +++ b/software/main/Kconfig.projbuild @@ -0,0 +1,19 @@ +menu "Smart Oil Heating Control System" + + config SSID + string "SSID" + default "my WiFi SSID" + config WIFI_PASSWORD + string "WIFI_PASSWORD" + default "my WIFI Password" + config STATIC_IP_ADDR + string "Static IPv4 address" + default "192.168.0.42" + config STATIC_IP_NETMASK + string "Static IPv4 netmask" + default "255.255.0.0" + config STATIC_GATEWAY_IP_ADDR + string "Static IPv4 gateway address" + default "192.168.0.1" + +endmenu diff --git a/software/main/control.c b/software/main/control.c new file mode 100644 index 0000000..473a0f4 diff --git a/software/main/control.h b/software/main/control.h new file mode 100644 index 0000000..473a0f4 diff --git a/software/main/http_metrics.c b/software/main/http_metrics.c new file mode 100644 index 0000000..b3aa75a --- /dev/null +++ b/software/main/http_metrics.c @@ -0,0 +1,154 @@ +#include "http_metrics.h" + +static EventGroupHandle_t s_wifi_event_group; + +static const char *TAG = "esp32-env-exp-http_metrics"; + +char caHtmlResponse[HTML_RESPONSE_SIZE]; +SemaphoreHandle_t xMutexAccessMetricResponse = NULL; + +void vSetMetrics(sMetric* paMetrics, uint16_t u16Size) { + + if( xSemaphoreTake( xMutexAccessMetricResponse, ( TickType_t ) 100 ) == pdTRUE ) + { + memset(caHtmlResponse,0,strlen(caHtmlResponse)); + for(uint16_t u16Index = 0U; u16Index < u16Size; u16Index++) { + char caValueBuffer[64]; + sprintf(caValueBuffer, " %f", paMetrics[u16Index].fMetricValue); + //printf("%s\n", caValueBuffer); + strcat(caHtmlResponse, paMetrics[u16Index].caMetricName); + strcat(caHtmlResponse, caValueBuffer); + strcat(caHtmlResponse, "\n"); + } + xSemaphoreGive( xMutexAccessMetricResponse ); + } else { + ESP_LOGI(TAG, "[SET] Unable to obtain mutex for metric response"); + } +} + +static void event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) + { + esp_wifi_connect(); + } + else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) + { + esp_wifi_connect(); + ESP_LOGI(TAG, "Retry to connect to the AP"); + } + else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) + { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + ESP_LOGI(TAG, "Got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } +} + +void connect_wifi(void) +{ + s_wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + esp_netif_t *my_sta = esp_netif_create_default_wifi_sta(); + esp_netif_dhcpc_stop(my_sta); + esp_netif_ip_info_t ip_info; + ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR); + ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_IP_ADDR); + ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_IP_NETMASK); + esp_netif_set_ip_info(my_sta, &ip_info); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + esp_event_handler_instance_t instance_any_id; + esp_event_handler_instance_t instance_got_ip; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &event_handler, + NULL, + &instance_any_id)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &event_handler, + NULL, + &instance_got_ip)); + + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_SSID, + .password = CONFIG_WIFI_PASSWORD, + .threshold.authmode = WIFI_AUTH_WPA2_PSK, + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY); + + if (bits & WIFI_CONNECTED_BIT) + { + ESP_LOGI(TAG, "Connected to ap SSID:%s", CONFIG_SSID); + } + else if (bits & WIFI_FAIL_BIT) + { + ESP_LOGI(TAG, "Failed to connect to SSID:%s", CONFIG_SSID); + } + else + { + ESP_LOGE(TAG, "Unexpected event"); + } + vEventGroupDelete(s_wifi_event_group); +} + +esp_err_t get_metrics_handler(httpd_req_t *req) +{ + if( xSemaphoreTake( xMutexAccessMetricResponse, ( TickType_t ) 100 ) == pdTRUE ) + { + esp_err_t err = httpd_resp_send(req, caHtmlResponse, HTTPD_RESP_USE_STRLEN); + xSemaphoreGive( xMutexAccessMetricResponse ); + return err; + } else + { + ESP_LOGI(TAG, "[GET] Unable to obtain mutex for metric response"); + return httpd_resp_send(req, 0, 0); + } +} + +httpd_uri_t uri_get = { + .uri = "/metrics", + .method = HTTP_GET, + .handler = get_metrics_handler, + .user_ctx = NULL +}; + +httpd_handle_t setup_server(void) +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = 9100; + httpd_handle_t server = NULL; + + + xMutexAccessMetricResponse = xSemaphoreCreateBinary(); + if(xMutexAccessMetricResponse == NULL) { + ESP_LOGE(TAG, "Unable to create mutex for metric response"); + vTaskDelay(pdMS_TO_TICKS(300*60)); //wait 5min before restart + esp_restart(); + } + xSemaphoreGive( xMutexAccessMetricResponse ); + + if (httpd_start(&server, &config) == ESP_OK) + { + httpd_register_uri_handler(server, &uri_get); + } + + return server; +} diff --git a/software/main/http_metrics.h b/software/main/http_metrics.h new file mode 100644 index 0000000..779780d --- /dev/null +++ b/software/main/http_metrics.h @@ -0,0 +1,40 @@ +#ifndef H_HTTPS_METRICS +#define H_HTTPS_METRICS + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "spi_flash_mmap.h" +#include + +#include "esp_wifi.h" +#include "esp_event.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include +#include +#include +#include + +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 +#define HTML_RESPONSE_SIZE 256U +#define METRIC_NAME_MAX_SIZE 64U +#define METRIC_MAX_COUNT 32U + +typedef struct _metric { + char caMetricName[METRIC_NAME_MAX_SIZE]; + float fMetricValue; +} sMetric; + + +void connect_wifi(void); +httpd_handle_t setup_server(void); +void vSetMetrics(sMetric* paMetrics, uint16_t u16Size); + +#endif /* H_HTTPS_METRICS */ \ No newline at end of file diff --git a/software/main/inputs.c b/software/main/inputs.c new file mode 100644 index 0000000..473a0f4 diff --git a/software/main/inputs.h b/software/main/inputs.h new file mode 100644 index 0000000..473a0f4 diff --git a/software/main/main.c b/software/main/main.c new file mode 100644 index 0000000..1607459 --- /dev/null +++ b/software/main/main.c @@ -0,0 +1,181 @@ +#include +#include "esp_log.h" +#include "driver/i2c.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "freertos/timers.h" + +#include "http_metrics.h" + +#define I2C_MASTER_SCL 19 +#define I2C_MASTER_SDA 18 + +#define BMP280 +#define DHT11 +//#define AHT10 +//#define VEML7700 + +#ifdef DHT11 +#define CONFIG_EXAMPLE_DATA_GPIO 17 +#define SENSOR_TYPE DHT_TYPE_DHT11 +#endif + +#ifdef AHT10 +#define AHT_TYPE AHT_TYPE_AHT1x +#endif + +static const char *TAG = "smart-oil-heater-control-system"; + +void app_main(void) +{ + ESP_LOGI(TAG, "starting ..."); + + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + connect_wifi(); //will return if successful + setup_server(); + + sMetric aMetrics[METRIC_MAX_COUNT]; + + /*Sensor Init*/ + ESP_ERROR_CHECK(i2cdev_init()); + +#ifdef BMP280 + bmp280_params_t params; + bmp280_init_default_params(¶ms); + bmp280_t devBMP280; + memset(&devBMP280, 0, sizeof(bmp280_t)); + ESP_ERROR_CHECK(bmp280_init_desc(&devBMP280, BMP280_I2C_ADDRESS_0, 0, I2C_MASTER_SDA, I2C_MASTER_SCL)); + ESP_ERROR_CHECK(bmp280_init(&devBMP280, ¶ms)); + bool bme280p = devBMP280.id == BME280_CHIP_ID; + ESP_LOGI(TAG, "BMP280: found %s\n", bme280p ? "BME280" : "BMP280"); +#endif + +#ifdef AHT10 + aht_t devAHT10 = {0}; + devAHT10.mode = AHT_MODE_NORMAL; + devAHT10.type = AHT_TYPE; + ESP_ERROR_CHECK(aht_init_desc(&devAHT10, AHT_I2C_ADDRESS_GND, 0, I2C_MASTER_SDA, I2C_MASTER_SCL)); + ESP_ERROR_CHECK(aht_init(&devAHT10)); +#endif + +#ifdef VEML7700 + i2c_dev_t veml7700_device; + veml7700_config_t veml7700_configuration; + + memset(&veml7700_device, 0, sizeof(i2c_dev_t)); + memset(&veml7700_configuration, 0, sizeof(veml7700_config_t)); + + // initialize the device struct + ESP_ERROR_CHECK(veml7700_init_desc(&veml7700_device, I2C_NUM_0, I2C_MASTER_SDA, I2C_MASTER_SCL)); + + // check if the device is available + ESP_ERROR_CHECK(veml7700_probe(&veml7700_device)); + + /* set configuration parameters + * select gain 1/8 coastest resolution but the sensor will most likely not + * over-saturated + */ + veml7700_configuration.gain = VEML7700_GAIN_DIV_8; + + /* set the time to integrate the light values. more time will lead to a finer + * resolution but will be over-saturated earlier + */ + veml7700_configuration.integration_time = VEML7700_INTEGRATION_TIME_100MS; + + // interrupt is not used + veml7700_configuration.persistence_protect = VEML7700_PERSISTENCE_PROTECTION_4; + veml7700_configuration.interrupt_enable = 1; + veml7700_configuration.shutdown = 0; + + // write the configuration to the device + ESP_ERROR_CHECK(veml7700_set_config(&veml7700_device, &veml7700_configuration)); + +#endif + + ESP_LOGI(TAG, "running"); + + while (1) + { + vTaskDelay(pdMS_TO_TICKS(5000)); + uint16_t u16MetricCounter = 0U; + + /*Wifi RSSI*/ + wifi_ap_record_t ap; + esp_wifi_sta_get_ap_info(&ap); + printf("WiFi RSSI: %d\n", ap.rssi); + strcpy(aMetrics[u16MetricCounter].caMetricName, "wifi_rssi"); + aMetrics[0].fMetricValue = ap.rssi; + u16MetricCounter++; + +#ifdef BMP280 + if ((bmp280_read_float(&devBMP280, &aMetrics[u16MetricCounter].fMetricValue, &aMetrics[u16MetricCounter+1].fMetricValue, 0) == ESP_OK)) + { + strcpy(aMetrics[u16MetricCounter].caMetricName, "bmp280_temperature"); + strcpy(aMetrics[u16MetricCounter+1].caMetricName, "bmp280_pressure"); + printf("(BMP280) Temperature: %.2f C, Pressure: %.2f Pa\n", aMetrics[u16MetricCounter].fMetricValue, aMetrics[u16MetricCounter+1].fMetricValue); + u16MetricCounter++; + u16MetricCounter++; + } else { + printf("(BMP280) Temperature/pressure reading failed\n"); + } +#endif + +#ifdef AHT10 + if ((aht_get_data(&devAHT10, &aMetrics[u16MetricCounter].fMetricValue, &aMetrics[u16MetricCounter+1].fMetricValue) == ESP_OK)) + { + strcpy(aMetrics[u16MetricCounter].caMetricName, "aht10_temperature"); + strcpy(aMetrics[u16MetricCounter+1].caMetricName, "aht10_humidity"); + printf("(AHT10) Temperature: %.2f C, Humidity: %.2f %% \n", aMetrics[u16MetricCounter].fMetricValue, aMetrics[u16MetricCounter+1].fMetricValue); + u16MetricCounter++; + u16MetricCounter++; + } else { + printf("(AHT10) Temperature/Humidity reading failed\n"); + } +#endif + +#ifdef DHT11 + if ((dht_read_float_data(SENSOR_TYPE, CONFIG_EXAMPLE_DATA_GPIO, &aMetrics[u16MetricCounter].fMetricValue, &aMetrics[u16MetricCounter+1].fMetricValue) == ESP_OK)) + { + strcpy(aMetrics[u16MetricCounter].caMetricName, "dht11_humidity"); + strcpy(aMetrics[u16MetricCounter+1].caMetricName, "dht11_temperature"); + printf("(DHT11) Humidity: %.2f %%, Temperature: %.2f C\n", aMetrics[u16MetricCounter].fMetricValue, aMetrics[u16MetricCounter+1].fMetricValue); + u16MetricCounter++; + u16MetricCounter++; + } else { + printf("(DHT11) Temperature/pressure reading failed\n"); + } +#endif + +#ifdef VEML7700 + uint32_t u32AmbientLight = 0U; + uint32_t u32WhiteChannel = 0U; + if ((veml7700_get_ambient_light(&veml7700_device, &veml7700_configuration, &u32AmbientLight) == ESP_OK) && (veml7700_get_white_channel(&veml7700_device, &veml7700_configuration, &u32WhiteChannel) == ESP_OK)) + { + aMetrics[u16MetricCounter].fMetricValue = u32AmbientLight; + aMetrics[u16MetricCounter+1].fMetricValue = u32WhiteChannel; + strcpy(aMetrics[u16MetricCounter].caMetricName, "veml7700_ambient_light"); + strcpy(aMetrics[u16MetricCounter+1].caMetricName, "veml7700_white_channel"); + printf("(VEML7700) Ambient light: %.2f lux, White channel: %.2f lux\n", aMetrics[u16MetricCounter].fMetricValue, aMetrics[u16MetricCounter+1].fMetricValue); + u16MetricCounter++; + u16MetricCounter++; + } else { + printf("(VEML7700) light reading failed\n"); + } +#endif + vSetMetrics(aMetrics, u16MetricCounter); + } +} diff --git a/software/main/output.h b/software/main/output.h new file mode 100644 index 0000000..473a0f4 diff --git a/software/main/outputs.c b/software/main/outputs.c new file mode 100644 index 0000000..473a0f4 diff --git a/software/main/safety.c b/software/main/safety.c new file mode 100644 index 0000000..473a0f4 diff --git a/software/main/safety.h b/software/main/safety.h new file mode 100644 index 0000000..473a0f4