main/iotbalkon.c

Mon, 17 Apr 2023 16:53:38 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Mon, 17 Apr 2023 16:53:38 +0200
changeset 33
5bd5f6668f71
parent 32
84e54b14e7db
permissions
-rw-r--r--

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.
}

mercurial