Fri, 31 Mar 2023 20:31:12 +0200
Added volts/current loop calculations. Added millis() timer running on the hardware clock. Completed most of the main state loop. Added MQTT wait for disconnect. MQTT disconnect in two passes, disconnect and stop.
/** * @file iotbalkon.c * @brief iotbalkon project. */ #include "config.h" static const char *TAG = "iotbalkon"; #define State_Init 0 #define State_Connect 1 #define State_Working 2 #define State_WorkDone 3 #define State_Stop 4 #define State_Wait 5 #define State_Measure 6 #define State_GoSleep 7 static TaskHandle_t xTaskBMP280 = NULL; static TaskHandle_t xTaskINA219 = NULL; static TaskHandle_t xTaskMQTT = NULL; static TaskHandle_t xTaskWifi = NULL; #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; bool WorkAgain = false; int DisCounter = 0; 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 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 // 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: %.3f Pressure: %.1f hPa\n", 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 / 1000); s_Current[loopno] = ina219_state->Solar.current; b_Volts[loopno] = ina219_state->Battery.volts + (ina219_state->Battery.shunt / 1000); 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 Solar: %.4fV %.4fmA Battery: %.4fV %.4fmA time %llu", loopno, (m_Valid[loopno]) ? "true":"false", s_Volts[loopno], s_Current[loopno], b_Volts[loopno], b_Current[loopno], m_Time[loopno]); if (loopno < (MAX_LOOPS - 1)) loopno++; } 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 gLastTime = millis(); ESP_ERROR_CHECK(i2cdev_init()); bmp280_init_default_params(&bmp280_params); memset(&bmp280_dev, 0, sizeof(bmp280_t)); memset(&ina219_b_dev, 0, sizeof(ina219_t)); memset(&ina219_s_dev, 0, sizeof(ina219_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 = 1000000; dev.addr = 0x39; if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { ESP_LOGI(TAG, "Found ADPS-9930"); } 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 Battery"); } 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 Solar"); } 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); } } /* * Create FreeRTOS tasks */ xSemaphoreBMP280 = xSemaphoreCreateMutex(); xSemaphoreINA219 = xSemaphoreCreateMutex(); xTaskCreate(&task_bmp280, "task_bmp280", 2560, NULL, 8, &xTaskBMP280); xTaskCreate(&task_ina219, "task_ina219", 2560, NULL, 8, &xTaskINA219); // esp_log_level_set("MQTT_CLIENT", ESP_LOG_ERROR); xTaskCreate(&task_mqtt, "task_mqtt", 4096, NULL, 5, &xTaskMQTT); // esp_log_level_set("wifi", ESP_LOG_ERROR); xTaskCreate(&task_wifi, "task_wifi", 4096, NULL, 3, &xTaskWifi); vTaskDelay(10 / portTICK_PERIOD_MS); /* * Main application loop. */ int State = State_Init; int OldState = State_Init + 1; while (1) { if (OldState != State) { ESP_LOGI(TAG, "Switch to state %d", State); OldState = State; } gTimeInMillis = millis(); switch (State) { case State_Init: getTempBaro(); // getLightValues(); getVoltsCurrent(); State = State_Connect; DisCounter = 0; request_WiFi(true); break; case State_Connect: if (ready_WiFi() && ready_mqtt()) { State = State_Working; Alarm &= ~AL_NOWIFI; ESP_LOGI(TAG, "Connected counter %d", DisCounter); DisCounter = 0; } else { DisCounter++; if (DisCounter > 30) { Alarm |= AL_NOWIFI; request_WiFi(false); State = State_Init; } vTaskDelay(2000 / portTICK_PERIOD_MS); } break; case State_Working: WorkAgain = false; // Measure 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 (EEPROM.read(EM_DS_Active)) { // totalTime += EEPROM.read(EM_DS_Time) * 1000; // batteryCurrent += DS_CURRENT * EEPROM.read(EM_DS_Time) * 1000; // // Serial.printf("Added %d totalTime %d\n", EEPROM.read(EM_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); /* Check alarm conditions */ if (batteryState <= 10) { Alarm |= AL_ACCULOW; } else { Alarm &= ~AL_ACCULOW; } } getTempBaro(); publish(); State = State_WorkDone; gTimeNext = millis() + SUB_TIME; break; case State_WorkDone: vTaskDelay(2 / portTICK_PERIOD_MS); // Hang around for a while to process the subscriptions. if (WorkAgain) { // Some command was executed. State = State_Working; } if (gTimeInMillis > gTimeNext) { State = State_Stop; } break; case State_Stop: request_WiFi(false); // // Reset values for average current measurement. // HaveIP = false; loopno = 0; gLastTime = millis(); vTaskDelay(10 / portTICK_PERIOD_MS); // #if defined(ARDUINO_ESP8266_WEMOS_D1MINI) // WiFi.forceSleepBegin(0); // 0 == forever // #endif /* * If any output is on, use 6 10 seconds loops. * If nothing on, do a deep sleep. */ // if (EEPROM.read(EM_Relay1) || EEPROM.read(EM_Relay2) || EEPROM.read(EM_Dimmer3) || EEPROM.read(EM_Dimmer4)) { // if (EEPROM.read(EM_DS_Active)) { // EEPROM.write(EM_DS_Active, 0); // EEPROM.commit(); // } // // Active mode, 60 seconds loop ST_LOOPS = 6; gTimeNext = millis() + ST_INTERVAL; ESP_LOGI(TAG, "Start sleeploops"); State = State_Wait; // #if Debug == true // Serial.println(F("Start sleeploops")); // #endif // State = State_Wait; // } else { // ds_time = DS_TIME; // if (solarVolts < 6) { // // At night, increase the deep-sleep time. // ds_time *= 4; // } // if ((! EEPROM.read(EM_DS_Active)) || (EEPROM.read(EM_DS_Time) != ds_time)) { // EEPROM.write(EM_DS_Active, 1); // EEPROM.write(EM_DS_Time, ds_time); // EEPROM.commit(); // Serial.println("wrote new deep-sleep settings"); // } // State = State_GoSleep; // } /* * Update CRC and write rtcData. */ // rtcData.crc32 = calculateCRC32((uint8_t*) &rtcData.data, sizeof(rtcData)-4); // #if defined(ARDUINO_ESP8266_WEMOS_D1MINI) // if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) { // #if Debug == true // Serial.print("Write: "); // printMemory(); // #endif // } else { // Serial.println("Write error rtcData"); // } // #endif break; case State_Wait: if (gTimeInMillis > gTimeNext) { State = State_Measure; } vTaskDelay(10 / portTICK_PERIOD_MS); break; case State_Measure: getVoltsCurrent(); // if (isnan(Temperature)) { // getTempHumi(); // } gTimeNext = millis() + ST_INTERVAL; if (loopno >= ST_LOOPS) { ESP_LOGI(TAG, "Enough loops, do connect"); // getLightValues(); State = State_Connect; DisCounter = 0; request_WiFi(true); } else { State = State_Wait; } break; case State_GoSleep: ESP_LOGE(TAG, "Entered GoSleep -- not supported"); // ds_time = EEPROM.read(EM_DS_Time); // #if Debug == true // Serial.printf("Going to deep-sleep for %d seconds\n", ds_time); // #endif // ESP.deepSleep(ds_time * 1e6); break; } vTaskDelay(20 / portTICK_PERIOD_MS); } // Not reached. }