void bootstrap()
{
    DPRINT("Device booted at time: %d\n", timer_get_counter_value()); // TODO not printed for some reason, debug later
    id = hw_get_unique_id();
    hw_radio_init(&alloc_new_packet, &release_packet);

    rx_cfg.channel_id = current_channel_id;
    tx_cfg.channel_id = current_channel_id;

    ubutton_register_callback(0, &userbutton_callback);
    ubutton_register_callback(1, &userbutton_callback);

    fifo_init(&uart_rx_fifo, uart_rx_buffer, sizeof(uart_rx_buffer));

    uart_set_rx_interrupt_callback(&uart_rx_cb);
    uart_rx_interrupt_enable(true);

    sched_register_task(&start_rx);
    sched_register_task(&transmit_packet);
    sched_register_task(&start);
    sched_register_task(&process_uart_rx_fifo);

    current_state = STATE_CONFIG_DIRECTION;

    sched_post_task(&start);
    sched_post_task(&process_uart_rx_fifo);
}
// TODO document state diagram
static void switch_state(state_t new_state)
{
    switch(new_state)
    {
        case D7ASP_STATE_MASTER:
            switch(d7asp_state)
            {
                case D7ASP_STATE_IDLE:
                    d7asp_state = new_state;
                    sched_post_task(&flush_fifos);
                    DPRINT("Switching to state D7ASP_STATE_MASTER");
                    break;
                case D7ASP_STATE_SLAVE_PENDING_MASTER:
                    d7asp_state = new_state;
                    sched_post_task(&flush_fifos);
                    DPRINT("Switching to state D7ASP_STATE_MASTER");
                    break;
                case D7ASP_STATE_SLAVE:
                    d7asp_state = D7ASP_STATE_SLAVE_PENDING_MASTER;
                    DPRINT("Switching to state D7ASP_STATE_SLAVE_PENDING_MASTER");
                    break;
                case D7ASP_STATE_MASTER:
                    // new requests in fifo, reschedule for later flushing
                    // TODO sched_post_task(&flush_fifos);
                    break;
                default:
                    assert(false);
            }

            break;
        case D7ASP_STATE_SLAVE:
            switch(d7asp_state)
            {
                case D7ASP_STATE_IDLE:
                    d7asp_state = new_state;
                    current_request_id = NO_ACTIVE_REQUEST_ID;
                    DPRINT("Switching to state D7ASP_STATE_SLAVE");
                    break;
                default:
                    assert(false);
            }
            break;
        case D7ASP_STATE_SLAVE_PENDING_MASTER:
            assert(d7asp_state == D7ASP_STATE_SLAVE);
            d7asp_state = D7ASP_STATE_SLAVE_PENDING_MASTER;
            DPRINT("Switching to state D7ASP_STATE_SLAVE_PENDING_MASTER");
            break;
        case D7ASP_STATE_IDLE:
            d7asp_state = new_state;
            current_request_id = NO_ACTIVE_REQUEST_ID;
            DPRINT("Switching to state D7ASP_STATE_IDLE");
            break;
        default:
            assert(false);
    }
}
void bootstrap() {
    DPRINT("Device booted at time: %d\n", timer_get_counter_value());

#ifndef FRAMEWORK_LOG_BINARY
    console_print("\r\nPER TEST - commands:\r\n");
    console_print("  CHANfffriii  channel settings:\r\n");
    console_print("               fff frequency : 433, 868, 915\r\n");
    console_print("               r   rate      : L(ow) N(ormal) H(igh)\r\n");
    console_print("               iii center_freq_index\r\n");
    console_print("  TRANsss      transmit a packet every sss seconds.\r\n");
    console_print("  RECV         receive packets\r\n");
    console_print("  RSET         reset module\r\n");
#endif

    id = hw_get_unique_id();
    hw_radio_init(&alloc_new_packet, &release_packet);

    rx_cfg.channel_id = current_channel_id;
    tx_cfg.channel_id = current_channel_id;

    ubutton_register_callback(0, &userbutton_callback);
    ubutton_register_callback(1, &userbutton_callback);

    fifo_init(&uart_rx_fifo, uart_rx_buffer, sizeof(uart_rx_buffer));

    console_set_rx_interrupt_callback(&uart_rx_cb);
    console_rx_interrupt_enable();

    sched_register_task(&start_rx);
    sched_register_task(&transmit_packet);
    sched_register_task(&start);
    sched_register_task(&process_uart_rx_fifo);

    current_state = STATE_CONFIG_DIRECTION;

    sched_post_task(&start);
    sched_post_task(&process_uart_rx_fifo);


#ifdef PLATFORM_EFM32GG_STK3700
#else
    	char str[20];
    	channel_id_to_string(&current_channel_id, str, sizeof(str));
    	lcd_write_line(6, str);
#endif

    timer_post_task(&transmit_packet, TIMER_TICKS_PER_SEC * 1);

}
static void process_uart_rx_fifo()
{
    if(fifo_get_size(&uart_rx_fifo) >= COMMAND_SIZE)
    {
        uint8_t received_cmd[COMMAND_SIZE];
        fifo_pop(&uart_rx_fifo, received_cmd, COMMAND_SIZE);
        if(strncmp(received_cmd, COMMAND_CHAN, COMMAND_SIZE) == 0)
        {
            process_command_chan();
        }
        else if(strncmp(received_cmd, COMMAND_TRAN, COMMAND_SIZE) == 0)
        {
            while(fifo_get_size(&uart_rx_fifo) < COMMAND_TRAN_PARAM_SIZE);

            char param[COMMAND_TRAN_PARAM_SIZE];
            fifo_pop(&uart_rx_fifo, param, COMMAND_TRAN_PARAM_SIZE);
            tx_packet_delay_s = atoi(param);
            DPRINT("performing TRAN command with %d tx_packet_delay_s\r\n",
                   tx_packet_delay_s);

            stop();
            is_mode_rx = false;
            current_state = STATE_RUNNING;
            sched_post_task(&start);
        }
        else if(strncmp(received_cmd, COMMAND_RECV, COMMAND_SIZE) == 0)
        {
            DPRINT("entering RECV mode\r\n");
            stop();
            is_mode_rx = true;
            current_state = STATE_RUNNING;
            sched_post_task(&start);
        }
        else if(strncmp(received_cmd, COMMAND_RSET, COMMAND_SIZE) == 0)
        {
            DPRINT("resetting...\r\n");
            hw_reset();
        }
        else
        {
            char err[40];
            DPRINT("ERROR invalid command %.4s\n\r", received_cmd);
        }

        fifo_clear(&uart_rx_fifo);
    }

    timer_post_task_delay(&process_uart_rx_fifo, TIMER_TICKS_PER_SEC);
}
void read_rssi()
{
    timestamped_rssi_t rssi_measurement;
    rssi_measurement.tick = timer_get_counter_value();

    char rssi_samples_str[5 * RSSI_SAMPLES_PER_MEASUREMENT] = "";
    int16_t max_rssi_sample = -200;
    for(int i = 0; i < RSSI_SAMPLES_PER_MEASUREMENT; i++)
    {
        rssi_measurement.rssi[i] = hw_radio_get_rssi();
        if(rssi_measurement.rssi[i] > max_rssi_sample)
            max_rssi_sample = rssi_measurement.rssi[i];

        sprintf(rssi_samples_str + (i * 5), ",%04i", rssi_measurement.rssi[i]);
        // TODO delay?
    }

    char str[80];
    char channel_str[8] = "";


    channel_id_to_string(&rx_cfg.channel_id, channel_str, sizeof(channel_str));
    lcd_write_string(channel_str);
    sprintf(str, "%7s,%i%s\n", channel_str, rssi_measurement.tick, rssi_samples_str);
    console_print(str);

#ifdef PLATFORM_EFM32GG_STK3700
    //lcd_all_on();
    lcd_write_number(max_rssi_sample);
#elif defined HAS_LCD
    sprintf(str, "%7s,%d\n", channel_str, max_rssi_sample);
    lcd_write_string(str);
#endif

    if(!use_manual_channel_switching)
    {
        switch_next_channel();
        sched_post_task(&start_rx);
    }
    else
    {
    	sched_post_task(&process_uart_rx_fifo); // check for UART commands first
        uint16_t delay = rand() % 5000;
        timer_post_task_delay(&read_rssi, delay);
    }

    hw_watchdog_feed();
}
void button_task()
{
	button_id_t button_id = NUM_USERBUTTONS;
	ubutton_callback_t callback = 0x0;

	start_atomic();
	for(int i = 0; i < NUM_USERBUTTONS;i++)
	{
		for(;buttons[i].cur_callback_id < BUTTON_QUEUE_SIZE && buttons[i].callbacks[buttons[i].cur_callback_id] == 0x0; buttons[i].cur_callback_id++);
		if(buttons[i].cur_callback_id < BUTTON_QUEUE_SIZE)
		{
			callback = buttons[i].callbacks[buttons[i].cur_callback_id];
			button_id = i;
			buttons[i].cur_callback_id++;
			break;
		}
	}
	end_atomic();

	if(button_id < NUM_USERBUTTONS && callback != 0x0)
	{
		//reschedule the task to do the next callback (if needed)
		sched_post_task(&button_task);
		callback(button_id);
	}
}
static void on_request_completed()
{
    assert(d7asp_state == D7ASP_STATE_MASTER);
    if(!bitmap_get(current_master_session.progress_bitmap, current_request_id))
    {
        current_request_retry_count++;
        // the request may be retransmitted, don't free yet (this will be done in flush_fifo() when failed)
    }
    else
    {
        // request completed, no retries needed so we can free the packet
        packet_queue_free_packet(current_request_packet);

        // terminate the dialog if all request handled
        // we need to switch to the state idle otherwise we may receive a new packet before the task flush_fifos is handled
        // in this case, we may assert since the state remains MASTER
        if (current_request_id == current_master_session.next_request_id - 1)
        {
            flush_completed();
            return;
        }
        current_request_id = NO_ACTIVE_REQUEST_ID;
    }

    sched_post_task(&flush_fifos); // continue flushing until all request handled ...
}
void bootstrap()
{
    hw_radio_init(NULL, NULL);

    sched_register_task(&read_rssi);
    sched_register_task(&start_rx);
    sched_post_task(&start_rx);
}
static void process_uart_rx_fifo()
{
    if(fifo_get_size(&uart_rx_fifo) >= COMMAND_SIZE)
    {
        uint8_t received_cmd[COMMAND_SIZE];
        fifo_pop(&uart_rx_fifo, received_cmd, COMMAND_SIZE);
        if(strncmp(received_cmd, COMMAND_CHAN, COMMAND_SIZE) == 0)
        {
            process_command_chan();
        }
        else if(strncmp(received_cmd, COMMAND_TRAN, COMMAND_SIZE) == 0)
        {
            while(fifo_get_size(&uart_rx_fifo) < COMMAND_TRAN_PARAM_SIZE);

            char param[COMMAND_TRAN_PARAM_SIZE];
            fifo_pop(&uart_rx_fifo, param, COMMAND_TRAN_PARAM_SIZE);
            tx_packet_delay_s = atoi(param);

            stop();
            is_mode_rx = false;
            current_state = STATE_RUNNING;
            sched_post_task(&start);
        }
        else if(strncmp(received_cmd, COMMAND_RECV, COMMAND_SIZE) == 0)
        {
            stop();
            is_mode_rx = true;
            current_state = STATE_RUNNING;
            sched_post_task(&start);
        }
        else if(strncmp(received_cmd, COMMAND_RSET, COMMAND_SIZE) == 0)
        {
            hw_reset();
        }
        else
        {
            char err[40];
            snprintf(err, sizeof(err), "ERROR invalid command %.4s\n", received_cmd);
            uart_transmit_string(err);
        }

        fifo_clear(&uart_rx_fifo);
    }

    timer_post_task_delay(&process_uart_rx_fifo, TIMER_TICKS_PER_SEC);
}
static void packet_received(hw_radio_packet_t* packet) {
    uint16_t crc = __builtin_bswap16(crc_calculate(packet->data, packet->length - 2));
    if(memcmp(&crc, packet->data + packet->length + 1 - 2, 2) != 0)
    {
        DPRINT("CRC error");
        missed_packets_counter++;
    }
    else
    {
		#if HW_NUM_LEDS > 0
			led_toggle(0);
		#endif
		uint16_t msg_counter = 0;
        uint64_t msg_id;
        memcpy(&msg_id, packet->data + 1, sizeof(msg_id));
        memcpy(&msg_counter, packet->data + 1 + sizeof(msg_id), sizeof(msg_counter));
        char chan[8];
        channel_id_to_string(&(packet->rx_meta.rx_cfg.channel_id), chan, sizeof(chan));
        console_printf("%7s,%i,%i,%lu,%lu,%i\n", chan, msg_counter, packet->rx_meta.rssi, (unsigned long)msg_id, (unsigned long)id, packet->rx_meta.timestamp);
    

		if(counter == 0)
		{
			// just start, assume received all previous counters to reset PER to 0%
			received_packets_counter = msg_counter - 1;
			counter = msg_counter - 1;
		}

		uint16_t expected_counter = counter + 1;
		if(msg_counter == expected_counter)
		{
			received_packets_counter++;
			counter++;
		}
		else if(msg_counter > expected_counter)
		{
			missed_packets_counter += msg_counter - expected_counter;
			counter = msg_counter;
		}
		else
		{
            sched_post_task(&start);
		}

	    double per = 0;
	    if(msg_counter > 0)
	        per = 100.0 - ((double)received_packets_counter / (double)msg_counter) * 100.0;

	    sprintf(lcd_msg, "%i %i", (int)per, packet->rx_meta.rssi);

#ifdef PLATFORM_EFM32GG_STK3700
	    lcd_write_string(lcd_msg);
#else
	    lcd_write_line(4, lcd_msg);
#endif

    }
}
static void process_rx_fifo(void *arg) {
  if(!parsed_header) {
    // <sync byte (0xC0)><version (0x00)><length of ALP command (1 byte)><ALP command> // TODO CRC
    if(fifo_get_size(&rx_fifo) > SERIAL_ALP_FRAME_HEADER_SIZE) {
        uint8_t header[SERIAL_ALP_FRAME_HEADER_SIZE];
        fifo_peek(&rx_fifo, header, 0, SERIAL_ALP_FRAME_HEADER_SIZE);
        DPRINT_DATA(header, 3); // TODO tmp

        if(header[0] != SERIAL_ALP_FRAME_SYNC_BYTE || header[1] != SERIAL_ALP_FRAME_VERSION) {
          fifo_skip(&rx_fifo, 1);
          DPRINT("skip");
          parsed_header = false;
          payload_len = 0;
          if(fifo_get_size(&rx_fifo) > SERIAL_ALP_FRAME_HEADER_SIZE)
            sched_post_task(&process_rx_fifo);

          return;
        }

        parsed_header = true;
        fifo_skip(&rx_fifo, SERIAL_ALP_FRAME_HEADER_SIZE);
        payload_len = header[2];
        DPRINT("found header, payload size = %i", payload_len);
        sched_post_task(&process_rx_fifo);
    }
  } else {
    if(fifo_get_size(&rx_fifo) < payload_len) {
      DPRINT("payload not complete yet");
      return;
    }

    // payload complete, start parsing
    // rx_fifo can be bigger than the current serial packet, init a subview fifo
    // which is restricted to payload_len so we can't parse past this packet.
    fifo_t payload_fifo;
    fifo_init_subview(&payload_fifo, &rx_fifo, 0, payload_len);
    process_serial_frame(&payload_fifo);

    // pop parsed bytes from original fifo
    fifo_skip(&rx_fifo, payload_len - fifo_get_size(&payload_fifo));
    parsed_header = false;
  }
}
static void uart_rx_cb(uint8_t data)
{
    if( echo ) {
      console_print_byte(data);
      if( data == '\r' ) { console_print_byte('\n'); }
    }
    error_t err;
    err = fifo_put(&cmd_fifo, &data, 1); assert(err == SUCCESS);
    if(!sched_is_scheduled(&process_cmd_fifo))
        sched_post_task(&process_cmd_fifo);
}
void userbutton_callback(button_id_t button_id)
{
    // change channel and restart
    use_manual_channel_switching = true;
    switch(button_id)
    {
        case 0: switch_prev_channel(); break;
        case 1: switch_next_channel(); break;
    }

    sched_post_task(&start_rx);
}
static void button_callback(pin_id_t pin_id, uint8_t event_mask)
{
	for(int i = 0; i < NUM_USERBUTTONS;i++)
	{
		if(hw_gpio_pin_matches(buttons[i].button_id, pin_id))
		{
			//set cur_callback_id to 0 to trigger all registered callbacks and post a task to do the actual callbacks
			buttons[i].cur_callback_id = 0;
			sched_post_task(&button_task);
		}
	}
}
void bootstrap()
{
    DPRINT("Device booted at time: %d\n", timer_get_counter_value()); // TODO not printed for some reason, debug later

    data[0] = PACKET_LENGTH-1;
    int i = 1;
    for (;i<PACKET_LENGTH;i++)
    	data[i] = i;

    hw_radio_init(&alloc_new_packet, &release_packet);

    tx_packet->tx_meta.tx_cfg = tx_cfg;

	#ifdef RX_MODE
    	sched_register_task(&start_rx);
        sched_post_task(&start_rx);
	#else
        sched_register_task(&transmit_packet);
        sched_post_task(&transmit_packet);
	#endif
}
// TODO doc
// ATx\r : shell command, where x is a char which maps to a command.
// List of supported commands:
// - R: reboot device
// AT$<command handler id> : command to be handled by the command handler specified. The command handler id is a byte < 65 (non ASCII)
// The handlers are passed the command fifo (including the header) and are responsible for pop()-ing the bytes which are processed by the handler.
// When the fifo does not yet contain a full command which can be processed by the specific handler nothing should be popped and the handler will
// called again later when more data is received.
static void process_cmd_fifo()
{
    if(fifo_get_size(&cmd_fifo) >= SHELL_CMD_HEADER_SIZE)
    {
        uint8_t cmd_header[SHELL_CMD_HEADER_SIZE];
        fifo_peek(&cmd_fifo, cmd_header, 0, SHELL_CMD_HEADER_SIZE);
        if(cmd_header[0] != 'A' || cmd_header[1] != 'T')
        {
            // unexpected data, pop and return
            // TODO log?
            fifo_pop(&cmd_fifo, cmd_header, 1);
            sched_post_task(&process_cmd_fifo);
            return;
        }

        if(cmd_header[2] != '$')
        {
            process_shell_cmd(cmd_header[2]);
            fifo_pop(&cmd_fifo, cmd_header, SHELL_CMD_HEADER_SIZE);
        }
        else
        {
            get_cmd_handler_callback(cmd_header[3])(&cmd_fifo);
        }

        sched_post_task(&process_cmd_fifo);
    } else if(fifo_get_size(&cmd_fifo) >= 3) {
      // AT[\r|\n]
      uint8_t cmd_header[3];
      fifo_peek(&cmd_fifo, cmd_header, 0, 3);
      if( cmd_header[0] == 'A' && cmd_header[1] == 'T'
          && ( cmd_header[2] == '\r' || cmd_header[2] == '\n' ) )
      {
        console_print("OK\r\n");
        fifo_pop(&cmd_fifo, cmd_header, 3);
      }
    }
}
static void userbutton_callback(button_id_t button_id)
{
	stop();
    switch(button_id)
    {
        case 0:
            switch(current_state)
            {
                case STATE_CONFIG_DIRECTION:
                    current_state = STATE_CONFIG_DATARATE;
                    break;
                case STATE_CONFIG_DATARATE:
                    current_state = STATE_RUNNING;
                    break;
                case STATE_RUNNING:
                    current_state = STATE_CONFIG_DIRECTION;
                    break;
            }
            break;
        case 1:
            switch(current_state)
            {
                case STATE_CONFIG_DIRECTION:
                    is_mode_rx = !is_mode_rx;
                    break;
                case STATE_CONFIG_DATARATE:
                    if(current_channel_id.channel_header.ch_class == PHY_CLASS_NORMAL_RATE)
                        current_channel_id.channel_header.ch_class = PHY_CLASS_LO_RATE;
                    else
                        current_channel_id.channel_header.ch_class = PHY_CLASS_NORMAL_RATE;

                    break;

                case STATE_RUNNING:
                	increase_channel();
                    break;
            }
            break;
    }

    sched_post_task(&start);
}
void userbutton_callback(button_id_t button_id)
{
    // change channel and restart
    switch(button_id)
    {
        case 0:
            if(current_channel_indexes_index > 0)
                current_channel_indexes_index--;
            else
                current_channel_indexes_index = channel_count - 1;
            break;
        case 1:
            if(current_channel_indexes_index < channel_count - 1)
                current_channel_indexes_index++;
            else
                current_channel_indexes_index = 0;
    }

    sched_post_task(&start);
}
void userbutton_callback(button_id_t button_id)
{
    switch(button_id)
    {
        case 0:
            // TODO switch to values in dBm and use API to change instead of directly changing reg values
            // change ezr eirp and restart
            if(current_eirp_level < MAX_EIRP+1)
                current_eirp_level -= 0x05;
            else
                current_eirp_level = MAX_EIRP;
            //change eirp level
            change_eirp();
            break;
        case 1:
            // change channel and restart
            if(current_channel_indexes_index < channel_count - 1)
                current_channel_indexes_index++;
            else
                current_channel_indexes_index = 0;
            sched_post_task(&start);
    }
}
void d7asp_signal_transaction_response_period_elapsed()
{
    if(d7asp_state == D7ASP_STATE_MASTER)
        on_request_completed();
    else if((d7asp_state == D7ASP_STATE_SLAVE) ||
            (d7asp_state == D7ASP_STATE_SLAVE_PENDING_MASTER))
    {
        if (current_response_packet)
        {
            DPRINT("Discard the response since the response period is expired");
            packet_queue_free_packet(current_response_packet);
            current_response_packet = NULL;
        }

        if (d7asp_state == D7ASP_STATE_SLAVE)
            switch_state(D7ASP_STATE_IDLE);
        else if(d7asp_state == D7ASP_STATE_SLAVE_PENDING_MASTER)
        {
            switch_state(D7ASP_STATE_MASTER);
            DPRINT("Schedule task to flush the fifo");
            sched_post_task(&flush_fifos);
        }
    }
}
static void execute_csma_ca()
{
    // TODO generate random channel queue
    //hw_radio_set_rx(NULL, NULL, NULL); // put radio in RX but disable callbacks to make sure we don't receive packets when in this state
                                        // TODO use correct rx cfg + it might be interesting to switch to idle first depending on calculated offset
    uint16_t tx_duration = calculate_tx_duration();
    timer_tick_t Tc = CONVERT_TO_TI(current_packet->d7atp_tc);
    switch (dll_state)
    {
        case DLL_STATE_CSMA_CA_STARTED:
        {
            dll_tca = Tc - tx_duration;
            dll_cca_started = timer_get_counter_value();
            DPRINT("Tca= %i = %i - %i", dll_tca, Tc, tx_duration);

#ifndef FRAMEWORK_TIMER_RESET_COUNTER
            // Adjust TCA value according the time already elapsed in the response period
            if (tc_starting_time) // TODO how do manage tc_starting_time? not set for now
            {
                dll_tca -= dll_cca_started - tc_starting_time;
                DPRINT("Adjusted Tca= %i = %i - %i", dll_tca, dll_cca_started, tc_starting_time);
            }
#endif

            if (dll_tca <= 0)
            {
                DPRINT("Tca negative, CCA failed");
                // Let the upper layer decide eventually to change the channel in order to get a chance a send this frame
                switch_state(DLL_STATE_IDLE);
                d7anp_signal_packet_csma_ca_insertion_completed(false);
                break;
            }

            uint16_t t_offset = 0;

            csma_ca_mode_t csma_ca_mode = current_access_profile->control_csma_ca_mode;
            // TODO overrule mode to UNC for subsequent requests by the requester, or a single response to a unicast request

            switch(csma_ca_mode)
            {
                case CSMA_CA_MODE_UNC:
                    // no delay
                    dll_slot_duration = 0;
                    break;
                case CSMA_CA_MODE_AIND: // TODO implement AIND
                {
                    dll_slot_duration = tx_duration;
                    // no initial delay
                    break;
                }
                case CSMA_CA_MODE_RAIND: // TODO implement RAIND
                {
                    dll_slot_duration = tx_duration;
                    uint16_t max_nr_slots = dll_tca / tx_duration;
                    uint16_t slots_wait = get_rnd() % max_nr_slots;
                    t_offset = slots_wait * tx_duration;
                    break;
                }
                case CSMA_CA_MODE_RIGD: // TODO implement RAIND
                {
                    dll_rigd_n = 0;
                    dll_tca0 = dll_tca;
                    dll_slot_duration = (uint16_t) ((double)dll_tca0) / (2 << (dll_rigd_n));
                    t_offset = get_rnd() % dll_slot_duration;
                    break;
                }
            }

            DPRINT("slot duration: %i t_offset: %i csma ca mode: %i", dll_slot_duration, t_offset, csma_ca_mode);

            dll_to = dll_tca - t_offset;

            if (t_offset > 0)
            {
                switch_state(DLL_STATE_CCA1);
                timer_post_task_delay(&execute_cca, t_offset);
            }
            else
            {
                switch_state(DLL_STATE_CCA1);
                sched_post_task(&execute_cca);
            }

            break;
        }
        case DLL_STATE_CSMA_CA_RETRY:
        {
        	int32_t cca_duration = timer_get_counter_value() - dll_cca_started;
        	dll_to -= cca_duration;


            DPRINT("RETRY dll_to = %i < %i ", dll_to, t_g);

            if (dll_to < t_g)
            {
                switch_state(DLL_STATE_CCA_FAIL);
                sched_post_task(&execute_csma_ca);
                break;
            }

            dll_tca = dll_to;
            dll_cca_started = timer_get_counter_value();
            uint16_t t_offset = 0;

            switch(current_access_profile->control_csma_ca_mode)
            {
                case CSMA_CA_MODE_AIND:
                case CSMA_CA_MODE_RAIND:
                {
                    uint16_t max_nr_slots = dll_tca / tx_duration;
                    uint16_t slots_wait = get_rnd() % max_nr_slots;
                    t_offset = slots_wait * tx_duration;
                    break;
                }
                case CSMA_CA_MODE_RIGD:
                {
                    dll_rigd_n++;
                    dll_slot_duration = (uint16_t) ((double)dll_tca0) / (2 << (dll_rigd_n+1));
                    if(dll_slot_duration != 0) // TODO can be 0, validate
                        t_offset = get_rnd() % dll_slot_duration;
                    else
                        t_offset = 0;

                    DPRINT("slot duration: %i", dll_slot_duration);
                    break;
                }
            }

            DPRINT("t_offset: %i", t_offset);

            dll_to = dll_tca - t_offset;

            if (t_offset > 0)
            {
                timer_post_task_delay(&execute_csma_ca, t_offset);
            }
            else
            {
                switch_state(DLL_STATE_CCA1);
                sched_post_task(&execute_cca);
            }

            break;
        }
        case DLL_STATE_CCA_FAIL:
        {
            // TODO hw_radio_set_idle();
            switch_state(DLL_STATE_IDLE);
            d7anp_signal_packet_csma_ca_insertion_completed(false);
            if (process_received_packets_after_tx)
            {
                sched_post_task(&process_received_packets);
                process_received_packets_after_tx = false;
            }

            if (resume_fg_scan)
            {
                switch_state(DLL_STATE_FOREGROUND_SCAN);

                hw_rx_cfg_t rx_cfg = (hw_rx_cfg_t){
                    .channel_id.channel_header = current_access_profile->subbands[0].channel_header,
                    .channel_id.center_freq_index = current_access_profile->subbands[0].channel_index_start,
                    .syncword_class = PHY_SYNCWORD_CLASS1,
                };

                hw_radio_set_rx(&rx_cfg, &packet_received, NULL);
                resume_fg_scan = false;
            }
            break;
        }
    }
}

void dll_execute_scan_automation()
{
    uint8_t scan_access_class = fs_read_dll_conf_active_access_class();
    if(active_access_class != scan_access_class)
    {
        fs_read_access_class(scan_access_class, &scan_access_profile);
        active_access_class = scan_access_class;
    }

    current_access_profile = &scan_access_profile;

    if(current_access_profile->control_scan_type_is_foreground && current_access_profile->control_number_of_subbands > 0) // TODO background scan
    {
        assert(current_access_profile->control_number_of_subbands == 1); // TODO multiple not supported
        switch_state(DLL_STATE_SCAN_AUTOMATION);
        hw_rx_cfg_t rx_cfg = {
            .channel_id = {
                .channel_header = current_access_profile->subbands[0].channel_header,
                .center_freq_index = current_access_profile->subbands[0].channel_index_start
            },
            .syncword_class = PHY_SYNCWORD_CLASS1
        };
static void flush_fifos()
{
    assert(d7asp_state == D7ASP_STATE_MASTER);
    DPRINT("Flushing FIFOs");
    hw_watchdog_feed(); // TODO do here?

    if(current_request_id == NO_ACTIVE_REQUEST_ID)
    {
        // find first request which is not acked or dropped
        int8_t found_next_req_index = bitmap_search(current_master_session.progress_bitmap, false, MODULE_D7AP_FIFO_MAX_REQUESTS_COUNT);
        if(found_next_req_index == -1 || found_next_req_index == current_master_session.next_request_id)
        {
            // we handled all requests ...
            flush_completed();

            // TODO move callback to ALP?
//            if(d7asp_init_args != NULL && d7asp_init_args->d7asp_fifo_flush_completed_cb != NULL)
//                d7asp_init_args->d7asp_fifo_flush_completed_cb(fifo.token, fifo.progress_bitmap, fifo.success_bitmap, REQUESTS_BITMAP_BYTE_COUNT);

            return;
        }

        current_request_id = found_next_req_index;
        current_request_retry_count = 0;

        current_request_packet = packet_queue_alloc_packet();
        packet_queue_mark_processing(current_request_packet);
        current_request_packet->d7anp_addressee = &(current_master_session.config.addressee); // TODO explicitly pass addressee down the stack layers?

        memcpy(current_request_packet->payload, current_master_session.request_buffer + current_master_session.requests_indices[current_request_id], current_master_session.requests_lengths[current_request_id]);
        current_request_packet->payload_length = current_master_session.requests_lengths[current_request_id];

        // TODO calculate Tl and Tc
        // Tc(NB, LEN, CH) = (SFC  * NB  + 1) * TTX(CH, LEN) + TG with NB the number of concurrent devices and SF the collision Avoidance Spreading Factor
        // Tl should correspond to the maximum time needed to send the remaining requests in the FIFO including the RETRY parameter

        // For now, set Tc and Tl according the transmission_timeout_period set in the access profile
        dae_access_profile_t active_addressee_access_profile;
        fs_read_access_class(current_request_packet->d7anp_addressee->ctrl.access_class, &active_addressee_access_profile);
        current_request_packet->d7atp_tc = active_addressee_access_profile.transmission_timeout_period;
        current_request_packet->d7anp_listen_timeout = active_addressee_access_profile.transmission_timeout_period;
    }
    else
    {
        // retrying request ...
        DPRINT("Current request retry count: %i", current_request_retry_count);
        if(current_request_retry_count == single_request_retry_limit)
        {
            // mark request as failed and pop
            mark_current_request_done();
            DPRINT("Request reached single request retry limit (%i), skipping request", single_request_retry_limit);
            packet_queue_free_packet(current_request_packet);
            current_request_id = NO_ACTIVE_REQUEST_ID;
            sched_post_task(&flush_fifos); // continue flushing until all request handled ...
            return;
        }

        // TODO stop on error
    }

    // TODO calculate D7ANP timeout (and update during transaction lifetime) (based on Tc, channel, cs, payload size, # msgs, # retries)
    d7atp_start_dialog(current_master_session.token, current_request_id, (current_request_id == current_master_session.next_request_id - 1), current_request_packet, &current_master_session.config.qos);
}
void rssi_valid(int16_t cur_rssi)
{
    sched_post_task(&read_rssi);
}
static void packet_transmitted(hw_radio_packet_t* hw_radio_packet)
{
    assert(dll_state == DLL_STATE_TX_FOREGROUND);
    switch_state(DLL_STATE_TX_FOREGROUND_COMPLETED);
    DPRINT("Transmitted packet with length = %i", hw_radio_packet->length);
    packet_t* packet = packet_queue_find_packet(hw_radio_packet);

    d7anp_signal_packet_transmitted(packet);

    if(process_received_packets_after_tx)
    {
        sched_post_task(&process_received_packets);
        process_received_packets_after_tx = false;
    }

#ifdef RESPONDER_USE_FG_SCAN_OUTSIDE_TRANSACTION // TODO validate if still needed, if yes: needs to be tested
    /*
     * Resume the FG scan only after an unicast packet, otherwise, wait the
     * response period expiration.
     */
    if (resume_fg_scan && packet->dll_header.control_target_address_set)
    {
        switch_state(DLL_STATE_FOREGROUND_SCAN);

        hw_rx_cfg_t rx_cfg = (hw_rx_cfg_t){
            .channel_id.channel_header = current_access_profile->subbands[0].channel_header,
            .channel_id.center_freq_index = current_access_profile->subbands[0].channel_index_start,
            .syncword_class = PHY_SYNCWORD_CLASS1,
        };

        hw_radio_set_rx(&rx_cfg, &packet_received, NULL);
        resume_fg_scan = false;
    }
#else
    if (resume_fg_scan)
    {
        switch_state(DLL_STATE_FOREGROUND_SCAN);

        hw_rx_cfg_t rx_cfg = (hw_rx_cfg_t){
            .channel_id.channel_header = current_access_profile->subbands[0].channel_header,
            .channel_id.center_freq_index = current_access_profile->subbands[0].channel_index_start,
            .syncword_class = PHY_SYNCWORD_CLASS1,
        };

        hw_radio_set_rx(&rx_cfg, &packet_received, NULL);
        resume_fg_scan = false;
    }
#endif

}

static void cca_rssi_valid(int16_t cur_rssi)
{
    // When the radio goes back to Rx state, the rssi_valid callback may be still set. Skip it in this case
    if (dll_state != DLL_STATE_CCA1 && dll_state != DLL_STATE_CCA2)
        return;

    if (cur_rssi <= E_CCA)
    {
        if(dll_state == DLL_STATE_CCA1)
        {
            DPRINT("CCA1 RSSI: %d", cur_rssi);
            switch_state(DLL_STATE_CCA2);
            timer_post_task_delay(&execute_cca, 5);
            return;
        }
        else if(dll_state == DLL_STATE_CCA2)
        {
            // OK, send packet
            DPRINT("CCA2 RSSI: %d", cur_rssi);
            DPRINT("CCA2 succeeded, transmitting ...");
            // log_print_data(current_packet->hw_radio_packet.data, current_packet->hw_radio_packet.length + 1); // TODO tmp

            switch_state(DLL_STATE_TX_FOREGROUND);

            d7anp_signal_packet_csma_ca_insertion_completed(true);
            error_t err = hw_radio_send_packet(&current_packet->hw_radio_packet, &packet_transmitted);
            assert(err == SUCCESS);

            if (!resume_fg_scan)
                hw_radio_set_idle(); // ensure radio goes back to IDLE after transmission instead of to RX
            return;
        }
    }
    else
    {
        DPRINT("Channel not clear, RSSI: %i", cur_rssi);
        switch_state(DLL_STATE_CSMA_CA_RETRY);
        execute_csma_ca();

        //switch_state(DLL_STATE_CCA_FAIL);
        //d7atp_signal_packet_csma_ca_insertion_completed(false);
    }
}

static void execute_cca()
{
    assert(dll_state == DLL_STATE_CCA1 || dll_state == DLL_STATE_CCA2);

    hw_rx_cfg_t rx_cfg =(hw_rx_cfg_t){
        .channel_id.channel_header = current_access_profile->subbands[0].channel_header,
        .channel_id.center_freq_index = current_access_profile->subbands[0].channel_index_start,
        .syncword_class = PHY_SYNCWORD_CLASS1,
    };

    hw_radio_set_rx(&rx_cfg, NULL, &cca_rssi_valid);
}

static uint16_t calculate_tx_duration()
{
    int data_rate = 6; // Normal rate: 6.9 bytes/tick
    // TODO select correct subband
    switch (current_access_profile->subbands[0].channel_header.ch_class)
    {
    case PHY_CLASS_LO_RATE:
        data_rate = 1; // Lo Rate: 1.2 bytes/tick
        break;
    case PHY_CLASS_HI_RATE:
        data_rate = 20; // High rate: 20.83 byte/tick
    }

    uint16_t duration = (current_packet->hw_radio_packet.length / data_rate) + 1;
    return duration;
}
void process_command_chan()
{
    while(fifo_get_size(&uart_rx_fifo) < COMMAND_CHAN_PARAM_SIZE);

    char param[COMMAND_CHAN_PARAM_SIZE];
    fifo_pop(&uart_rx_fifo, param, COMMAND_CHAN_PARAM_SIZE);

    channel_id_t new_channel = {
		.channel_header.ch_coding = PHY_CODING_PN9
    };

    if(strncmp(param, "433", 3) == 0)
        new_channel.channel_header.ch_freq_band = PHY_BAND_433;
    else if(strncmp(param, "868", 3) == 0)
        new_channel.channel_header.ch_freq_band = PHY_BAND_868;
    else if(strncmp(param, "915", 3) == 0)
        new_channel.channel_header.ch_freq_band = PHY_BAND_915;
    else
        goto error;

    char channel_class = param[3];
    if(channel_class == 'L')
        new_channel.channel_header.ch_class = PHY_CLASS_LO_RATE;
    else if(channel_class == 'N')
        new_channel.channel_header.ch_class = PHY_CLASS_NORMAL_RATE;
    else if(channel_class == 'H')
        new_channel.channel_header.ch_class = PHY_CLASS_HI_RATE;
    else
        goto error;

    uint16_t center_freq_index = atoi((const char*)(param + 4));
    new_channel.center_freq_index = center_freq_index;

    // validate
    if(new_channel.channel_header.ch_freq_band == PHY_BAND_433)
    {
        if(new_channel.channel_header.ch_class == PHY_CLASS_NORMAL_RATE
                && (new_channel.center_freq_index % 8 != 0 || new_channel.center_freq_index > 56))
            goto error;
        else if(new_channel.channel_header.ch_class == PHY_CLASS_LO_RATE && new_channel.center_freq_index > 68)
            goto error;
        else if(new_channel.channel_header.ch_class == PHY_CLASS_HI_RATE)
            goto error;
    } // TODO validate PHY_BAND_868

    // valid band, apply ...
    rx_cfg.channel_id = new_channel;

    // change channel and restart
    prepare_channel_indexes();
    current_channel_indexes_index = 0;
    sched_post_task(&start_rx);
    return;

    error:
        console_print("Error parsing CHAN command. Expected format example: '433L001'\n");
        fifo_clear(&uart_rx_fifo);
}


void process_command_loop()
{
    use_manual_channel_switching = false;
    sched_post_task(&start_rx);
}