/* ULP Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <inttypes.h>
#include "esp_sleep.h"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "soc/sens_reg.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_periph.h"
#include "soc/rtc_io_struct.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "ulp.h"
#include "ulp_main.h"
#include "ulp/ulp_config.h"
#include "ulp_adc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "tusb_console.h"

extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[]   asm("_binary_ulp_main_bin_end");

static bool usb_power_en = false;

static void initialize_pins(void);
static void initialize_rtc_pins(void);
static void init_ulp_program(void);
static void update_pulse_count(void);
static void initialize_usb_serial(void);
static int adc_to_voltage(int input);

#define LED_S2			GPIO_NUM_15
#define LED_STATE		GPIO_NUM_35
#define BATT_VOL		GPIO_NUM_9
#define BATT_VOL_ADC	ADC_CHANNEL_8
#define BATT_EN			GPIO_NUM_12

//=====================================================================================
void app_main(void)
{
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();

	// Установка пинов
	initialize_pins();
	usb_power_en = !gpio_get_level(BATT_EN);

    bool b = true;

    gpio_set_level(LED_S2, b);
    gpio_set_level(LED_STATE, b);
    vTaskDelay(100 / portTICK_PERIOD_MS);
    gpio_set_level(LED_STATE, usb_power_en);
    
    initialize_usb_serial();
    printf("\nStartup %u\n", cause);

    int i = 20;
    while (i--) {
        vTaskDelay(100 / portTICK_PERIOD_MS);
        gpio_set_level(LED_S2, b);
        b = !b;
    }

    if (cause != ESP_SLEEP_WAKEUP_ULP) {
        printf("Not ULP wakeup, initializing ULP\n");
        initialize_rtc_pins();
        init_ulp_program();
    } else {
        static const char event_text[][16] = { "None", "Time", "Button" };
        printf("ULP wakeup, event: %s\n", event_text[ulp_wake_up_event]);
        ulp_wake_up_event = 0;
        //update_pulse_count();
    }

	int loop_count = 0;
    while (1) {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
		loop_count++;
        
        if (ulp_battery_adc_value > 4000) b = true;
        gpio_set_level(LED_S2, b);
        b = !b;

		usb_power_en = !gpio_get_level(BATT_EN);
        gpio_set_level(LED_STATE, usb_power_en);
		if (!usb_power_en && (loop_count > 10)) {
    		printf("Entering deep sleep\n\n");
    		ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup() );
            esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
    		esp_deep_sleep_start();
		}

//3v = 7580
//4v = 
//4.5v = 8191

        printf("Input %4u, CH0 %5u adc %4u, CH1 %5u adc %4u, batt %4u, wake %3u\n", 
            (uint16_t)ulp_input, 
            (uint16_t)ulp_ch0_pulse_count, (uint16_t)ulp_ch0_adc_value, 
            (uint16_t)ulp_ch1_pulse_count, (uint16_t)ulp_ch1_adc_value,
			(uint16_t)(adc_to_voltage(ulp_battery_adc_value)), (uint16_t)ulp_wake_up_counter);

    }

    //printf("Entering deep sleep\n\n");
    //ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup() );
    //esp_deep_sleep_start();
}

//=====================================================================================
static void startup(void)
{

}

//=====================================================================================
static void init_ulp_program(void)
{
    // Load binary
    esp_err_t err = ulp_load_binary(0, ulp_main_bin_start,
            (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
    ESP_ERROR_CHECK(err);

    /* Initialize some variables used by ULP program.
     * Each 'ulp_xyz' variable corresponds to 'xyz' variable in the ULP program.
     * These variables are declared in an auto generated header file,
     * 'ulp_main.h', name of this file is defined in component.mk as ULP_APP_NAME.
     * These variables are located in RTC_SLOW_MEM and can be accessed both by the
     * ULP and the main CPUs.
     *
     * Note that the ULP reads only the lower 16 bits of these variables.
     */
    ulp_wake_up_counter = 0;
    ulp_wake_up_period = 120 * ULP_WAKEUP_PERIOD_SEC;

    ulp_ch0_type = 1;
    ulp_ch1_type = 2;
	ulp_use_led = 1;
	ulp_use_out = 1;

    ulp_adc_cfg_t cfg_1 = {
        .adc_n    = ADC_UNIT_1,
        .channel  = ULP_BATTERY_ADC,
        .width    = ADC_BITWIDTH_13,
        .atten    = ADC_ATTEN_DB_12,
        .ulp_mode = ADC_ULP_MODE_FSM,
    };
    ESP_ERROR_CHECK(ulp_adc_init(&cfg_1));

#if CONFIG_IDF_TARGET_ESP32
    /* Disconnect GPIO12 and GPIO15 to remove current drain through
     * pullup/pulldown resistors on modules which have these (e.g. ESP32-WROVER)
     * GPIO12 may be pulled high to select flash voltage.
     */
    rtc_gpio_isolate(GPIO_NUM_12);
    rtc_gpio_isolate(GPIO_NUM_15);
#endif // CONFIG_IDF_TARGET_ESP32

    esp_deep_sleep_disable_rom_logging(); // suppress boot messages

    // Set ULP wake up period
    ulp_set_wakeup_period(0, ULP_WAKEUP_PERIOD);

    // Start the program
    err = ulp_run(&ulp_entry - RTC_SLOW_MEM);
    ESP_ERROR_CHECK(err);
}

//=====================================================================================
static void initialize_pins(void)
{
    gpio_set_direction(LED_S2, GPIO_MODE_OUTPUT);
    gpio_set_direction(LED_STATE, GPIO_MODE_OUTPUT);
    gpio_set_direction(BATT_EN, GPIO_MODE_INPUT);
    gpio_set_direction(BATT_VOL, GPIO_MODE_INPUT);
    gpio_pulldown_dis(BATT_EN);
    gpio_pullup_en(BATT_EN);
}

//=====================================================================================
static void initialize_rtc_pins(void)
{
	// ULP_BUTTON_IO: input, pull-up
    rtc_gpio_init(ULP_BUTTON_IO);
    rtc_gpio_set_direction(ULP_BUTTON_IO, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_BUTTON_IO);
    rtc_gpio_pullup_en(ULP_BUTTON_IO);
    rtc_gpio_hold_en(ULP_BUTTON_IO);

	// ULP_PULL_UP: output, no pull-up
    rtc_gpio_init(ULP_PULL_UP);
    rtc_gpio_set_direction(ULP_PULL_UP, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_PULL_UP);
    rtc_gpio_pullup_dis(ULP_PULL_UP);
    rtc_gpio_hold_en(ULP_PULL_UP);

	// ULP_PWR: output, no pull-up
    rtc_gpio_init(ULP_PWR);
    rtc_gpio_set_direction(ULP_PWR, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_PWR);
    rtc_gpio_pullup_dis(ULP_PWR);
    rtc_gpio_hold_en(ULP_PWR);

	// ULP_CH0_IO: input, no pull-up
    rtc_gpio_init(ULP_CH0_IO);
    rtc_gpio_set_direction(ULP_CH0_IO, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_CH0_IO);
    rtc_gpio_pullup_dis(ULP_CH0_IO);
    rtc_gpio_hold_en(ULP_CH0_IO);

	// ULP_CH1_IO: input, no pull-up
    rtc_gpio_init(ULP_CH1_IO);
    rtc_gpio_set_direction(ULP_CH1_IO, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_CH1_IO);
    rtc_gpio_pullup_dis(ULP_CH1_IO);
    rtc_gpio_hold_en(ULP_CH1_IO);

	// ULP_CH0_LED: output, no pull-up
    rtc_gpio_init(ULP_CH0_LED);
    rtc_gpio_set_direction(ULP_CH0_LED, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_CH0_LED);
    rtc_gpio_pullup_dis(ULP_CH0_LED);
    rtc_gpio_hold_en(ULP_CH0_LED);

	// ULP_CH1_LED: output, no pull-up
    rtc_gpio_init(ULP_CH1_LED);
    rtc_gpio_set_direction(ULP_CH1_LED, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_CH1_LED);
    rtc_gpio_pullup_dis(ULP_CH1_LED);
    rtc_gpio_hold_en(ULP_CH1_LED);

    rtc_gpio_init(ULP_CH0_OUT);
    rtc_gpio_set_direction(ULP_CH0_OUT, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_CH0_OUT);
    rtc_gpio_pullup_dis(ULP_CH0_OUT);
    rtc_gpio_hold_en(ULP_CH0_OUT);

    rtc_gpio_init(ULP_CH1_OUT);
    rtc_gpio_set_direction(ULP_CH1_OUT, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(ULP_CH1_OUT);
    rtc_gpio_pullup_dis(ULP_CH1_OUT);
    rtc_gpio_hold_en(ULP_CH1_OUT);

    //rtc_gpio_force_hold_en_all();
}

//=====================================================================================
static void initialize_usb_serial(void)
{
    const tinyusb_config_t tusb_cfg = {
        .device_descriptor = NULL,
        .string_descriptor = NULL,
        .external_phy = false,
    };
    if (tinyusb_driver_install(&tusb_cfg) == ESP_OK) {

        tinyusb_config_cdcacm_t acm_cfg = {
            .usb_dev = TINYUSB_USBDEV_0,
            .cdc_port = TINYUSB_CDC_ACM_0,
            .rx_unread_buf_sz = 64,
            .callback_rx = NULL,
            .callback_rx_wanted_char = NULL,
            .callback_line_state_changed = NULL,
            .callback_line_coding_changed = NULL
        }; 
        tusb_cdc_acm_init(&acm_cfg);
    }
    
    esp_tusb_init_console(TINYUSB_CDC_ACM_0);
}

//=====================================================================================
static int adc_to_voltage(int input)
{
    return input * 4 * 1100 / ULP_ADC_MAX;
}


//=====================================================================================
static void update_pulse_count(void)
{
    const char* nvs_namespace = "plusecnt";
    const char* count_key = "count";

    ESP_ERROR_CHECK( nvs_flash_init() );
    nvs_handle_t handle;
    ESP_ERROR_CHECK( nvs_open(nvs_namespace, NVS_READWRITE, &handle));
    uint32_t pulse_count = 0;
    esp_err_t err = nvs_get_u32(handle, count_key, &pulse_count);
    assert(err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND);
    printf("Read pulse count from NVS: %5" PRIu32"\n", pulse_count);

    /* ULP program counts signal edges, convert that to the number of pulses */
    uint32_t pulse_count_from_ulp = (ulp_ch0_pulse_count & UINT16_MAX) / 2;
    /* In case of an odd number of edges, keep one until next time */
    printf("Pulse count from ULP: %5" PRIu32"\n", pulse_count_from_ulp);

    /* Save the new pulse count to NVS */
    pulse_count += pulse_count_from_ulp;
    ESP_ERROR_CHECK(nvs_set_u32(handle, count_key, pulse_count));
    ESP_ERROR_CHECK(nvs_commit(handle));
    nvs_close(handle);
    printf("Wrote updated pulse count to NVS: %5" PRIu32"\n", pulse_count);
}
