/* 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 "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.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 adc_oneshot_unit_handle_t adc1_handle;
static adc_cali_handle_t adc_batt_cali_handle = NULL;

static void initialize_pins(void);
static void init_ulp_program(void);
static void update_pulse_count(void);
static void initialize_usb_serial(void);
static int get_batt_voltage(void);

#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)
{
	// Установка пинов
	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\n");

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

    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    if (cause != ESP_SLEEP_WAKEUP_ULP) {
        printf("Not ULP wakeup, initializing ULP\n");
        init_ulp_program();
    } else {
        printf("ULP wakeup, saving pulse count\n");
        //update_pulse_count();
    }

	int loop_count = 0;
    while (1) {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
		loop_count++;
        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() );
    		rtc_gpio_force_hold_en_all();
    		esp_deep_sleep_start();
		}

        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)(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_0,
        .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 bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
    adc_cali_handle_t handle = NULL;
    esp_err_t ret = ESP_FAIL;
    bool calibrated = false;

#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
    if (!calibrated) {
        adc_cali_curve_fitting_config_t cali_config = {
            .unit_id = unit,
            .chan = channel,
            .atten = atten,
            .bitwidth = ADC_BITWIDTH_DEFAULT,
        };
        ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
        if (ret == ESP_OK) {
            calibrated = true;
        }
    }
#endif

#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
    if (!calibrated) {
        adc_cali_line_fitting_config_t cali_config = {
            .unit_id = unit,
            .atten = atten,
            .bitwidth = ADC_BITWIDTH_DEFAULT,
        };
        ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
        if (ret == ESP_OK) {
            calibrated = true;
        }
    }
#endif

    *out_handle = handle;
    if (ret == ESP_OK) {
        // Calibration Success
    } else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
        // eFuse not burnt, skip software calibration
    } else {
        // Invalid arg or no memory
    }

    return calibrated;
}

//=====================================================================================
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);

    // Initialize RTC GPIO

    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);

    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);

    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
	RTCIO.touch_pad[ULP_CH0_IO].mux_sel = 1;
	RTCIO.touch_pad[ULP_CH0_IO].fun_sel = 0;
	RTCIO.touch_pad[ULP_CH0_IO].fun_ie = 1;
	RTCIO.touch_pad[ULP_CH0_IO].slp_ie = 1;
	RTCIO.touch_pad[ULP_CH0_IO].rde = 0;
	RTCIO.touch_pad[ULP_CH0_IO].rue = 0;
	RTCIO.enable_w1tc.w1tc = 1 << ULP_CH0_IO;
    rtc_gpio_hold_en(ULP_CH0_IO);

	// ULP_CH1_IO: input, no pull-up
	RTCIO.touch_pad[ULP_CH1_IO].mux_sel = 1;
	RTCIO.touch_pad[ULP_CH1_IO].fun_sel = 0;
	RTCIO.touch_pad[ULP_CH1_IO].fun_ie = 1;
	RTCIO.touch_pad[ULP_CH1_IO].slp_ie = 1;
	RTCIO.touch_pad[ULP_CH1_IO].rde = 0;
	RTCIO.touch_pad[ULP_CH1_IO].rue = 0;
	RTCIO.enable_w1tc.w1tc = 1 << ULP_CH1_IO;
    rtc_gpio_hold_en(ULP_CH1_IO);

	// ULP_CH0_LED: output, no pull-up
	RTCIO.touch_pad[ULP_CH0_LED].mux_sel = 1;
	RTCIO.touch_pad[ULP_CH0_LED].fun_sel = 0;
	RTCIO.touch_pad[ULP_CH0_LED].slp_oe = 1;
	RTCIO.touch_pad[ULP_CH0_LED].rde = 0;
	RTCIO.touch_pad[ULP_CH0_LED].rue = 0;
	RTCIO.enable_w1ts.w1ts = 1 << ULP_CH0_LED;
    rtc_gpio_hold_en(ULP_CH0_LED);
    rtc_gpio_hold_dis(ULP_CH0_LED);

	// ULP_CH1_LED: output, no pull-up
	RTCIO.touch_pad[ULP_CH1_LED].mux_sel = 1;
	RTCIO.touch_pad[ULP_CH1_LED].fun_sel = 0;
	RTCIO.touch_pad[ULP_CH1_LED].slp_oe = 1;
	RTCIO.touch_pad[ULP_CH1_LED].rde = 0;
	RTCIO.touch_pad[ULP_CH1_LED].rue = 0;
	RTCIO.enable_w1ts.w1ts = 1 << 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);

	return;

	// Setup ADC
    adc_oneshot_unit_init_cfg_t init_config1 = {
        .unit_id = ADC_UNIT_1,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));

    adc_oneshot_chan_cfg_t config = {
        .bitwidth = ADC_BITWIDTH_DEFAULT,
        .atten = ADC_ATTEN_DB_0,
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, BATT_VOL_ADC, &config));

	// ADC calibration
    adc_calibration_init(ADC_UNIT_1, BATT_VOL_ADC, ADC_ATTEN_DB_0, &adc_batt_cali_handle);
}

//=====================================================================================
static int get_batt_voltage(void)
{
	int adc_raw = 0, voltage = 0;
    ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, BATT_VOL_ADC, &adc_raw));

	printf("ADC Raw Data: %d\n", adc_raw);
    ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_batt_cali_handle, adc_raw, &voltage));
    printf("ADC Voltage: %d mV\n", voltage);

	return voltage;
}

//=====================================================================================
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 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);
}
