#include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "esp_log.h" #include #include "inputs.h" #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MAX_DN18B20_SENSORS 4U #define PERIODIC_INTERVAL 1U // read and compute the inputs every 1sec #define AVG10_SAMPLE_SIZE 10U #define AVG60_SAMPLE_SIZE 60U typedef struct _Average { float value; float samples[MAX(AVG10_SAMPLE_SIZE, AVG60_SAMPLE_SIZE)]; size_t bufferIndex; size_t bufferCount; } sAverage; typedef struct _Measurement { float value; sAverage average10s; sAverage average60s; } sMeasurement; static const char *TAG = "smart-oil-heater-control-system-inputs"; const uint8_t uBurnerFaultPin = 19U; const uint8_t uDS18B20Pin = 4U; const onewire_addr_t uChamperTempSensorAddr = 0x3e0000001754be28; const onewire_addr_t uOutdoorTempSensorAddr = 0x880000001648e328; const onewire_addr_t uInletFlowTempSensorAddr = 0xe59cdef51e64ff28; const onewire_addr_t uReturnFlowTempSensorAddr = 0xa7a8e1531f64ff28; onewire_addr_t uOneWireAddresses[MAX_DN18B20_SENSORS]; float fDS18B20Temps[MAX_DN18B20_SENSORS]; size_t sSensorCount = 0U; static SemaphoreHandle_t xMutexAccessInputs = NULL; static eBurnerErrorState sBurnerErrorState; static sMeasurement fChamperTemperature; static sMeasurement fOutdoorTemperature; static sMeasurement fInletFlowTemperature; static sMeasurement fReturnFlowTemperature; void taskInput(void *pvParameters); void updateAverage(sMeasurement *pMeasurement); void initInputs(void) { gpio_config_t ioConfBurnerFault = { .pin_bit_mask = (1ULL << uBurnerFaultPin), // Pin mask .mode = GPIO_MODE_INPUT, // Set as inout .pull_up_en = GPIO_PULLUP_ENABLE, // Enable pull-up .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down .intr_type = GPIO_INTR_DISABLE // Disable interrupts }; gpio_config(&ioConfBurnerFault); xMutexAccessInputs = xSemaphoreCreateBinary(); if (xMutexAccessInputs == NULL) { ESP_LOGE(TAG, "Unable to create mutex"); } xSemaphoreGive(xMutexAccessInputs); BaseType_t taskCreated = xTaskCreate( taskInput, // Function to implement the task "taskInput", // Task name 2048, // Stack size (in words, not bytes) NULL, // Parameters to the task function (none in this case) 5, // Task priority (higher number = higher priority) NULL // Task handle (optional) ); if (taskCreated == pdPASS) { ESP_LOGI(TAG, "Task created successfully!"); } else { ESP_LOGE(TAG, "Failed to create task"); } } void updateAverage(sMeasurement *pMeasurement) { /* Average form the last 10sec */ pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->value; pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10_SAMPLE_SIZE; if (pMeasurement->average10s.bufferCount < AVG10_SAMPLE_SIZE) { pMeasurement->average10s.bufferCount++; } if (pMeasurement->average10s.bufferCount == 0U) { pMeasurement->average10s.value = pMeasurement->value; } float sum = 0.0; for (int i = 0; i < pMeasurement->average10s.bufferCount; i++) { sum += pMeasurement->average10s.samples[i]; } pMeasurement->average10s.value = sum / pMeasurement->average10s.bufferCount; /* Average form the last 60sec */ pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->value; pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60_SAMPLE_SIZE; if (pMeasurement->average60s.bufferCount < AVG60_SAMPLE_SIZE) { pMeasurement->average60s.bufferCount++; } if (pMeasurement->average60s.bufferCount == 0U) { pMeasurement->average60s.value = pMeasurement->value; } sum = 0.0; for (int i = 0; i < pMeasurement->average60s.bufferCount; i++) { sum += pMeasurement->average60s.samples[i]; } pMeasurement->average60s.value = sum / pMeasurement->average60s.bufferCount; } void taskInput(void *pvParameters) { while (1) { vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); if (gpio_get_level(uBurnerFaultPin) == 1) { sBurnerErrorState = FAULT; } else { sBurnerErrorState = NO_ERROR; } if (ds18x20_scan_devices(uDS18B20Pin, uOneWireAddresses, MAX_DN18B20_SENSORS, &sSensorCount) != ESP_OK) { ESP_LOGE(TAG, "1-Wire device scan error!"); } if (!sSensorCount) { ESP_LOGW(TAG, "No 1-Wire devices detected!"); } else { ESP_LOGI(TAG, "%d 1-Wire devices detected", sSensorCount); if (sSensorCount > MAX_DN18B20_SENSORS) { sSensorCount = MAX_DN18B20_SENSORS; ESP_LOGW(TAG, "More 1-Wire devices found than expected!"); } if (ds18x20_measure_and_read_multi(uDS18B20Pin, uOneWireAddresses, sSensorCount, fDS18B20Temps) != ESP_OK) { ESP_LOGE(TAG, "1-Wire devices read error"); } else { for (int j = 0; j < sSensorCount; j++) { float temp_c = fDS18B20Temps[j]; ESP_LOGI(TAG, "Sensor: %08" PRIx64 " reports %.3f°C", (uint64_t)uOneWireAddresses[j], temp_c); if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) { switch ((uint64_t)uOneWireAddresses[j]) { case ((uint64_t)uChamperTempSensorAddr): fChamperTemperature.value = temp_c; updateAverage(&fChamperTemperature); break; case ((uint64_t)uOutdoorTempSensorAddr): fOutdoorTemperature.value = temp_c; updateAverage(&fOutdoorTemperature); break; case ((uint64_t)uInletFlowTempSensorAddr): fInletFlowTemperature.value = temp_c; updateAverage(&fInletFlowTemperature); break; case ((uint64_t)uReturnFlowTempSensorAddr): fReturnFlowTemperature.value = temp_c; updateAverage(&fReturnFlowTemperature); break; default: break; } xSemaphoreGive(xMutexAccessInputs); } } } } } } float getChamberTemperature(eMeasurementMode mode) { float ret = 0.0f; if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) { switch (mode) { case CURRENT: ret = fChamperTemperature.value; break; case AVERAGE_10S: ret = fChamperTemperature.average10s.value; break; case AVERAGE_60S: ret = fChamperTemperature.average60s.value; break; default: break; } xSemaphoreGive(xMutexAccessInputs); } return ret; } float getOutdoorTemperature(eMeasurementMode mode) { float ret = 0.0f; if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) { switch (mode) { case CURRENT: ret = fOutdoorTemperature.value; break; case AVERAGE_10S: ret = fOutdoorTemperature.average10s.value; break; case AVERAGE_60S: ret = fOutdoorTemperature.average60s.value; break; default: break; } xSemaphoreGive(xMutexAccessInputs); } return ret; } float getInletFlowTemperature(eMeasurementMode mode) { float ret = 0.0f; if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) { switch (mode) { case CURRENT: ret = fInletFlowTemperature.value; break; case AVERAGE_10S: ret = fInletFlowTemperature.average10s.value; break; case AVERAGE_60S: ret = fInletFlowTemperature.average60s.value; break; default: break; } xSemaphoreGive(xMutexAccessInputs); } return ret; } float getReturnFlowTemperature(eMeasurementMode mode) { float ret = 0.0f; if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) { switch (mode) { case CURRENT: ret = fReturnFlowTemperature.value; break; case AVERAGE_10S: ret = fReturnFlowTemperature.average10s.value; break; case AVERAGE_60S: ret = fReturnFlowTemperature.average60s.value; break; default: break; } xSemaphoreGive(xMutexAccessInputs); } return ret; } eBurnerErrorState getBurnerError(void) { eBurnerErrorState ret = FAULT; if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) { ret = sBurnerErrorState; xSemaphoreGive(xMutexAccessInputs); } return ret; }