Mon, 17 Apr 2023 16:53:38 +0200
Removed all BLE code. It is not usefull in this application.
/** * @file iotbalkon.c * @brief iotbalkon project. */ #include "config.h" static const char *TAG = "iotbalkon"; /* * Main State table. */ typedef enum { State_Init = 0, State_Connect_Wifi, State_Connect_MQTT, State_Working, State_WorkDone, State_Disconnect_MQTT, State_Disconnect_Wifi, State_Wait, State_Measure, State_GoSleep } Main_State; static TaskHandle_t xTaskBMP280 = NULL; static TaskHandle_t xTaskINA219 = NULL; static TaskHandle_t xTaskAPDS9930 = NULL; static TaskHandle_t xTaskMQTT = NULL; static TaskHandle_t xTaskWifi = NULL; static TaskHandle_t xTaskOUT = NULL; static TaskHandle_t xTaskTEMP = NULL; #define DS_TIME 60 #define DS_CURRENT 6.6 #define MAX_LOOPS 32 #define SUB_TIME 1000 float temperature; float pressure; float solarVolts, solarCurrent, solarPower; float batteryVolts, batteryCurrent, batteryPower; int batteryState; float s_Volts[MAX_LOOPS + 1]; float s_Current[MAX_LOOPS + 1]; float b_Volts[MAX_LOOPS + 1]; float b_Current[MAX_LOOPS + 1]; bool m_Valid[MAX_LOOPS + 1]; uint64_t m_Time[MAX_LOOPS + 1]; uint64_t gLastTime; uint64_t gTimeNext; uint8_t loopno = 0; uint8_t loops = 0; uint8_t ST_LOOPS = 6; int DisCounter = 0; extern TEMP_State *temp_state; extern SemaphoreHandle_t xSemaphoreTEMP; extern BMP280_State *bmp280_state; ///< I2C state extern SemaphoreHandle_t xSemaphoreBMP280; ///< I2C lock semaphore extern bmp280_params_t bmp280_params; extern bmp280_t bmp280_dev; extern SemaphoreHandle_t xSemaphoreINA219; extern ina219_t ina219_b_dev; extern ina219_t ina219_s_dev; extern INA219_State *ina219_state; extern SemaphoreHandle_t xSemaphoreAPDS9930; extern apds9930_t apds9930_dev; extern APDS9930_State *apds9930_state; extern SemaphoreHandle_t xSemaphoreWiFi; extern WIFI_State *wifi_state; ///< WiFi state uint32_t Alarm = 0; /* * Alarm bits */ #define AL_ACCULOW 0x01 #define AL_NOWIFI 0x02 #define ST_INTERVAL 10000 #define MAX_LOOPS 32 /* * Variables in NVS namespace "balkon" */ uint8_t Relay1; uint8_t Relay2; uint8_t Dimmer3; uint8_t Dimmer4; uint8_t DS_Active; uint32_t DS_Time; // 0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100% float Charge_C_5[11] = { 12.40, 12.60, 12.75, 12.95, 13.20, 13.35, 13.55, 13.67, 14.00, 15.25, 15.94 }; float Charge_C_10[11] = { 12.20, 12.38, 12.60, 12.80, 13.05, 13.20, 13.28, 13.39, 13.60, 14.20, 15.25 }; float Charge_C_20[11] = { 12.00, 12.07, 12.42, 12.70, 12.85, 13.02, 13.11, 13.15, 13.25, 13.60, 14.15 }; float Charge_C_40[11] = { 11.55, 11.80, 12.25, 12.57, 12.70, 12.80, 12.90, 12.95, 13.00, 13.20, 13.50 }; float Rest[11] = { 11.50, 11.70, 11.88, 12.10, 12.22, 12.30, 12.40, 12.50, 12.57, 12.64, 12.72 }; float Load_C_20[11] = { 11.45, 11.70, 11.80, 12.05, 12.20, 12.28, 12.37, 12.48, 12.55, 12.57, 12.60 }; float Load_C_10[11] = { 10.98, 11.27, 11.50, 11.65, 11.85, 12.00, 12.10, 12.20, 12.30, 12.40, 12.50 }; float Load_C_5[11] = { 10.20, 10.65, 10.90, 11.15, 11.35, 11.55, 11.63, 11.75, 11.90, 12.00, 12.08 }; /* * Calculate the load state of the battery. */ void BatteryState(float Voltage, float Current) { int i; batteryState = 0; ESP_LOGI(TAG, "Batt %.4fV %.4fmA", Voltage, Current); if (Current < -750) { // Load current > C/5, 1A for (i = 0; i < 10; i++) { if (Load_C_5[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Load C/5 %d%%", batteryState); } else if (Current < -375) { // Load current > C/10, 500mA for (i = 0; i < 10; i++) { if (Load_C_10[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Load C/10 %d%%", batteryState); } else if (Current < -125) { // Load current > C/20, 250mA for (i = 0; i < 10; i++) { if (Load_C_20[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Load C/20 %d%%", batteryState); } else if (Current > 750) { // Charge > C/5, 1A for (i = 0; i < 10; i++) { if (Charge_C_5[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Charge C/5 %d%%", batteryState); } else if (Current > 375) { // Charge > C/10, 500mA for (i = 0; i < 10; i++) { if (Charge_C_10[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Charge C/10 %d%%", batteryState); } else if (Current > 187.5) { // Charge > C/20, 250 mA for (i = 0; i < 10; i++) { if (Charge_C_20[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Charge C/20 %d%%", batteryState); } else if (Current > 62.5) { // Charge > C/40, 125 mA for (i = 0; i < 10; i++) { if (Charge_C_40[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Charge C/40 %d%%", batteryState); } else { // Rest for (i = 0; i < 10; i++) { if (Rest[i + 1] >= Voltage) { break; } } batteryState = i * 10; ESP_LOGI(TAG, "Rest %d%%", batteryState); } } uint64_t millis(void) { return esp_timer_get_time() / 1000; } /* * Read the temperature and pressure from the BMP280. */ void getTempBaro() { temperature = 20; pressure = 1000; request_bmp280(); while (! ready_bmp280()) { vTaskDelay(10 / portTICK_PERIOD_MS); } if (xSemaphoreTake(xSemaphoreBMP280, 25) == pdTRUE) { temperature = bmp280_state->temperature; pressure = bmp280_state->pressure / 100.0; ESP_LOGI(TAG, "Temperature: %.2f Pressure: %.1f hPa", temperature, pressure); xSemaphoreGive(xSemaphoreBMP280); } else { ESP_LOGE(TAG, "Can't lock xSemaphoreBMP280"); } } /* * Read voltage and current from both INA219 boards. The results * are stored in an array so that we later can average the results. */ void getVoltsCurrent() { request_ina219(); while (! ready_ina219()) { vTaskDelay(10 / portTICK_PERIOD_MS); } if (xSemaphoreTake(xSemaphoreINA219, 25) == pdTRUE) { s_Volts[loopno] = ina219_state->Solar.volts + ina219_state->Solar.shunt; s_Current[loopno] = ina219_state->Solar.current; b_Volts[loopno] = ina219_state->Battery.volts + ina219_state->Battery.shunt; b_Current[loopno] = ina219_state->Battery.current; m_Valid[loopno] = ina219_state->valid; xSemaphoreGive(xSemaphoreINA219); /* * Check for crazy values */ if ((b_Volts[loopno] < 10) || (b_Volts[loopno] > 15.0)) { m_Valid[loopno] = false; } if ((b_Current[loopno] < 0) || (b_Current[loopno] > 2000)) { m_Valid[loopno] = false; } if (s_Volts[loopno] < 0) { s_Volts[loopno] = 0.0; } if (s_Volts[loopno] > 24) { m_Valid[loopno] = false; } if (s_Current[loopno] < 0) { s_Current[loopno] = 0.0; } if (s_Current[loopno] > 2000) { m_Valid[loopno] = false; } } uint64_t ms = millis(); m_Time[loopno] = ms - gLastTime; gLastTime = ms; ESP_LOGI(TAG, "Measure: %d Valid: %s Battery: %.3fV %.1fmA Solar: %.3fV %.1fmA time %llu", loopno, (m_Valid[loopno]) ? "true":"false", b_Volts[loopno], b_Current[loopno], s_Volts[loopno], s_Current[loopno], m_Time[loopno]); if (loopno < (MAX_LOOPS - 1)) loopno++; } void getLightValues() { request_apds9930(); while (! ready_apds9930()) { vTaskDelay(10 / portTICK_PERIOD_MS); } } void app_main(void) { uint64_t totalTime, gTimeInMillis; #ifdef CONFIG_CODE_PRODUCTION ESP_LOGI(TAG, "Starting production"); #endif #ifdef CONFIG_CODE_TESTING ESP_LOGI(TAG, "Starting testing"); #endif /* * Initialize NVS */ esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_ERROR_CHECK(err); nvsio_init(); Relay1 = nvsio_read_u8((char *)"out1"); Relay2 = nvsio_read_u8((char *)"out2"); Dimmer3 = nvsio_read_u8((char *)"out3"); Dimmer4 = nvsio_read_u8((char *)"out4"); DS_Active = nvsio_read_u8((char *)"ds_active"); DS_Time = nvsio_read_u32((char *)"ds_time"); if (DS_Time == 0) { DS_Time = DS_TIME; nvsio_write_u32((char *)"ds_time", DS_Time); } gLastTime = millis(); ESP_ERROR_CHECK(i2cdev_init()); bmp280_init_default_params(&bmp280_params); /* * Override some defaults to make the BMP280 use less power * and still provide enough resolution. */ bmp280_params.oversampling_pressure = BMP280_LOW_POWER; bmp280_params.oversampling_temperature = BMP280_LOW_POWER; bmp280_params.standby = BMP280_STANDBY_1000; memset(&bmp280_dev, 0, sizeof(bmp280_t)); memset(&ina219_b_dev, 0, sizeof(ina219_t)); memset(&ina219_s_dev, 0, sizeof(ina219_t)); memset(&apds9930_dev, 0, sizeof(apds9930_t)); i2c_dev_t dev = { 0 }; dev.cfg.sda_io_num = CONFIG_I2C_MASTER_SDA; dev.cfg.scl_io_num = CONFIG_I2C_MASTER_SCL; dev.cfg.master.clk_speed = 100000; dev.addr = 0x39; if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { ESP_ERROR_CHECK(apds9930_init_desc(&apds9930_dev, 0x39, 0, CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL)); ESP_ERROR_CHECK(apds9930_init(&apds9930_dev)); ESP_LOGI(TAG, "Found APDS-9930 id: 0x%02x", apds9930_dev.id); } else { ESP_LOGW(TAG, "No APDS-9930 found"); } dev.addr = 0x40; if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { ESP_ERROR_CHECK(ina219_init_desc(&ina219_b_dev, 0x40, 0, CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL)); ESP_ERROR_CHECK(ina219_init(&ina219_b_dev)); ESP_LOGI(TAG, "Found INA219 @0x40 Battery"); } else { ESP_LOGW(TAG, "No INA219 @0x40 found"); } dev.addr = 0x41; if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { ESP_ERROR_CHECK(ina219_init_desc(&ina219_s_dev, 0x41, 0, CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL)); ESP_ERROR_CHECK(ina219_init(&ina219_s_dev)); ESP_LOGI(TAG, "Found INA219 @0x41 Solar"); } else { ESP_LOGW(TAG, "No INA219 @0x41 found"); } dev.addr = 0x76; if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { ESP_ERROR_CHECK(bmp280_init_desc(&bmp280_dev, BMP280_I2C_ADDRESS_0, 0, CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL)); ESP_ERROR_CHECK(bmp280_init(&bmp280_dev, &bmp280_params)); ESP_LOGI(TAG, "Found BMP280 @ 0x76 id: 0x%02x", bmp280_dev.id); } else { dev.addr = 0x77; if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { ESP_ERROR_CHECK(bmp280_init_desc(&bmp280_dev, BMP280_I2C_ADDRESS_1, 0, CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL)); ESP_ERROR_CHECK(bmp280_init(&bmp280_dev, &bmp280_params)); ESP_LOGI(TAG, "Found BMP280 @ 0x77 id: 0x%02x", bmp280_dev.id); } else { ESP_LOGW(TAG, "No BMP280 found"); } } /* * Create FreeRTOS tasks */ xSemaphoreBMP280 = xSemaphoreCreateMutex(); xSemaphoreINA219 = xSemaphoreCreateMutex(); xSemaphoreAPDS9930 = xSemaphoreCreateMutex(); xSemaphoreTEMP = xSemaphoreCreateMutex(); xTaskCreate(&task_bmp280, "task_bmp280", 2560, NULL, 8, &xTaskBMP280); xTaskCreate(&task_ina219, "task_ina219", 2560, NULL, 8, &xTaskINA219); xTaskCreate(&task_apds9930, "task_apds9930", 2560, NULL, 8, &xTaskAPDS9930); xTaskCreate(&task_out, "task_out", 2560, NULL, 9, &xTaskOUT); xTaskCreate(&task_mqtt, "task_mqtt", 4096, NULL, 5, &xTaskMQTT); xTaskCreate(&task_wifi, "task_wifi", 4096, NULL, 3, &xTaskWifi); xTaskCreate(&task_temp, "task_temp", 2560, NULL, 9, &xTaskTEMP); vTaskDelay(10 / portTICK_PERIOD_MS); /* * Main application loop. */ int State = State_Init; int OldState = State_Init + 1; uint8_t ds_time = DS_Time; while (0) { request_temp(); // request_bmp280(); // request_ina219(); // request_apds9930(); vTaskDelay(5000 / portTICK_PERIOD_MS); } while (1) { if (OldState != State) { ESP_LOGD(TAG, "Switch to state %d", State); OldState = State; } gTimeInMillis = millis(); switch (State) { case State_Init: getTempBaro(); getLightValues(); getVoltsCurrent(); State = State_Connect_Wifi; DisCounter = 0; request_WiFi(true); break; case State_Connect_Wifi: if (ready_WiFi()) { State = State_Connect_MQTT; /* * Give the network stack a bit more time to setup */ vTaskDelay(250 / portTICK_PERIOD_MS); request_mqtt(true); Alarm &= ~AL_NOWIFI; ESP_LOGD(TAG, "Connected counter WiFi %d", DisCounter); DisCounter = 0; } else { /* * 15 seconds connection try. */ DisCounter++; vTaskDelay(500 / portTICK_PERIOD_MS); ESP_LOGI(TAG, "* Counter WiFi %d", DisCounter); if (DisCounter > 30) { Alarm |= AL_NOWIFI; request_WiFi(false); State = State_Init; vTaskDelay(4000 / portTICK_PERIOD_MS); } } break; case State_Connect_MQTT: if (ready_mqtt()) { State = State_Working; ESP_LOGI(TAG, "Connected counter MQTT %d", DisCounter); DisCounter = 0; } else { DisCounter++; if (DisCounter > 60) { request_mqtt(false); request_WiFi(false); State = State_Init; vTaskDelay(2000 / portTICK_PERIOD_MS); } vTaskDelay(1000 / portTICK_PERIOD_MS); } break; case State_Working: /* * The final measure power usage. * This one should include the WiFi usage current. */ getVoltsCurrent(); solarVolts = solarCurrent = solarPower = batteryVolts = batteryCurrent = batteryPower = 0; loops = 0; totalTime = 0; for (int i = 0; i < loopno; i++) { /* * If there are only 2 loops, and the flag that we came from deep-sleep is set * then assume the following: * 1. No current (or very low for the dc converter) during DS_TIME seconds. * 2. Low power during 0.5 second (no WiFi yet). * 3. 5 seconds power usage for the last measurement. * Calculate the average battery current from this. * * If there are more loops and we came from continues running do the following: * 1. Take the current use from all exept the last one, weight loops * 10 seconds. * 2. Take the last one, and weight for 5 seconds. * Calculate the average battery current from this. */ if (m_Valid[i]) { solarVolts += s_Volts[i]; solarCurrent += s_Current[i]; batteryVolts += b_Volts[i]; if (i == (loopno - 1)) { // Add the extra time m_Time[i] += SUB_TIME; } batteryCurrent += b_Current[i] * m_Time[i]; totalTime += m_Time[i]; loops++; } } if (DS_Active) { totalTime += DS_Time * 1000; batteryCurrent += DS_CURRENT * DS_Time * 1000; ESP_LOGI(TAG, "Added %ld totalTime %lld", DS_Time * 1000, totalTime); } // If valid measurements if (loops) { solarVolts = solarVolts / loops; solarCurrent = solarCurrent / loops; solarPower = solarVolts * solarCurrent; batteryVolts = batteryVolts / loops; batteryCurrent = batteryCurrent / totalTime; batteryPower = batteryVolts * batteryCurrent; BatteryState(batteryVolts, (0 - batteryCurrent) + solarCurrent); ESP_LOGI(TAG, " Solar Volts: %.4fV Current %.4fmA Power %.4fmW", solarVolts, solarCurrent, solarPower); ESP_LOGI(TAG, "Battery Volts: %.4fV Current %.4fmA Power %.4fmW", batteryVolts, batteryCurrent, batteryPower); #ifdef CONFIG_CODE_PRODUCTION /* Check alarm conditions */ if (batteryState <= 10) { Alarm |= AL_ACCULOW; } else { Alarm &= ~AL_ACCULOW; } #endif #ifdef CONFIG_CODE_TESTING Alarm &= ~AL_ACCULOW; #endif } getTempBaro(); request_temp(); vTaskDelay(2000 / portTICK_PERIOD_MS); publish(); State = State_WorkDone; /* * Wait an extra second so the publish message will reach * the broker and the subscriptions are really here. */ gTimeNext = millis() + 1000; break; case State_WorkDone: if (gTimeInMillis >= gTimeNext) { State = State_Disconnect_MQTT; request_mqtt(false); } vTaskDelay(50 / portTICK_PERIOD_MS); break; case State_Disconnect_MQTT: wait_mqtt(10000); State = State_Disconnect_Wifi; break; case State_Disconnect_Wifi: request_WiFi(false); // // Reset values for average current measurement. loopno = 0; gLastTime = millis(); /* * If any output is on, use 6 10 seconds loops. * If nothing on, do a deep sleep. */ if (Relay1 || Relay2 || Dimmer3 || Dimmer4) { DS_Active = 0; nvsio_write_u8((char *)"ds_active", 0); // Active mode, 60 seconds loop ST_LOOPS = 6; gTimeNext = millis() + ST_INTERVAL; ESP_LOGD(TAG, "Start sleeploops"); State = State_Wait; } else { ds_time = DS_TIME; if (solarVolts < 6) { // At night, increase the deep-sleep time. ds_time *= 4; } nvsio_write_u8((char *)"ds_active", 1); DS_Active = 1; nvsio_write_u32((char *)"ds_time", ds_time); DS_Time = ds_time; State = State_GoSleep; } break; case State_Wait: if (gTimeInMillis >= gTimeNext) { State = State_Measure; } vTaskDelay(50 / portTICK_PERIOD_MS); break; case State_Measure: gTimeNext = millis() + ST_INTERVAL; getVoltsCurrent(); if (loopno >= ST_LOOPS) { ESP_LOGD(TAG, "Enough loops, do connect"); getLightValues(); State = State_Connect_Wifi; DisCounter = 0; request_WiFi(true); } else { State = State_Wait; } break; case State_GoSleep: ESP_LOGI(TAG, "Going to deep-sleep for %ld seconds", DS_Time); ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(DS_Time * 1e6)); esp_deep_sleep_start(); break; } } // Not reached. }