main/co2meter.c

Fri, 11 Oct 2019 13:12:34 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 11 Oct 2019 13:12:34 +0200
changeset 8
c6bbd1380f22
parent 7
6eafc4c2bf3d
child 9
a85995941d0d
permissions
-rw-r--r--

Added alarm flag for units.

/*
 * co2meter project.
 */

#include "config.h"

static const char *TAG = "co2meter";

#define PIN_SDA 		(CONFIG_I2C_MASTER_SDA)
#define PIN_SCL			(CONFIG_I2C_MASTER_SCL)
#define ROT_ENC_A_GPIO		(CONFIG_ROT_ENC_A_GPIO)
#define ROT_ENC_B_GPIO		(CONFIG_ROT_ENC_B_GPIO)
#define ROT_ENC_SW_GPIO		(CONFIG_ROT_ENC_SW_GPIO)


#define ENABLE_HALF_STEPS	false		///< Set to true to enable tracking of rotary encoder at half step resolution
#define RESET_AT		0		///< Set to a positive non-zero number to reset the position if this value is exceeded
#define FLIP_DIRECTION		false		///< Set to true to reverse the clockwise/counterclockwise sense


int					Main_Loop1 = MAIN_LOOP1_INIT;	///< Loop 1 init
int					Main_Loop2 = -1;		///< Loop 2 invalid
bool                            	System_TimeOk = false;          ///< System time status
time_t                          	now;                            ///< Current time
struct tm                       	timeinfo;                       ///< Current time structure
char                            	strftime_buf[64];               ///< Time buffer
static RTC_DATA_ATTR struct timeval	sleep_enter_time;
static TaskHandle_t			xTaskDS18B20 = NULL;
static TaskHandle_t			xTaskADC = NULL;
static TaskHandle_t			xTaskWifi = NULL;
static TaskHandle_t			xTaskMQTT = NULL;
const esp_app_desc_t			*app_desc = NULL;


extern unit_t				units[3];			///< Pressure test units
extern SemaphoreHandle_t		xSemaphoreUnits;		///< Units lock semaphore
extern DS18B20_State            	*ds18b20_state;         	///< DS18B20 state
extern SemaphoreHandle_t        	xSemaphoreDS18B20;      	///< DS18B20 lock semaphore
extern ADC_State                	*adc_state;             	///< ADC state
extern SemaphoreHandle_t        	xSemaphoreADC;          	///< ADC lock semaphore


void app_main()
{
    struct timeval	now;
    gettimeofday(&now, NULL);
    int			sleep_time_ms = (now.tv_sec - sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - sleep_enter_time.tv_usec) / 1000;
    int			New_Loop2 = MAIN_LOOP2_INIT;
    char		buf[65];
    esp_err_t           ret;

    Main_Loop1 = MAIN_LOOP1_INIT;
    Main_Loop2 = -1;

    switch (esp_sleep_get_wakeup_cause()) {
        case ESP_SLEEP_WAKEUP_EXT1: {
            uint64_t wakeup_pin_mask = esp_sleep_get_ext1_wakeup_status();
            if (wakeup_pin_mask != 0) {
                int pin = __builtin_ffsll(wakeup_pin_mask) - 1;
                printf("Wake up from GPIO %d\n", pin);
            } else {
                printf("Wake up from GPIO\n");
            }
            break;
        }
        case ESP_SLEEP_WAKEUP_TIMER: {
	    ESP_LOGI(TAG, "Starting from deep sleep, timer wakeup after %dms", sleep_time_ms);
            break;
        }
        case ESP_SLEEP_WAKEUP_UNDEFINED:
        default:
            ESP_LOGI(TAG, "Starting from hard reset");
    }

    const int wakeup_time_sec = 55;
    ESP_LOGI(TAG, "Enabling timer wakeup, %ds", wakeup_time_sec);
    esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000);

//    const int ext_wakeup_pin_1 = ROT_ENC_SW_GPIO; // 25 in example, redefine to rotary name.
//    const uint64_t ext_wakeup_pin_1_mask = 1ULL << ext_wakeup_pin_1;

//    printf("Enabling EXT1 wakeup on pins GPIO%d\n", ext_wakeup_pin_1);
//    esp_sleep_enable_ext1_wakeup(ext_wakeup_pin_1_mask, ESP_EXT1_WAKEUP_ANY_HIGH); // TODO: what is the logic of the rotary button.

    // Isolate GPIO12 pin from external circuits. This is needed for modules
    // which have an external pull-up resistor on GPIO12 (such as ESP32-WROVER)
    // to minimize current consumption.
//    rtc_gpio_isolate(GPIO_NUM_12);

    app_desc = esp_ota_get_app_description();

    /*
     * Initialize NVS
     */
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    /*
     * Setup the OLED display.
     * See: https://github.com/nkolban/esp32-snippets/blob/master/hardware/displays/U8G2/
     */
    u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
    u8g2_esp32_hal.sda = PIN_SDA;
    u8g2_esp32_hal.scl = PIN_SCL;
    u8g2_esp32_hal_init(u8g2_esp32_hal);

    u8g2_t u8g2; // a structure which will contain all the data for one display
    u8g2_Setup_sh1106_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb);  // init u8g2 structure
    u8x8_SetI2CAddress(&u8g2.u8x8, 0x78);
    ESP_LOGI(TAG, "u8g2_InitDisplay");
    u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,

    /*
     * Setup SPIFFS filesystem
     */
    ESP_LOGI(TAG, "Initializing SPIFFS");
    esp_vfs_spiffs_conf_t conf = {
        .base_path = "/spiffs",
        .partition_label = NULL,
        .max_files = 5,
        .format_if_mount_failed = true
    };

    /*
     * Use settings defined above to initialize and mount SPIFFS filesystem.
     * Note: esp_vfs_spiffs_register is an all-in-one convenience function.
     */
    ret = esp_vfs_spiffs_register(&conf);

    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount or format filesystem");
        } else if (ret == ESP_ERR_NOT_FOUND) {
            ESP_LOGE(TAG, "Failed to find SPIFFS partition");
        } else {
            ESP_LOGE(TAG, "Failed to initialize SPIFFS (%d)", ret);
        }
	u8g2_SetFont(&u8g2, u8g2_font_t0_15_tr);
	u8g2_DrawStr(&u8g2,2,12,"SPIFFS:");
	u8g2_DrawStr(&u8g2,2,24,"init error");
	u8g2_SendBuffer(&u8g2);
        u8g2_SetPowerSave(&u8g2, 0);
        return; // Stop application.
    }

    size_t total = 0, used = 0;
    ret = esp_spiffs_info(NULL, &total, &used);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to get SPIFFS partition information");
	u8g2_SetFont(&u8g2, u8g2_font_t0_15_tr);
        u8g2_DrawStr(&u8g2,2,12,"SPIFFS:");
	u8g2_DrawStr(&u8g2,2,24,"partition error");
        u8g2_SendBuffer(&u8g2);
        u8g2_SetPowerSave(&u8g2, 0);
        return; // Stop application.
    } else {
        ESP_LOGI(TAG, "Partition size: %d, used: %d - %d%%", total, used, (used * 100) / total);
    }

    // Just to debug, list the /spiffs filesystem.
#if 0
    DIR *dir = opendir("/spiffs");
    struct dirent* de = readdir(dir);
    while (de) {
        if (de->d_type == DT_REG) {
            printf("F ");
        }
        if (de->d_type == DT_DIR) {
            printf("D ");
        }
        printf("%s\n", de->d_name);
        de = readdir(dir);
    }
    closedir(dir);
#endif

    /*
     * Read or create configuration
     */
    read_config();
    read_units();

//add_station((uint8_t *)"MBSE_WLR", (uint8_t *)"abcjkltuv");
//remove_station((uint8_t *)"MBSE_WLP");

    /*
     * Create FreeRTOS tasks
     */
    xSemaphoreDS18B20 = xSemaphoreCreateMutex();
    xSemaphoreADC = xSemaphoreCreateMutex();
    xSemaphoreUnits = xSemaphoreCreateMutex();

    xTaskCreate(&task_ds18b20, "task_ds18b20",  2560, NULL, 8, &xTaskDS18B20);
    xTaskCreate(&task_adc,     "task_adc",      2560, NULL, 8, &xTaskADC);
    esp_log_level_set("wifi", ESP_LOG_ERROR);
    xTaskCreate(&task_wifi,    "task_wifi",     4096, NULL, 3, &xTaskWifi);
    vTaskDelay(10 / portTICK_PERIOD_MS);
    esp_log_level_set("MQTT_CLIENT", ESP_LOG_ERROR);
    xTaskCreate(&task_mqtt,    "task_mqtt",     4096, NULL, 5, &xTaskMQTT);

    /*
     * Setup the Rotary Encoder.
     * esp32-rotary-encoder requires that the GPIO ISR service is 
     * installed before calling rotary_encoder_register()
     */
    ESP_ERROR_CHECK(gpio_install_isr_service(0));
    rotary_encoder_info_t rinfo = { 0 };
    ESP_ERROR_CHECK(rotary_encoder_init(&rinfo, ROT_ENC_A_GPIO, ROT_ENC_B_GPIO));
//    ESP_ERROR_CHECK(rotary_encoder_enable_half_steps(&rinfo, ENABLE_HALF_STEPS));
#ifdef FLIP_DIRECTION
//    ESP_ERROR_CHECK(rotary_encoder_flip_direction(&rinfo));
#endif

    // Create a queue for events from the rotary encoder driver.
    // Tasks can read from this queue to receive up to date position information.
//    QueueHandle_t event_queue = rotary_encoder_create_queue();
//    ESP_ERROR_CHECK(rotary_encoder_set_queue(&rinfo, event_queue));


    /*
     * Main application loop.
     */
    while (1) {

	ESP_LOGI(TAG, "Entered app loop");

	/* Measure process or user input via rotary switch */
	while (1) {
	    switch (Main_Loop1) {
		case MAIN_LOOP1_INIT:
		    ESP_LOGI(TAG, "Loop timer: Init");
		    // If configured do MAIN_LOOP1_CONNECT
		    Main_Loop1 = MAIN_LOOP1_CONNECT;
		    requestWiFi_system(true);
		    request_ds18b20();
		    request_adc();

                    u8g2_ClearBuffer(&u8g2);
		    u8g2_DrawHLine(&u8g2, 0, 14, 128);
                    u8g2_SetFont(&u8g2, u8g2_font_t0_15_tr);
		    sprintf(buf, "CO2 meter %s", app_desc->version);
		    u8g2_uint_t w = u8g2_GetStrWidth(&u8g2, buf);
                    u8g2_DrawStr(&u8g2, (128 - w) / 2,12, buf);

		    break;

		case MAIN_LOOP1_CONNECT:
                    if (ready_WiFi())
                        Main_Loop1 = MAIN_LOOP1_MQTT_CONNECT;
                    break;

		case MAIN_LOOP1_MQTT_CONNECT:
		    if (ready_ds18b20() && ready_adc()) {
			connect_mqtt(true);
			Main_Loop1 = MAIN_LOOP1_WAITCON;
			ESP_LOGI(TAG, "Loop timer: Wait MQTT");

			/* Get global temperature, use for all units. */
			uint32_t temp = 0;
			int state = 0;
			char rom_code[17];
			if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) {
			    temp = (ds18b20_state->sensor[0].temperature * 1000);
			    state = (ds18b20_state->sensor[0].error == 0) ? 0:1;
			    strncpy(rom_code, ds18b20_state->sensor[0].rom_code, strlen(ds18b20_state->sensor[0].rom_code));
        		    xSemaphoreGive(xSemaphoreDS18B20);
    			}

			u8g2_SetFont(&u8g2, u8g2_font_t0_22b_tf);
			sprintf(buf, "%.1f °C", temp / 1000.0);
			u8g2_uint_t w = u8g2_GetUTF8Width(&u8g2, buf);
			u8g2_DrawUTF8(&u8g2, (128 - w) / 2,40, buf);
			u8g2_SetFont(&u8g2, u8g2_font_t0_18b_tf);

			/* Copy measured data and calculate results */
			if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE) {
			    for (int i = 0; i < 3; i++) {
				if (i == 0)
				    units[i].mode = 1;
			    	units[i].temperature = temp;
			    	units[i].temperature_state = state;
				units[i].alarm = 0;
				if (state)
				    units[i].alarm |= ALARM_SYS_TEMPERATURE & ALARM_UNIT_TEMPERATURE;
			    	strncpy(units[i].temperature_rom_code, rom_code, 17);
			    	if (xSemaphoreTake(xSemaphoreADC, 10) == pdTRUE) {
				    units[i].pressure_state = adc_state->Pressure[i].error;
				    units[i].pressure_channel = adc_state->Pressure[i].channel;
				    units[i].pressure_voltage = adc_state->Pressure[i].voltage;
				    units[i].pressure_zero = 110;
				    if (units[i].pressure_state || units[i].pressure_voltage < 80)
					units[i].alarm |= ALARM_UNIT_PRESSURE;
				    int P = (units[i].pressure_voltage / (adc_state->Batt_voltage / 1000) - units[i].pressure_zero) * 14; // in bar
				    if (P < 0)
				    	P = 0;
				    units[i].pressure = P;
				    sprintf(buf, "%.1f", P / 1000.0);
				    w = u8g2_GetUTF8Width(&u8g2, buf);
				    u8g2_DrawUTF8(&u8g2, ((42 - w) / 2) + i * 43,63, buf);
printf("%d volt: %d batt: %d scale: %d  bar: %d\n", i, units[i].pressure_voltage, adc_state->Batt_voltage, 
		units[i].pressure_voltage / (adc_state->Batt_voltage / 1000) - units[i].pressure_zero, P);
// Moet die echt op 5 volt?
// Verbruik 10 mA
// Setup tijd max 2 mS
				    xSemaphoreGive(xSemaphoreADC);
			    	}
			    }
			    write_units();
			    xSemaphoreGive(xSemaphoreUnits);
			    u8g2_SendBuffer(&u8g2);
			    u8g2_SetPowerSave(&u8g2, 0); // wake up display
			}
		    }
		    break;

		case MAIN_LOOP1_WAITCON:
		    if (ready_mqtt())
			Main_Loop1 = MAIN_LOOP1_SEND;
		    break;

		case MAIN_LOOP1_SEND:
		    ESP_LOGI(TAG, "Loop timer: Send MQTT");
		    publishNode();
		    publishUnits();

Main_Loop1 = MAIN_LOOP1_MQTT_DISCONNECT;
		    break;

		case MAIN_LOOP1_WAITACK:
		    break;

		case MAIN_LOOP1_MQTT_DISCONNECT:
		    ESP_LOGI(TAG, "Loop timer: Disconnect MQTT");
		    connect_mqtt(false);
                    Main_Loop1 = MAIN_LOOP1_DISCONNECT;
		    break;

		case MAIN_LOOP1_DISCONNECT:
		    if (! ready_mqtt()) {
			ESP_LOGI(TAG, "Loop timer: WiFi off");
		    	requestWiFi_system(false);
		    	Main_Loop1 = MAIN_LOOP1_WIFI_OFF;
		    }
		    break;

		case MAIN_LOOP1_WIFI_OFF:
		    if (! ready_WiFi()) {
			ESP_LOGI(TAG, "Loop timer: Done");
			Main_Loop1 = MAIN_LOOP1_DONE;
		    }
		    break;

		case MAIN_LOOP1_DONE:
		    break;
	    }

	    /*
	     * One time actions
	     */
	    if (New_Loop2 != Main_Loop2) {

		Main_Loop2 = New_Loop2;

		switch (Main_Loop2) {
		    case MAIN_LOOP2_INIT:
			ESP_LOGI(TAG, "Loop user: Init");
//			u8g2_SetPowerSave(&u8g2, 0); // wake up display
//			u8g2_ClearBuffer(&u8g2);
//			New_Loop2 = MAIN_LOOP2_INACTIVE;
			New_Loop2 = MAIN_LOOP2_DONE;
			break;

		    case MAIN_LOOP2_INACTIVE:
//			u8g2_SetPowerSave(&u8g2, 1); // powersave display
			New_Loop2 = MAIN_LOOP2_DONE;
			break;

		    default:
			break;
		}
	    }

	    /*
	     * Action process.
	     */
	    switch (Main_Loop2) {
	    // If wakeup from GPIO -- state machine 2
		// Init OLED
		// If not configured, start configure
		// If configured select first unit
		// New rotate position, set screen, reset waittimer
		// Handle screen (first is show measured values)
		// Count inactivity
		// flag if inactive and OLED lowpower.

	    // Break if all done and inactive.
		default:
		    break;
	    }

	    if (Main_Loop1 == MAIN_LOOP1_DONE && Main_Loop2 == MAIN_LOOP2_DONE) 
	    	break;

	    vTaskDelay(10 / portTICK_PERIOD_MS);
	}

//	u8g2_ClearBuffer(&u8g2);
//	u8g2_SendBuffer(&u8g2);
//	u8g2_SetPowerSave(&u8g2, 1);

//	printf("Simulate deep sleep\n");
// 	vTaskDelay(1000 * wakeup_time_sec / portTICK_PERIOD_MS);
  
	printf("Entering deep sleep\n");
    	gettimeofday(&sleep_enter_time, NULL);
	esp_deep_sleep_start();

	Main_Loop1 = MAIN_LOOP1_INIT;
	New_Loop2 = MAIN_LOOP2_INIT;
    }

}

mercurial