mirror of https://github.com/ARMmbed/mbed-os.git
1272 lines
63 KiB
C
1272 lines
63 KiB
C
/*
|
|
* Copyright (c) 2018-2021, Pelion and affiliates.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "nsconfig.h"
|
|
#include "ns_types.h"
|
|
#include "fhss_api.h"
|
|
#include "fhss_config.h"
|
|
#include "fhss.h"
|
|
#include "fhss_common.h"
|
|
#include "fhss_statistics.h"
|
|
#include "channel_list.h"
|
|
#include "channel_functions.h"
|
|
#include "fhss_ws.h"
|
|
#include "nsdynmemLIB.h"
|
|
#include "common_functions.h"
|
|
#include "eventOS_callback_timer.h"
|
|
#include "randLIB.h"
|
|
#include "ns_trace.h"
|
|
#include "platform/arm_hal_interrupt.h"
|
|
#include <string.h>
|
|
|
|
#define TRACE_GROUP "fhss"
|
|
|
|
// Enable this flag to use channel traces
|
|
// #define FHSS_CHANNEL_DEBUG
|
|
// Enable this flag to use debug callbacks
|
|
// #define FHSS_CHANNEL_DEBUG_CBS
|
|
|
|
#ifdef FHSS_CHANNEL_DEBUG_CBS
|
|
void (*fhss_uc_switch)(void) = NULL;
|
|
void (*fhss_bc_switch)(void) = NULL;
|
|
#endif /*FHSS_CHANNEL_DEBUG_CBS*/
|
|
// Seconds to milliseconds
|
|
#define S_TO_MS(x) (((int64_t)x)*1000)
|
|
// Milliseconds to seconds
|
|
#define MS_TO_S(x) divide_integer(x, 1000)
|
|
// Seconds to microseconds
|
|
#define S_TO_US(x) (((int64_t)x)*1000000)
|
|
// Microseconds to seconds
|
|
#define US_TO_S(x) divide_integer(x, 1000000)
|
|
// Milliseconds to microseconds
|
|
#define MS_TO_US(x) (((int64_t)x)*1000)
|
|
// Microseconds to milliseconds
|
|
#define US_TO_MS(x) divide_integer(x, 1000)
|
|
// Milliseconds to nanoseconds
|
|
#define MS_TO_NS(x) (((int64_t)x)*1000000)
|
|
// Nanoseconds to milliseconds
|
|
#define NS_TO_MS(x) divide_integer(x, 1000000)
|
|
// Microseconds to nanoseconds
|
|
#define US_TO_NS(x) (((int64_t)x)*1000)
|
|
// Nanoseconds to microseconds
|
|
#define NS_TO_US(x) divide_integer(x, 1000)
|
|
#define DEF_2E24 0x1000000
|
|
#define IE_HEADER_LENGTH_MASK 0x007f
|
|
#define IE_HEADER_ID_MASK 0x7f80
|
|
#define WH_IE_ID 0x2a
|
|
#define WH_SUB_ID_UTT 1
|
|
#define WH_SUB_ID_BT 2
|
|
|
|
#ifdef HAVE_WS
|
|
|
|
struct ws_ie_t {
|
|
uint8_t *content_ptr;
|
|
unsigned length: 7;
|
|
uint8_t id;
|
|
};
|
|
|
|
static int fhss_ws_manage_channel_table_allocation(fhss_structure_t *fhss_structure, uint16_t channel_count);
|
|
static void fhss_event_timer_cb(int8_t timer_id, uint16_t slots);
|
|
static void fhss_ws_update_uc_channel_callback(fhss_structure_t *fhss_structure);
|
|
static void fhss_unicast_handler(const fhss_api_t *fhss_api, uint16_t delay);
|
|
static void fhss_broadcast_handler(const fhss_api_t *fhss_api, uint16_t delay);
|
|
static bool fhss_ws_check_tx_allowed(fhss_structure_t *fhss_structure);
|
|
static bool fhss_allow_transmitting_on_rx_slot(fhss_structure_t *fhss_structure);
|
|
static bool fhss_allow_unicast_on_broadcast_channel(fhss_structure_t *fhss_structure);
|
|
static int32_t fhss_channel_index_from_mask(const uint32_t *channel_mask, int32_t channel_index, uint16_t number_of_channels);
|
|
|
|
// This function supports rounding up
|
|
static int64_t divide_integer(int64_t dividend, int32_t divisor)
|
|
{
|
|
if (!divisor) {
|
|
return dividend;
|
|
}
|
|
if (dividend < 0) {
|
|
return (dividend - divisor / 2) / divisor;
|
|
}
|
|
return (dividend + divisor / 2) / divisor;
|
|
}
|
|
|
|
static uint32_t get_remaining_slots_us(fhss_structure_t *fhss_structure, void (*callback)(const fhss_api_t *api, uint16_t), uint32_t max_timeout_us)
|
|
{
|
|
uint32_t remaining_time_us = fhss_structure->platform_functions.fhss_get_remaining_slots(callback, fhss_structure->fhss_api);
|
|
// When remaining time goes negative, use 0.
|
|
if (remaining_time_us > max_timeout_us) {
|
|
remaining_time_us = 0;
|
|
}
|
|
return remaining_time_us;
|
|
}
|
|
|
|
void fhss_ws_start_timer(fhss_structure_t *fhss_structure, uint32_t time, void (*callback)(const fhss_api_t *fhss_api, uint16_t))
|
|
{
|
|
// Number of millisecond slots in timeout(us)
|
|
int32_t time_in_ms = divide_integer(time, 1000);
|
|
// Reduce the compensation (per millisecond) from timeout.
|
|
time -= NS_TO_US(time_in_ms * fhss_structure->ws->drift_per_millisecond_ns);
|
|
fhss_start_timer(fhss_structure, time, callback);
|
|
if (callback == fhss_unicast_handler) {
|
|
fhss_structure->ws->next_uc_timeout = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api) + time;
|
|
} else if (callback == fhss_broadcast_handler) {
|
|
fhss_structure->ws->next_bc_timeout = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api) + time;
|
|
}
|
|
}
|
|
|
|
fhss_structure_t *fhss_ws_enable(fhss_api_t *fhss_api, const fhss_ws_configuration_t *fhss_configuration, const fhss_timer_t *fhss_timer)
|
|
{
|
|
if (!fhss_api || !fhss_configuration || !fhss_timer) {
|
|
tr_err("Invalid FHSS enable configuration");
|
|
return NULL;
|
|
}
|
|
int bc_channel_count = channel_list_count_channels(fhss_configuration->channel_mask);
|
|
int uc_channel_count = channel_list_count_channels(fhss_configuration->unicast_channel_mask);
|
|
|
|
if (bc_channel_count <= 0) {
|
|
// There must be at least one configured channel in channel list
|
|
return NULL;
|
|
}
|
|
fhss_structure_t *fhss_struct = fhss_allocate_instance(fhss_api, fhss_timer);
|
|
if (!fhss_struct) {
|
|
return NULL;
|
|
}
|
|
fhss_struct->ws = ns_dyn_mem_alloc(sizeof(fhss_ws_t));
|
|
if (!fhss_struct->ws) {
|
|
fhss_free_instance(fhss_api);
|
|
return NULL;
|
|
}
|
|
memset(fhss_struct->ws, 0, sizeof(fhss_ws_t));
|
|
if (fhss_ws_manage_channel_table_allocation(fhss_struct, uc_channel_count > bc_channel_count ? uc_channel_count : bc_channel_count)) {
|
|
ns_dyn_mem_free(fhss_struct->ws);
|
|
fhss_free_instance(fhss_api);
|
|
tr_error("Failed to allocate channel tables");
|
|
return NULL;
|
|
}
|
|
|
|
fhss_struct->fhss_event_timer = eventOS_callback_timer_register(fhss_event_timer_cb);
|
|
fhss_struct->ws->fhss_configuration = *fhss_configuration;
|
|
if (uc_channel_count == 0) {
|
|
//If Unicast channel is empty use Domain mask
|
|
for (uint8_t i = 0; i < 8; i++) {
|
|
fhss_struct->ws->fhss_configuration.unicast_channel_mask[i] = fhss_configuration->channel_mask[i];
|
|
}
|
|
uc_channel_count = bc_channel_count;
|
|
}
|
|
|
|
fhss_struct->number_of_channels = fhss_configuration->channel_mask_size;
|
|
fhss_struct->number_of_uc_channels = uc_channel_count;
|
|
fhss_struct->number_of_bc_channels = bc_channel_count;
|
|
fhss_struct->optimal_packet_length = OPTIMAL_PACKET_LENGTH;
|
|
fhss_ws_set_hop_count(fhss_struct, 0xff);
|
|
fhss_struct->rx_channel = fhss_configuration->unicast_fixed_channel;
|
|
fhss_struct->ws->min_synch_interval = DEFAULT_MIN_SYNCH_INTERVAL;
|
|
// By default, allow transmitting unicast data only on TX slots.
|
|
fhss_struct->ws->tx_level = WS_TX_SLOT;
|
|
// By default, allow always transmitting unicast data in expedited forwarding mode.
|
|
fhss_struct->ws->ef_tx_level = WS_TX_ALWAYS;
|
|
ns_list_init(&fhss_struct->fhss_failed_tx_list);
|
|
return fhss_struct;
|
|
}
|
|
|
|
static int fhss_ws_manage_channel_table_allocation(fhss_structure_t *fhss_structure, uint16_t channel_count)
|
|
{
|
|
// Must allocate channel table for TR51
|
|
if (!fhss_structure->ws->tr51_channel_table && !fhss_structure->ws->tr51_output_table) {
|
|
fhss_structure->ws->tr51_channel_table = ns_dyn_mem_alloc(sizeof(int16_t) * tr51_calc_nearest_prime_number(channel_count));
|
|
if (!fhss_structure->ws->tr51_channel_table) {
|
|
return -1;
|
|
}
|
|
fhss_structure->ws->tr51_output_table = ns_dyn_mem_alloc(sizeof(int16_t) * channel_count);
|
|
if (!fhss_structure->ws->tr51_output_table) {
|
|
ns_dyn_mem_free(fhss_structure->ws->tr51_channel_table);
|
|
return -1;
|
|
}
|
|
tr51_init_channel_table(fhss_structure->ws->tr51_channel_table, channel_count);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void fhss_set_txrx_slot_length(fhss_structure_t *fhss_structure)
|
|
{
|
|
// No broadcast schedule, no TX slots
|
|
if (fhss_structure->ws->fhss_configuration.fhss_broadcast_interval == 0 || fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval == 0) {
|
|
return;
|
|
}
|
|
uint32_t txrx_slot_length_ms_tmp = WS_TXRX_SLOT_LEN_MS_MAX;
|
|
if (fhss_structure->callbacks.read_datarate) {
|
|
/* Calculate minimum TX slot length which can fit optimal packet length twice.
|
|
* Twice, because 0, 1, 4, 5... hop starts transmission at the beginning of TX slot and 2, 3, 6, 7... hop at the middle of TX slot
|
|
*
|
|
* hop 0
|
|
* tx'ing | | | | | |
|
|
* | BC | RX | TX | RX | TX | RX | TX | BC |
|
|
* hop 1
|
|
* tx'ing | | | | | |
|
|
* | BC | TX | RX | TX | RX | TX | RX | BC |
|
|
* hop 2
|
|
* tx'ing | | | | | |
|
|
* | BC | RX | TX | RX | TX | RX | TX | BC |
|
|
* hop 3
|
|
* tx'ing | | | | | |
|
|
* | BC | TX | RX | TX | RX | TX | RX | BC |
|
|
*/
|
|
uint32_t datarate = fhss_structure->callbacks.read_datarate(fhss_structure->fhss_api);
|
|
if (datarate) {
|
|
txrx_slot_length_ms_tmp = ((fhss_structure->optimal_packet_length * 2) * (8000000 / datarate)) / 1000;
|
|
// Do not allow using too high TX slot length.
|
|
if (txrx_slot_length_ms_tmp > WS_TXRX_SLOT_LEN_MS_MAX) {
|
|
tr_debug("TX slot length setting too high %"PRIu32"ms, using %"PRIu32"ms", txrx_slot_length_ms_tmp, (uint32_t)WS_TXRX_SLOT_LEN_MS_MAX);
|
|
txrx_slot_length_ms_tmp = WS_TXRX_SLOT_LEN_MS_MAX;
|
|
}
|
|
// Do not allow using too low TX slot length.
|
|
if (txrx_slot_length_ms_tmp < WS_TXRX_SLOT_LEN_MS_MIN) {
|
|
tr_debug("TX slot length setting too low %"PRIu32"ms, using %"PRIu32"ms", txrx_slot_length_ms_tmp, (uint32_t)WS_TXRX_SLOT_LEN_MS_MIN);
|
|
txrx_slot_length_ms_tmp = WS_TXRX_SLOT_LEN_MS_MIN;
|
|
}
|
|
}
|
|
}
|
|
uint32_t number_of_tx_slots = ((fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) / txrx_slot_length_ms_tmp) / 2;
|
|
if (!number_of_tx_slots) {
|
|
return;
|
|
}
|
|
fhss_structure->ws->txrx_slot_length_ms = (fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) / (number_of_tx_slots * 2);
|
|
tr_info("TX slot length: %"PRIu32"ms", fhss_structure->ws->txrx_slot_length_ms);
|
|
}
|
|
|
|
static uint32_t fhss_get_number_of_tx_slots(fhss_structure_t *fhss_structure)
|
|
{
|
|
return ((fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) / fhss_structure->ws->txrx_slot_length_ms) / 2;
|
|
}
|
|
|
|
static int32_t fhss_ws_calc_bc_channel(fhss_structure_t *fhss_structure)
|
|
{
|
|
int32_t next_channel = fhss_structure->ws->fhss_configuration.broadcast_fixed_channel;
|
|
|
|
if (fhss_structure->ws->fhss_configuration.ws_bc_channel_function == WS_TR51CF) {
|
|
next_channel = tr51_get_bc_channel_index(fhss_structure->ws->tr51_channel_table, fhss_structure->ws->tr51_output_table, fhss_structure->ws->bc_slot, fhss_structure->ws->fhss_configuration.bsi, fhss_structure->number_of_bc_channels, NULL);
|
|
next_channel = fhss_channel_index_from_mask(fhss_structure->ws->fhss_configuration.channel_mask, next_channel, fhss_structure->number_of_channels);
|
|
if (++fhss_structure->ws->bc_slot == fhss_structure->number_of_bc_channels) {
|
|
fhss_structure->ws->bc_slot = 0;
|
|
}
|
|
} else if (fhss_structure->ws->fhss_configuration.ws_bc_channel_function == WS_DH1CF) {
|
|
fhss_structure->ws->bc_slot++;
|
|
next_channel = dh1cf_get_bc_channel_index(fhss_structure->ws->bc_slot, fhss_structure->ws->fhss_configuration.bsi, fhss_structure->number_of_bc_channels);
|
|
next_channel = fhss_channel_index_from_mask(fhss_structure->ws->fhss_configuration.channel_mask, next_channel, fhss_structure->number_of_channels);
|
|
} else if (fhss_structure->ws->fhss_configuration.ws_bc_channel_function == WS_VENDOR_DEF_CF) {
|
|
if (fhss_structure->ws->fhss_configuration.vendor_defined_cf) {
|
|
next_channel = fhss_structure->ws->fhss_configuration.vendor_defined_cf(fhss_structure->fhss_api, fhss_structure->ws->bc_slot, NULL, fhss_structure->ws->fhss_configuration.bsi, fhss_structure->number_of_channels);
|
|
}
|
|
}
|
|
#ifdef FHSS_CHANNEL_DEBUG
|
|
tr_info("%"PRIu32" BC %u %u %u", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api), next_channel, fhss_structure->ws->bc_slot, fhss_structure->ws->fhss_configuration.bsi);
|
|
#endif /*FHSS_CHANNEL_DEBUG*/
|
|
return next_channel;
|
|
}
|
|
|
|
static uint8_t calc_own_tx_trig_slot(uint8_t own_hop)
|
|
{
|
|
if (own_hop == 0xff) {
|
|
return 0;
|
|
}
|
|
if (own_hop & 1) {
|
|
own_hop--;
|
|
}
|
|
own_hop /= 2;
|
|
return (own_hop & 1);
|
|
}
|
|
|
|
static int32_t fhss_channel_index_from_mask(const uint32_t *channel_mask, int32_t channel_index, uint16_t number_of_channels)
|
|
{
|
|
//Function will return real active channel index at list
|
|
int32_t active_channels = 0;
|
|
// Set channel maks outside excluded channels
|
|
for (int32_t i = 0; i < number_of_channels; i++) {
|
|
if (channel_mask[i / 32] & (1U << (i % 32))) {
|
|
if (channel_index == active_channels) {
|
|
return i;
|
|
}
|
|
active_channels++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void fhss_broadcast_handler(const fhss_api_t *fhss_api, uint16_t delay)
|
|
{
|
|
(void) delay;
|
|
int32_t next_channel;
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(fhss_api);
|
|
if (!fhss_structure) {
|
|
return;
|
|
}
|
|
platform_enter_critical();
|
|
if (fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval == 0 || fhss_structure->ws->fhss_configuration.fhss_broadcast_interval == 0) {
|
|
// stop broadcast schedule
|
|
fhss_structure->ws->is_on_bc_channel = false;
|
|
fhss_structure->ws->synchronization_time = 0;
|
|
fhss_structure->ws->broadcast_timer_running = false;
|
|
platform_exit_critical();
|
|
return;
|
|
}
|
|
|
|
int32_t delay_us = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api) - fhss_structure->ws->next_bc_timeout;
|
|
// Assume this was initial call for this function without timer
|
|
if (!fhss_structure->ws->bc_slot && !fhss_structure->ws->next_bc_timeout) {
|
|
delay_us = 0;
|
|
}
|
|
|
|
if (fhss_structure->ws->is_on_bc_channel == false) {
|
|
// If callback was delayed longer than broadcast interval, BC slot must be updated
|
|
fhss_structure->ws->bc_slot += (US_TO_MS(delay_us) + (fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval)) / fhss_structure->ws->fhss_configuration.fhss_broadcast_interval;
|
|
// We weren't on broadcast channel. Check if after delayed interrupt we still shouldn't go to broadcast channel. Setting is_on_bc_channel to true forces to skip BC dwell interval next.
|
|
if ((US_TO_MS(delay_us) % fhss_structure->ws->fhss_configuration.fhss_broadcast_interval) > fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) {
|
|
fhss_structure->ws->is_on_bc_channel = true;
|
|
// If delay + assumed timeout is longer than broadcast interval, delay value must be computed differently to make compensation work properly
|
|
if (US_TO_MS(delay_us) + (fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) > fhss_structure->ws->fhss_configuration.fhss_broadcast_interval) {
|
|
delay_us = (MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) + delay_us) % MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval);
|
|
}
|
|
} else {
|
|
if (US_TO_MS(delay_us) + (fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) > fhss_structure->ws->fhss_configuration.fhss_broadcast_interval) {
|
|
delay_us = ((MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) + delay_us) % MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval)) - MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval);
|
|
}
|
|
}
|
|
} else {
|
|
// If callback was delayed longer than broadcast interval, BC slot must be updated
|
|
fhss_structure->ws->bc_slot += (US_TO_MS(delay_us) + fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) / fhss_structure->ws->fhss_configuration.fhss_broadcast_interval;
|
|
// We were on broadcast channel. Check if after delayed interrupt we should go broadcast channel again. Setting is_on_bc_channel to false forces to start BC dwell interval next.
|
|
if ((US_TO_MS(delay_us) % fhss_structure->ws->fhss_configuration.fhss_broadcast_interval) > (fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval)) {
|
|
fhss_structure->ws->is_on_bc_channel = false;
|
|
}
|
|
delay_us %= MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval);
|
|
delay_us %= MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval);
|
|
}
|
|
|
|
if (fhss_structure->ws->fhss_configuration.ws_bc_channel_function == WS_TR51CF) {
|
|
fhss_structure->ws->bc_slot %= fhss_structure->number_of_bc_channels;
|
|
}
|
|
|
|
if (fhss_structure->ws->is_on_bc_channel == false) {
|
|
fhss_ws_start_timer(fhss_structure, MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) - ((int64_t) delay_us * fhss_structure->platform_functions.fhss_resolution_divider), fhss_broadcast_handler);
|
|
fhss_structure->ws->is_on_bc_channel = true;
|
|
next_channel = fhss_structure->ws->bc_channel = fhss_ws_calc_bc_channel(fhss_structure);
|
|
if (fhss_structure->ws->expedited_forwarding_enabled_us) {
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, EXPEDITED_FORWARDING_POLL_PERIOD);
|
|
} else {
|
|
/* Start timer with random timeout to trigger broadcast TX queue poll event.
|
|
* Min random is 1/50 of the channel dwell interval.
|
|
* Max random is 3/4 of the channel dwell interval.
|
|
* Event timer resolution is 50us.
|
|
*/
|
|
uint32_t bc_dwell_us = MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval);
|
|
uint16_t bc_min_random = (bc_dwell_us / 50) / 50;
|
|
uint16_t bc_max_random = (bc_dwell_us - (bc_dwell_us / 4)) / 50;
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, randLIB_get_random_in_range(bc_min_random, bc_max_random));
|
|
}
|
|
} else {
|
|
fhss_structure->ws->unicast_start_time_us = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api);
|
|
uint32_t timeout = MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval);
|
|
fhss_ws_start_timer(fhss_structure, timeout - (delay_us * fhss_structure->platform_functions.fhss_resolution_divider), fhss_broadcast_handler);
|
|
fhss_structure->ws->is_on_bc_channel = false;
|
|
// Should return to own (unicast) listening channel after broadcast channel
|
|
next_channel = fhss_structure->rx_channel;
|
|
if (fhss_structure->ws->expedited_forwarding_enabled_us) {
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, EXPEDITED_FORWARDING_POLL_PERIOD);
|
|
} else {
|
|
/* Start timer with random timeout to trigger unicast TX queue poll event.
|
|
* For hops 0,1,4,5,8,9,...
|
|
* Min random is 1/100 of the TX slot length.
|
|
* Max random is 1/5 of the TX slot length.
|
|
*
|
|
* For hops 2,3,6,7,10,11,...
|
|
* Min random is 1/100 of the TX slot length plus 0.5*TX slot length.
|
|
* Max random is 1/10 of the TX slot length plus 0.5*TX slot length.
|
|
* Event timer resolution is 50us.
|
|
*/
|
|
// returns 1 if polling of TX queue is done on latter half of the TX slot
|
|
uint8_t own_tx_trig_slot = calc_own_tx_trig_slot(fhss_structure->own_hop);
|
|
uint32_t txrx_slot_length_us = MS_TO_US(fhss_structure->ws->txrx_slot_length_ms);
|
|
uint16_t uc_min_random = (((txrx_slot_length_us / 2) * own_tx_trig_slot) / 50) + ((txrx_slot_length_us / 100) / 50);
|
|
uint16_t uc_max_random = (((txrx_slot_length_us / 2) * own_tx_trig_slot) / 50) + ((txrx_slot_length_us / 5) / 50);
|
|
bool tx_allowed = fhss_ws_check_tx_allowed(fhss_structure);
|
|
if (!tx_allowed) {
|
|
uc_min_random += (txrx_slot_length_us) / 50;
|
|
uc_max_random += (txrx_slot_length_us) / 50;
|
|
}
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, randLIB_get_random_in_range(uc_min_random, uc_max_random));
|
|
}
|
|
|
|
#ifdef FHSS_CHANNEL_DEBUG
|
|
tr_info("%"PRIu32" UC %u", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api), fhss_structure->rx_channel);
|
|
#endif /*FHSS_CHANNEL_DEBUG*/
|
|
#ifdef TIMING_TOOL_TRACES
|
|
tr_info("%u UC_change %u", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api), next_channel);
|
|
#endif
|
|
}
|
|
fhss_structure->callbacks.change_channel(fhss_structure->fhss_api, next_channel);
|
|
#ifdef FHSS_CHANNEL_DEBUG_CBS
|
|
if (fhss_bc_switch) {
|
|
fhss_bc_switch();
|
|
}
|
|
#endif /*FHSS_CHANNEL_DEBUG_CBS*/
|
|
#ifdef TIMING_TOOL_TRACES
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
tr_info("%u BC_start %u", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api), next_channel);
|
|
} else {
|
|
tr_info("%u BC_done", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api));
|
|
}
|
|
#endif
|
|
platform_exit_critical();
|
|
}
|
|
|
|
static int own_floor(float value)
|
|
{
|
|
return (int)value;
|
|
}
|
|
|
|
static int own_ceil(float value)
|
|
{
|
|
int ivalue = (int)value;
|
|
if (value == (float)ivalue) {
|
|
return ivalue;
|
|
}
|
|
return ivalue + 1;
|
|
}
|
|
|
|
static void fhss_event_timer_cb(int8_t timer_id, uint16_t slots)
|
|
{
|
|
(void) slots;
|
|
uint16_t queue_size = 0;
|
|
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_timer_id(timer_id);
|
|
if (!fhss_structure) {
|
|
return;
|
|
}
|
|
|
|
if (fhss_structure->ws->expedited_forwarding_enabled_us) {
|
|
if ((fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api) - fhss_structure->ws->expedited_forwarding_enabled_us) > S_TO_US(EXPEDITED_FORWARDING_PERIOD)) {
|
|
fhss_structure->ws->expedited_forwarding_enabled_us = 0;
|
|
}
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, EXPEDITED_FORWARDING_POLL_PERIOD);
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
queue_size = fhss_structure->callbacks.read_tx_queue_size(fhss_structure->fhss_api, true);
|
|
} else {
|
|
queue_size = fhss_structure->callbacks.read_tx_queue_size(fhss_structure->fhss_api, false);
|
|
}
|
|
} else if ((fhss_structure->ws->unicast_timer_running == false) && (fhss_structure->ws->broadcast_timer_running == false)) {
|
|
// No one would poll TX queue if schedule timers were not started. Start poll timer with default interval.
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, DEFAULT_POLL_PERIOD);
|
|
queue_size = fhss_structure->callbacks.read_tx_queue_size(fhss_structure->fhss_api, false);
|
|
} else {
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
queue_size = fhss_structure->callbacks.read_tx_queue_size(fhss_structure->fhss_api, true);
|
|
} else {
|
|
// On unicast, start timer to trigger polling event on next TX slot
|
|
uint32_t delay_between_tx_slots_us = MS_TO_US(fhss_structure->ws->txrx_slot_length_ms) * 2;
|
|
// Timer could drift to RX slot when broadcast interval is high. Return timer to TX slot.
|
|
if (fhss_ws_check_tx_allowed(fhss_structure) == false) {
|
|
delay_between_tx_slots_us -= MS_TO_US(fhss_structure->ws->txrx_slot_length_ms - (calc_own_tx_trig_slot(fhss_structure->own_hop) * (fhss_structure->ws->txrx_slot_length_ms / 2)));
|
|
}
|
|
if (delay_between_tx_slots_us < get_remaining_slots_us(fhss_structure, fhss_broadcast_handler, MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval))) {
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, delay_between_tx_slots_us / 50);
|
|
}
|
|
queue_size = fhss_structure->callbacks.read_tx_queue_size(fhss_structure->fhss_api, false);
|
|
}
|
|
}
|
|
if (queue_size) {
|
|
fhss_structure->callbacks.tx_poll(fhss_structure->fhss_api);
|
|
#ifdef TIMING_TOOL_TRACES
|
|
tr_info("%u TX_poll", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static uint32_t fhss_ws_calculate_ufsi(fhss_structure_t *fhss_structure, uint32_t tx_time)
|
|
{
|
|
uint8_t dwell_time = fhss_structure->ws->fhss_configuration.fhss_uc_dwell_interval;
|
|
uint16_t cur_slot = fhss_structure->ws->uc_slot;
|
|
if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_TR51CF) {
|
|
if (cur_slot == 0) {
|
|
cur_slot = fhss_structure->number_of_uc_channels;
|
|
}
|
|
}
|
|
cur_slot--;
|
|
|
|
uint32_t time_to_tx = 0;
|
|
uint32_t cur_time = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api);
|
|
// High time to TX value (1000ms) is because actual TX time already passed.
|
|
if (US_TO_MS(tx_time - cur_time) < 1000) {
|
|
time_to_tx = US_TO_MS(tx_time - cur_time);
|
|
}
|
|
uint64_t ms_since_seq_start;
|
|
if (fhss_structure->ws->unicast_timer_running == true) {
|
|
// Allow timer interrupt to delay max 10 seconds, otherwise assume next_uc_timeout overflowed
|
|
if ((fhss_structure->ws->next_uc_timeout < cur_time) && ((cur_time - fhss_structure->ws->next_uc_timeout) < 10000000)) {
|
|
// The unicast timer has already expired, so count all previous slots
|
|
// plus 1 completed slot
|
|
// plus the time from timer expiration to now
|
|
// plus the time until Tx
|
|
ms_since_seq_start = ((cur_slot + 1) * dwell_time) + US_TO_MS(cur_time - fhss_structure->ws->next_uc_timeout) + time_to_tx;
|
|
} else {
|
|
// The unicast timer is still running, so count all previous slots
|
|
// plus the remaining time in the slot
|
|
// plus the time until Tx
|
|
uint32_t remaining_time_ms = US_TO_MS(fhss_structure->ws->next_uc_timeout - cur_time);
|
|
ms_since_seq_start = (cur_slot * dwell_time) + (dwell_time - remaining_time_ms) + time_to_tx;
|
|
}
|
|
} else {
|
|
// The unicast timer is not running. Act as if the slot has completed.
|
|
// count all previous slots
|
|
// plus 1 completed slot
|
|
// plus the time until Tx
|
|
ms_since_seq_start = ((cur_slot + 1) * dwell_time) + time_to_tx;
|
|
}
|
|
|
|
uint32_t seq_length = 0x10000;
|
|
if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_TR51CF) {
|
|
ms_since_seq_start %= (dwell_time * fhss_structure->number_of_uc_channels);
|
|
seq_length = fhss_structure->number_of_uc_channels;
|
|
}
|
|
return own_floor((float)(ms_since_seq_start * DEF_2E24) / (seq_length * dwell_time));
|
|
}
|
|
|
|
static uint32_t fhss_ws_calculate_broadcast_interval_offset(fhss_structure_t *fhss_structure, uint32_t tx_time)
|
|
{
|
|
uint8_t dwell_time = fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval;
|
|
uint32_t broadcast_interval = fhss_structure->ws->fhss_configuration.fhss_broadcast_interval;
|
|
uint32_t remaining_time_ms = US_TO_MS(get_remaining_slots_us(fhss_structure, fhss_broadcast_handler, MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval)));
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
remaining_time_ms += (broadcast_interval - dwell_time);
|
|
}
|
|
uint32_t time_to_tx = 0;
|
|
uint32_t cur_time = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api);
|
|
if (cur_time < tx_time) {
|
|
time_to_tx = US_TO_MS(tx_time - cur_time);
|
|
}
|
|
return (broadcast_interval - remaining_time_ms) + time_to_tx;
|
|
}
|
|
|
|
static uint16_t fhss_ws_calculate_destination_slot(fhss_ws_neighbor_timing_info_t *neighbor_timing_info, uint32_t tx_time)
|
|
{
|
|
uint_fast24_t ufsi = neighbor_timing_info->uc_timing_info.ufsi;
|
|
uint32_t ufsi_timestamp = neighbor_timing_info->uc_timing_info.utt_rx_timestamp;
|
|
uint8_t dwell_time = neighbor_timing_info->uc_timing_info.unicast_dwell_interval;
|
|
uint32_t seq_length = 0x10000;
|
|
if (neighbor_timing_info->uc_timing_info.unicast_channel_function == WS_TR51CF) {
|
|
seq_length = neighbor_timing_info->uc_timing_info.unicast_number_of_channels;
|
|
}
|
|
uint32_t dest_ms_since_seq_start = own_ceil((float)((uint64_t)ufsi * seq_length * dwell_time) / DEF_2E24);
|
|
return (own_floor(((float)(US_TO_MS(tx_time - ufsi_timestamp) + dest_ms_since_seq_start) / dwell_time)) % seq_length);
|
|
}
|
|
|
|
static uint32_t fhss_ws_get_sf_timeout_callback(fhss_structure_t *fhss_structure)
|
|
{
|
|
return MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_uc_dwell_interval);
|
|
}
|
|
|
|
static int16_t fhss_ws_synch_state_set_callback(const fhss_api_t *api, fhss_states fhss_state, uint16_t pan_id)
|
|
{
|
|
(void) pan_id;
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return -1;
|
|
}
|
|
if (fhss_state == FHSS_EXPEDITED_FORWARDING) {
|
|
if (!fhss_structure->ws->expedited_forwarding_enabled_us) {
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, EXPEDITED_FORWARDING_POLL_PERIOD);
|
|
}
|
|
fhss_structure->ws->expedited_forwarding_enabled_us = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api);
|
|
if (!fhss_structure->ws->expedited_forwarding_enabled_us) {
|
|
fhss_structure->ws->expedited_forwarding_enabled_us++;
|
|
}
|
|
return 0;
|
|
}
|
|
if (fhss_state == FHSS_SYNCHRONIZED) {
|
|
uint32_t fhss_broadcast_interval = fhss_structure->ws->fhss_configuration.fhss_broadcast_interval;
|
|
uint8_t fhss_bc_dwell_interval = fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval;
|
|
fhss_set_txrx_slot_length(fhss_structure);
|
|
// Start broadcast schedule when BC intervals are known
|
|
if (fhss_broadcast_interval && fhss_bc_dwell_interval) {
|
|
fhss_broadcast_handler(fhss_structure->fhss_api, 0);
|
|
fhss_structure->ws->broadcast_timer_running = true;
|
|
}
|
|
// Start unicast schedule
|
|
if ((fhss_structure->ws->fhss_configuration.ws_uc_channel_function != WS_FIXED_CHANNEL)) {
|
|
fhss_ws_update_uc_channel_callback(fhss_structure);
|
|
fhss_ws_start_timer(fhss_structure, MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_uc_dwell_interval), fhss_unicast_handler);
|
|
fhss_structure->ws->unicast_timer_running = true;
|
|
}
|
|
} else if (fhss_state == FHSS_UNSYNCHRONIZED) {
|
|
fhss_structure->ws->synchronization_time = 0;
|
|
eventOS_callback_timer_stop(fhss_structure->fhss_event_timer);
|
|
fhss_stop_timer(fhss_structure, fhss_unicast_handler);
|
|
fhss_stop_timer(fhss_structure, fhss_broadcast_handler);
|
|
fhss_structure->ws->broadcast_timer_running = false;
|
|
fhss_structure->ws->is_on_bc_channel = false;
|
|
fhss_structure->ws->synchronization_time = 0;
|
|
}
|
|
|
|
fhss_structure->fhss_state = fhss_state;
|
|
int16_t current_channel = fhss_structure->rx_channel;
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
current_channel = fhss_structure->ws->bc_channel;
|
|
}
|
|
return current_channel;
|
|
}
|
|
|
|
static void fhss_ws_update_uc_channel_callback(fhss_structure_t *fhss_structure)
|
|
{
|
|
uint8_t mac_address[8];
|
|
int32_t next_channel = fhss_structure->rx_channel;
|
|
fhss_structure->callbacks.read_mac_address(fhss_structure->fhss_api, mac_address);
|
|
if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_FIXED_CHANNEL) {
|
|
return;
|
|
} else if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_TR51CF) {
|
|
next_channel = tr51_get_uc_channel_index(fhss_structure->ws->tr51_channel_table, fhss_structure->ws->tr51_output_table, fhss_structure->ws->uc_slot, mac_address, fhss_structure->number_of_uc_channels, NULL);
|
|
next_channel = fhss_structure->rx_channel = fhss_channel_index_from_mask(fhss_structure->ws->fhss_configuration.unicast_channel_mask, next_channel, fhss_structure->number_of_channels);
|
|
if (++fhss_structure->ws->uc_slot == fhss_structure->number_of_uc_channels) {
|
|
fhss_structure->ws->uc_slot = 0;
|
|
}
|
|
} else if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_DH1CF) {
|
|
next_channel = dh1cf_get_uc_channel_index(fhss_structure->ws->uc_slot, mac_address, fhss_structure->number_of_uc_channels);
|
|
next_channel = fhss_structure->rx_channel = fhss_channel_index_from_mask(fhss_structure->ws->fhss_configuration.unicast_channel_mask, next_channel, fhss_structure->number_of_channels);
|
|
fhss_structure->ws->uc_slot++;
|
|
} else if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_VENDOR_DEF_CF) {
|
|
if (fhss_structure->ws->fhss_configuration.vendor_defined_cf) {
|
|
next_channel = fhss_structure->rx_channel = fhss_structure->ws->fhss_configuration.vendor_defined_cf(fhss_structure->fhss_api, fhss_structure->ws->bc_slot, mac_address, fhss_structure->ws->fhss_configuration.bsi, fhss_structure->number_of_uc_channels);
|
|
}
|
|
}
|
|
// Do not switch unicast channel when broadcast channel is active.
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
return;
|
|
}
|
|
#ifdef FHSS_CHANNEL_DEBUG
|
|
tr_info("%"PRIu32" UC %u %u", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api), next_channel, fhss_structure->ws->uc_slot);
|
|
#endif /*FHSS_CHANNEL_DEBUG*/
|
|
#ifdef TIMING_TOOL_TRACES
|
|
tr_info("%u UC_change %u", fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api), next_channel);
|
|
#endif
|
|
fhss_structure->callbacks.change_channel(fhss_structure->fhss_api, next_channel);
|
|
#ifdef FHSS_CHANNEL_DEBUG_CBS
|
|
if (fhss_uc_switch) {
|
|
fhss_uc_switch();
|
|
}
|
|
#endif /*FHSS_CHANNEL_DEBUG_CBS*/
|
|
}
|
|
|
|
static int fhss_ws_tx_handle_callback(const fhss_api_t *api, bool is_broadcast_addr, uint8_t *destination_address, int frame_type, uint16_t frame_length, uint8_t phy_header_length, uint8_t phy_tail_length, uint32_t tx_time)
|
|
{
|
|
(void) frame_type;
|
|
(void) frame_length;
|
|
(void) phy_header_length;
|
|
(void) phy_tail_length;
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return -1;
|
|
}
|
|
// Allow broadcast destination on broadcast channel
|
|
if (is_broadcast_addr && (fhss_structure->ws->is_on_bc_channel == true)) {
|
|
return 0;
|
|
}
|
|
// Do not allow broadcast destination on unicast channel
|
|
if (is_broadcast_addr && (fhss_structure->ws->is_on_bc_channel == false)) {
|
|
return -3;
|
|
}
|
|
// Do not allow unicast destination on broadcast channel unless it is specifically enabled
|
|
if (!is_broadcast_addr && (fhss_structure->ws->is_on_bc_channel == true)) {
|
|
if (fhss_allow_unicast_on_broadcast_channel(fhss_structure)) {
|
|
return 0;
|
|
}
|
|
return -3;
|
|
}
|
|
// Check TX/RX slot
|
|
if (!fhss_allow_transmitting_on_rx_slot(fhss_structure) && !fhss_ws_check_tx_allowed(fhss_structure)) {
|
|
return -1;
|
|
}
|
|
if (fhss_structure->fhss_state == FHSS_SYNCHRONIZED) {
|
|
fhss_ws_neighbor_timing_info_t *neighbor_timing_info = fhss_structure->ws->get_neighbor_info(api, destination_address);
|
|
if (!neighbor_timing_info || neighbor_timing_info->uc_timing_info.unicast_number_of_channels == 0) {
|
|
fhss_stats_update(fhss_structure, STATS_FHSS_UNKNOWN_NEIGHBOR, 1);
|
|
return -2;
|
|
}
|
|
|
|
uint16_t destination_slot = fhss_ws_calculate_destination_slot(neighbor_timing_info, tx_time);
|
|
int32_t tx_channel = neighbor_timing_info->uc_timing_info.fixed_channel;
|
|
if (neighbor_timing_info->uc_timing_info.unicast_channel_function == WS_TR51CF) {
|
|
tx_channel = tr51_get_uc_channel_index(fhss_structure->ws->tr51_channel_table, fhss_structure->ws->tr51_output_table, destination_slot, destination_address, neighbor_timing_info->uc_timing_info.unicast_number_of_channels, NULL);
|
|
tx_channel = fhss_channel_index_from_mask(neighbor_timing_info->uc_channel_list.channel_mask, tx_channel, fhss_structure->number_of_channels);
|
|
} else if (neighbor_timing_info->uc_timing_info.unicast_channel_function == WS_DH1CF) {
|
|
tx_channel = dh1cf_get_uc_channel_index(destination_slot, destination_address, neighbor_timing_info->uc_channel_list.channel_count);
|
|
tx_channel = fhss_channel_index_from_mask(neighbor_timing_info->uc_channel_list.channel_mask, tx_channel, fhss_structure->number_of_channels);
|
|
} else if (neighbor_timing_info->uc_timing_info.unicast_channel_function == WS_VENDOR_DEF_CF) {
|
|
if (fhss_structure->ws->fhss_configuration.vendor_defined_cf) {
|
|
tx_channel = fhss_structure->ws->fhss_configuration.vendor_defined_cf(fhss_structure->fhss_api, fhss_structure->ws->bc_slot, destination_address, fhss_structure->ws->fhss_configuration.bsi, neighbor_timing_info->uc_timing_info.unicast_number_of_channels);
|
|
} else {
|
|
tr_err("FHSS: vendor defined configuration failed");
|
|
return -1;
|
|
}
|
|
}
|
|
#ifdef FHSS_CHANNEL_DEBUG
|
|
tr_debug("TX channel: %u %u %s", tx_channel, destination_slot + 1, trace_array(destination_address, 8));
|
|
#endif /*FHSS_CHANNEL_DEBUG*/
|
|
fhss_structure->callbacks.change_channel(fhss_structure->fhss_api, tx_channel);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool fhss_check_bad_channel(fhss_structure_t *fhss_structure, uint8_t handle)
|
|
{
|
|
// When operating on fixed channel, we must ignore the bad channel check.
|
|
if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_FIXED_CHANNEL) {
|
|
return true;
|
|
}
|
|
fhss_failed_tx_t *failed_tx = fhss_failed_handle_find(fhss_structure, handle);
|
|
if (!failed_tx) {
|
|
return true;
|
|
}
|
|
if (failed_tx->bad_channel == fhss_structure->rx_channel) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool fhss_ws_check_tx_allowed(fhss_structure_t *fhss_structure)
|
|
{
|
|
// Ignore TX/RX slots
|
|
if (fhss_structure->own_hop == 0xff) {
|
|
return true;
|
|
}
|
|
// Currently on broadcast channel
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
return true;
|
|
}
|
|
uint32_t number_of_tx_slots = fhss_get_number_of_tx_slots(fhss_structure);
|
|
// Allow transmission when broadcast interval is very short comparing to MAX slot length
|
|
if (!number_of_tx_slots) {
|
|
return true;
|
|
}
|
|
|
|
uint32_t remaining_time_ms = get_remaining_slots_us(fhss_structure, fhss_broadcast_handler, MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval)) / 1000;
|
|
uint32_t tx_slot_begin_ms = (fhss_structure->ws->fhss_configuration.fhss_broadcast_interval - fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) - (fhss_structure->ws->txrx_slot_length_ms * (fhss_structure->own_hop & 1));
|
|
/* Return false when our first TX slot has not yet started
|
|
* |------remaining_time_ms---|
|
|
* |
|
|
* v
|
|
* | BC | RX | TX | RX | TX | RX | TX | BC |
|
|
*/
|
|
if (tx_slot_begin_ms < remaining_time_ms) {
|
|
return false;
|
|
}
|
|
tx_slot_begin_ms = tx_slot_begin_ms - (((tx_slot_begin_ms - remaining_time_ms) / (2 * fhss_structure->ws->txrx_slot_length_ms)) * (2 * fhss_structure->ws->txrx_slot_length_ms));
|
|
uint32_t rx_slot_begin_ms = tx_slot_begin_ms - fhss_structure->ws->txrx_slot_length_ms;
|
|
/* Check if we are currently on TX slot.
|
|
* | | |
|
|
* v v v
|
|
* | BC | RX | TX | RX | TX | RX | TX | BC |
|
|
*/
|
|
if ((remaining_time_ms <= tx_slot_begin_ms) && (remaining_time_ms > rx_slot_begin_ms)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool fhss_ws_check_tx_time(fhss_structure_t *fhss_structure, uint16_t tx_length, uint8_t phy_header_length, uint8_t phy_tail_length)
|
|
{
|
|
/*
|
|
* Check if there is enough time for transmitting before the next multicast slot.
|
|
*/
|
|
#ifdef FHSS_WS_PROTECT_MC_SLOTS
|
|
if (!fhss_structure->ws->fhss_configuration.fhss_broadcast_interval || !fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) {
|
|
return true;
|
|
}
|
|
uint32_t tx_time = fhss_get_tx_time(fhss_structure, tx_length, phy_header_length, phy_tail_length);
|
|
uint32_t time_to_bc_channel_us = get_remaining_slots_us(fhss_structure, fhss_broadcast_handler, MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval));
|
|
if (tx_time > time_to_bc_channel_us) {
|
|
return false;
|
|
}
|
|
return true;
|
|
#else
|
|
(void) fhss_structure;
|
|
(void) tx_length;
|
|
(void) phy_header_length;
|
|
(void) phy_tail_length;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static bool fhss_allow_transmitting_on_rx_slot(fhss_structure_t *fhss_structure)
|
|
{
|
|
if (fhss_structure->ws->tx_level >= WS_TX_AND_RX_SLOT) {
|
|
return true;
|
|
}
|
|
// This is allowed only for devices in expedited forwarding mode
|
|
if (fhss_structure->ws->expedited_forwarding_enabled_us && (fhss_structure->ws->ef_tx_level >= WS_TX_AND_RX_SLOT)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool fhss_allow_unicast_on_broadcast_channel(fhss_structure_t *fhss_structure)
|
|
{
|
|
if (fhss_structure->ws->tx_level >= WS_TX_ALWAYS) {
|
|
return true;
|
|
}
|
|
// This is allowed only for devices in expedited forwarding mode
|
|
if (fhss_structure->ws->expedited_forwarding_enabled_us && (fhss_structure->ws->ef_tx_level >= WS_TX_ALWAYS)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool fhss_ws_check_tx_conditions_callback(const fhss_api_t *api, bool is_broadcast_addr, uint8_t handle, int frame_type, uint16_t frame_length, uint8_t phy_header_length, uint8_t phy_tail_length)
|
|
{
|
|
(void) frame_type;
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return true;
|
|
}
|
|
// Do not allow broadcast destination on unicast channel
|
|
if (is_broadcast_addr && (fhss_structure->ws->is_on_bc_channel == false)) {
|
|
return false;
|
|
}
|
|
// Do not allow unicast destination on broadcast channel unless it is specifically enabled
|
|
if (!is_broadcast_addr && (fhss_structure->ws->is_on_bc_channel == true) && !fhss_allow_unicast_on_broadcast_channel(fhss_structure)) {
|
|
return false;
|
|
}
|
|
// This condition will check that message is not sent on bad channel
|
|
if (fhss_check_bad_channel(fhss_structure, handle) == false) {
|
|
return false;
|
|
}
|
|
// Check that there is enough unicast TX time before next broadcast channel. We try to avoid delaying the change to broadcast channel because of ongoing transmission.
|
|
if (!is_broadcast_addr && !fhss_ws_check_tx_time(fhss_structure, frame_length, phy_header_length, phy_tail_length)) {
|
|
return false;
|
|
}
|
|
// Check TX/RX slot for unicast frames
|
|
if (!is_broadcast_addr && !fhss_allow_transmitting_on_rx_slot(fhss_structure) && !fhss_ws_check_tx_allowed(fhss_structure)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static uint8_t fhss_ws_ie_header_discover(uint8_t *header_ptr, uint16_t length, struct ws_ie_t *header_ie, uint8_t sub_id)
|
|
{
|
|
struct ws_ie_t ie_element;
|
|
uint8_t *sub_id_ptr;
|
|
uint16_t ie_dummy;
|
|
while (length > 2) {
|
|
ie_dummy = common_read_16_bit_inverse(header_ptr);
|
|
ie_element.length = (ie_dummy & IE_HEADER_LENGTH_MASK);
|
|
ie_element.id = ((ie_dummy & IE_HEADER_ID_MASK) >> 7);
|
|
ie_element.content_ptr = header_ptr + 2;
|
|
sub_id_ptr = ie_element.content_ptr;
|
|
if (ie_element.length && (header_ie->id == ie_element.id) && (*sub_id_ptr == sub_id)) {
|
|
sub_id_ptr++;
|
|
ie_element.length--;
|
|
header_ie->content_ptr = sub_id_ptr;
|
|
header_ie->length = ie_element.length;
|
|
return ie_element.length;
|
|
}
|
|
length -= ie_element.length + 2;
|
|
header_ptr += ie_element.length + 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int16_t fhss_ws_write_synch_info_callback(const fhss_api_t *api, uint8_t *ptr, uint8_t length, int frame_type, uint32_t tx_time)
|
|
{
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure || !ptr || (frame_type != FHSS_DATA_FRAME)) {
|
|
return -1;
|
|
}
|
|
platform_enter_critical();
|
|
struct ws_ie_t header_ie;
|
|
header_ie.id = WH_IE_ID;
|
|
if (fhss_ws_ie_header_discover(ptr, length, &header_ie, WH_SUB_ID_UTT)) {
|
|
uint32_t ufsi = fhss_ws_calculate_ufsi(fhss_structure, tx_time);
|
|
common_write_24_bit_inverse(ufsi, header_ie.content_ptr + 1);
|
|
}
|
|
if (fhss_ws_ie_header_discover(ptr, length, &header_ie, WH_SUB_ID_BT)) {
|
|
uint32_t broadcast_interval_offset = fhss_ws_calculate_broadcast_interval_offset(fhss_structure, tx_time);
|
|
common_write_16_bit_inverse(fhss_structure->ws->bc_slot, header_ie.content_ptr);
|
|
common_write_24_bit_inverse(broadcast_interval_offset, header_ie.content_ptr + 2);
|
|
}
|
|
platform_exit_critical();
|
|
//TODO return destination channel here
|
|
return fhss_structure->rx_channel;
|
|
}
|
|
|
|
static void fhss_ws_data_tx_done_callback(const fhss_api_t *api, bool waiting_ack, bool tx_completed, uint8_t handle)
|
|
{
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return;
|
|
}
|
|
if (fhss_structure->fhss_state == FHSS_SYNCHRONIZED) {
|
|
if (waiting_ack == false) {
|
|
if (fhss_structure->ws->is_on_bc_channel == false) {
|
|
fhss_structure->callbacks.change_channel(fhss_structure->fhss_api, fhss_structure->rx_channel);
|
|
} else {
|
|
fhss_structure->callbacks.change_channel(fhss_structure->fhss_api, fhss_structure->ws->bc_channel);
|
|
}
|
|
}
|
|
}
|
|
// Buffer was successfully transmitted. Remove stored failure handle if exists.
|
|
if (tx_completed == true) {
|
|
fhss_failed_tx_t *fhss_failed_tx = fhss_failed_handle_find(fhss_structure, handle);
|
|
if (fhss_failed_tx) {
|
|
fhss_failed_handle_remove(fhss_structure, handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool fhss_ws_data_tx_fail_callback(const fhss_api_t *api, uint8_t handle, int frame_type, uint8_t channel)
|
|
{
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return false;
|
|
}
|
|
// Only use channel retries when device is synchronized
|
|
if (fhss_structure->fhss_state == FHSS_UNSYNCHRONIZED) {
|
|
return false;
|
|
}
|
|
// Use channel retries only for data frames
|
|
if (FHSS_DATA_FRAME != frame_type) {
|
|
return false;
|
|
}
|
|
// Channel retries are disabled
|
|
if (!fhss_structure->ws->fhss_configuration.config_parameters.number_of_channel_retries) {
|
|
return false;
|
|
}
|
|
fhss_failed_tx_t *fhss_failed_tx = fhss_failed_handle_find(fhss_structure, handle);
|
|
if (fhss_failed_tx) {
|
|
fhss_failed_tx->retries_done++;
|
|
if (fhss_failed_tx->retries_done >= fhss_structure->ws->fhss_configuration.config_parameters.number_of_channel_retries) {
|
|
// No more retries. Return false to stop retransmitting.
|
|
fhss_failed_handle_remove(fhss_structure, handle);
|
|
return false;
|
|
}
|
|
fhss_failed_tx->bad_channel = channel;
|
|
} else {
|
|
// Create new failure handle and return true to retransmit
|
|
fhss_failed_handle_add(fhss_structure, handle, fhss_structure->rx_channel);
|
|
}
|
|
fhss_stats_update(fhss_structure, STATS_FHSS_CHANNEL_RETRY, 1);
|
|
return true;
|
|
}
|
|
|
|
static void fhss_ws_receive_frame_callback(const fhss_api_t *api, uint16_t pan_id, uint8_t *source_address, uint32_t timestamp, uint8_t *synch_info, int frame_type)
|
|
{
|
|
(void) api;
|
|
(void) pan_id;
|
|
(void) source_address;
|
|
(void) timestamp;
|
|
(void) synch_info;
|
|
(void) frame_type;
|
|
}
|
|
|
|
static bool fhss_ws_is_broadcast_channel_callback(const fhss_api_t *api)
|
|
{
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return true;
|
|
}
|
|
return fhss_structure->ws->is_on_bc_channel;
|
|
}
|
|
|
|
static bool fhss_ws_use_broadcast_queue_cb(const fhss_api_t *api, bool is_broadcast_addr, int frame_type)
|
|
{
|
|
(void) frame_type;
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return false;
|
|
}
|
|
// Broadcast packets are stored in broadcast queue
|
|
return is_broadcast_addr;
|
|
}
|
|
|
|
static uint32_t fhss_ws_get_retry_period_callback(const fhss_api_t *api, uint8_t *destination_address, uint16_t phy_mtu)
|
|
{
|
|
(void) destination_address;
|
|
(void) phy_mtu;
|
|
uint32_t return_value = 0;
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(api);
|
|
if (!fhss_structure) {
|
|
return return_value;
|
|
}
|
|
// We don't know the broadcast schedule, use randomised large backoff with MAC retries
|
|
if (fhss_structure->ws->broadcast_timer_running == false) {
|
|
return (uint32_t) randLIB_get_random_in_range(20000, 45000);
|
|
}
|
|
// We don't know the TX/RX slots, use randomised large backoff with MAC retries
|
|
if (fhss_structure->own_hop == 0xff) {
|
|
return (uint32_t) randLIB_get_random_in_range(20000, 45000);
|
|
}
|
|
if (fhss_structure->ws->is_on_bc_channel == true) {
|
|
return return_value;
|
|
}
|
|
// No need to wait until TX slot when sending on RX slot is allowed
|
|
if (fhss_allow_transmitting_on_rx_slot(fhss_structure) == true) {
|
|
return return_value;
|
|
}
|
|
|
|
uint32_t txrx_slot_length_us = MS_TO_US(fhss_structure->ws->txrx_slot_length_ms);
|
|
uint32_t unicast_start_us = fhss_structure->ws->unicast_start_time_us;
|
|
uint32_t cur_time_us = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api);
|
|
uint32_t tx_trig_offset_us = (txrx_slot_length_us / 2) * calc_own_tx_trig_slot(fhss_structure->own_hop);
|
|
|
|
uint32_t next_tx_trig_slot_start_us = unicast_start_us + (txrx_slot_length_us * (fhss_structure->own_hop & 1)) + tx_trig_offset_us;
|
|
uint32_t next_tx_trig_slot_end_us = next_tx_trig_slot_start_us + (txrx_slot_length_us / 2);
|
|
while ((next_tx_trig_slot_start_us < cur_time_us) || ((next_tx_trig_slot_start_us - cur_time_us) > (uint32_t) MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval))) {
|
|
if (cur_time_us < next_tx_trig_slot_end_us) {
|
|
return 0;
|
|
}
|
|
next_tx_trig_slot_start_us += (txrx_slot_length_us * 2);
|
|
next_tx_trig_slot_end_us = next_tx_trig_slot_start_us + (txrx_slot_length_us / 2);
|
|
}
|
|
return_value = next_tx_trig_slot_start_us - cur_time_us;
|
|
uint32_t time_to_bc_channel = get_remaining_slots_us(fhss_structure, fhss_broadcast_handler, MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_broadcast_interval));
|
|
if (time_to_bc_channel <= return_value) {
|
|
return_value += MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval);
|
|
}
|
|
|
|
return return_value;
|
|
}
|
|
|
|
static void fhss_unicast_handler(const fhss_api_t *fhss_api, uint16_t delay)
|
|
{
|
|
(void) delay;
|
|
uint32_t timeout = 0;
|
|
fhss_structure_t *fhss_structure = fhss_get_object_with_api(fhss_api);
|
|
if (!fhss_structure) {
|
|
return;
|
|
}
|
|
platform_enter_critical();
|
|
int32_t delay_us = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api) - fhss_structure->ws->next_uc_timeout;
|
|
if (!fhss_structure->ws->uc_slot && !fhss_structure->ws->next_uc_timeout) {
|
|
delay_us = 0;
|
|
}
|
|
|
|
// If callback was delayed longer than dwell time, UC slot must be updated
|
|
if ((fhss_structure->ws->fhss_configuration.ws_uc_channel_function != WS_FIXED_CHANNEL)) {
|
|
fhss_structure->ws->uc_slot += US_TO_MS(delay_us) / fhss_structure->ws->fhss_configuration.fhss_uc_dwell_interval;
|
|
if (fhss_structure->ws->fhss_configuration.ws_uc_channel_function == WS_TR51CF) {
|
|
fhss_structure->ws->uc_slot %= fhss_structure->number_of_uc_channels;
|
|
}
|
|
}
|
|
delay_us %= MS_TO_US(fhss_structure->ws->fhss_configuration.fhss_uc_dwell_interval);
|
|
|
|
|
|
timeout = fhss_ws_get_sf_timeout_callback(fhss_structure);
|
|
if (!timeout) {
|
|
fhss_stop_timer(fhss_structure, fhss_unicast_handler);
|
|
fhss_structure->ws->unicast_timer_running = false;
|
|
platform_exit_critical();
|
|
return;
|
|
}
|
|
fhss_ws_start_timer(fhss_structure, timeout - (delay_us * fhss_structure->platform_functions.fhss_resolution_divider), fhss_unicast_handler);
|
|
fhss_structure->ws->unicast_timer_running = true;
|
|
fhss_ws_update_uc_channel_callback(fhss_structure);
|
|
// Unless we have broadcast schedule, we have to poll unicast queue when changing channel. This is randomized by the unicast schedule.
|
|
if (!fhss_structure->ws->fhss_configuration.fhss_broadcast_interval || !fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval) {
|
|
if (fhss_structure->callbacks.read_tx_queue_size(fhss_structure->fhss_api, false)) {
|
|
fhss_structure->callbacks.tx_poll(fhss_structure->fhss_api);
|
|
}
|
|
}
|
|
platform_exit_critical();
|
|
}
|
|
|
|
int fhss_ws_set_callbacks(fhss_structure_t *fhss_structure)
|
|
{
|
|
// Set external API
|
|
fhss_structure->fhss_api->is_broadcast_channel = &fhss_ws_is_broadcast_channel_callback;
|
|
fhss_structure->fhss_api->use_broadcast_queue = &fhss_ws_use_broadcast_queue_cb;
|
|
fhss_structure->fhss_api->tx_handle = &fhss_ws_tx_handle_callback;
|
|
fhss_structure->fhss_api->check_tx_conditions = &fhss_ws_check_tx_conditions_callback;
|
|
fhss_structure->fhss_api->receive_frame = &fhss_ws_receive_frame_callback;
|
|
fhss_structure->fhss_api->data_tx_done = &fhss_ws_data_tx_done_callback;
|
|
fhss_structure->fhss_api->data_tx_fail = &fhss_ws_data_tx_fail_callback;
|
|
fhss_structure->fhss_api->synch_state_set = &fhss_ws_synch_state_set_callback;
|
|
fhss_structure->fhss_api->read_timestamp = NULL;
|
|
fhss_structure->fhss_api->get_retry_period = &fhss_ws_get_retry_period_callback;
|
|
fhss_structure->fhss_api->write_synch_info = &fhss_ws_write_synch_info_callback;
|
|
fhss_structure->fhss_api->init_callbacks = &fhss_init_callbacks_cb;
|
|
return 0;
|
|
}
|
|
|
|
int fhss_ws_set_parent(fhss_structure_t *fhss_structure, const uint8_t eui64[8], const broadcast_timing_info_t *bc_timing_info, const bool force_synch)
|
|
{
|
|
(void) eui64;
|
|
if (!fhss_structure->ws) {
|
|
return -1;
|
|
}
|
|
if (!bc_timing_info->broadcast_interval || !bc_timing_info->broadcast_dwell_interval) {
|
|
return -1;
|
|
}
|
|
if (((uint32_t)S_TO_US(fhss_structure->ws->min_synch_interval) > (fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api) - fhss_structure->ws->synchronization_time)) && !force_synch) {
|
|
return 0;
|
|
}
|
|
platform_enter_critical();
|
|
|
|
uint16_t own_bc_slot = fhss_structure->ws->bc_slot;
|
|
uint32_t prev_synchronization_time = fhss_structure->ws->synchronization_time;
|
|
fhss_structure->ws->synchronization_time = fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api);
|
|
uint32_t time_since_last_synch_us = fhss_structure->ws->synchronization_time - prev_synchronization_time;
|
|
uint32_t own_bc_interval_offset = fhss_ws_calculate_broadcast_interval_offset(fhss_structure, fhss_structure->ws->synchronization_time);
|
|
fhss_stop_timer(fhss_structure, fhss_broadcast_handler);
|
|
uint32_t time_from_reception_ms = US_TO_MS(fhss_structure->callbacks.read_timestamp(fhss_structure->fhss_api) - bc_timing_info->bt_rx_timestamp);
|
|
uint32_t true_bc_interval_offset = (bc_timing_info->broadcast_interval_offset + time_from_reception_ms) % bc_timing_info->broadcast_interval;
|
|
if (true_bc_interval_offset >= bc_timing_info->broadcast_dwell_interval) {
|
|
fhss_structure->ws->is_on_bc_channel = false;
|
|
}
|
|
uint32_t timeout = MS_TO_US(bc_timing_info->broadcast_interval - true_bc_interval_offset);
|
|
|
|
if (fhss_structure->ws->is_on_bc_channel) {
|
|
timeout -= MS_TO_US(bc_timing_info->broadcast_interval - bc_timing_info->broadcast_dwell_interval);
|
|
}
|
|
fhss_ws_start_timer(fhss_structure, timeout, fhss_broadcast_handler);
|
|
fhss_structure->ws->broadcast_timer_running = true;
|
|
uint16_t slots_since_reception = (bc_timing_info->broadcast_interval_offset + time_from_reception_ms) / bc_timing_info->broadcast_interval;
|
|
// TODO: Calculate drift error
|
|
fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval = bc_timing_info->broadcast_dwell_interval;
|
|
fhss_structure->ws->fhss_configuration.fhss_broadcast_interval = bc_timing_info->broadcast_interval;
|
|
fhss_set_txrx_slot_length(fhss_structure);
|
|
if (fhss_structure->ws->fhss_configuration.ws_bc_channel_function == WS_FIXED_CHANNEL) {
|
|
fhss_structure->ws->bc_slot = 0;
|
|
} else {
|
|
fhss_structure->ws->bc_slot = bc_timing_info->broadcast_slot + slots_since_reception;
|
|
}
|
|
if (fhss_structure->ws->fhss_configuration.ws_bc_channel_function == WS_TR51CF) {
|
|
fhss_structure->ws->bc_slot %= fhss_structure->number_of_channels;
|
|
}
|
|
//TODO: support multiple parents
|
|
fhss_structure->ws->parent_bc_info = bc_timing_info;
|
|
if (prev_synchronization_time && fhss_structure->ws->fhss_configuration.ws_bc_channel_function != WS_FIXED_CHANNEL) {
|
|
//TODO: Compensation for fixed channel configuration
|
|
if (SYNCH_COMPENSATION_MIN_INTERVAL <= US_TO_S(time_since_last_synch_us)) {
|
|
// Update clock drift
|
|
int32_t drift_per_ms_tmp = divide_integer((int32_t)MS_TO_NS((true_bc_interval_offset - own_bc_interval_offset) + ((int32_t)(fhss_structure->ws->bc_slot - own_bc_slot) * bc_timing_info->broadcast_interval)), US_TO_MS(time_since_last_synch_us));
|
|
if (drift_per_ms_tmp > MAX_DRIFT_COMPENSATION_STEP) {
|
|
drift_per_ms_tmp = MAX_DRIFT_COMPENSATION_STEP;
|
|
} else if (drift_per_ms_tmp < -MAX_DRIFT_COMPENSATION_STEP) {
|
|
drift_per_ms_tmp = -MAX_DRIFT_COMPENSATION_STEP;
|
|
}
|
|
// Timer drift is unpredictable with linux platform. Do not set drift compensation
|
|
#ifndef __linux__
|
|
fhss_structure->ws->drift_per_millisecond_ns += drift_per_ms_tmp;
|
|
#endif
|
|
fhss_stats_update(fhss_structure, STATS_FHSS_DRIFT_COMP, NS_TO_US((int64_t)(fhss_structure->ws->drift_per_millisecond_ns * bc_timing_info->broadcast_dwell_interval)));
|
|
}
|
|
tr_debug("synch to parent: %s, drift: %"PRIi32"ms in %"PRIu64" seconds, compensation: %"PRIi32"ns per ms", trace_array(eui64, 8), true_bc_interval_offset - own_bc_interval_offset + ((int32_t)(fhss_structure->ws->bc_slot - own_bc_slot) * bc_timing_info->broadcast_interval), US_TO_S(time_since_last_synch_us), fhss_structure->ws->drift_per_millisecond_ns);
|
|
}
|
|
platform_exit_critical();
|
|
fhss_stats_update(fhss_structure, STATS_FHSS_SYNCH_INTERVAL, US_TO_S(time_since_last_synch_us));
|
|
return 0;
|
|
}
|
|
|
|
int fhss_ws_remove_parent(fhss_structure_t *fhss_structure, const uint8_t eui64[8])
|
|
{
|
|
(void) eui64;
|
|
if (!fhss_structure->ws) {
|
|
return -1;
|
|
}
|
|
fhss_structure->ws->parent_bc_info = NULL;
|
|
return 0;
|
|
}
|
|
|
|
int fhss_ws_configuration_set(fhss_structure_t *fhss_structure, const fhss_ws_configuration_t *fhss_configuration)
|
|
{
|
|
int channel_count_bc = channel_list_count_channels(fhss_configuration->channel_mask);
|
|
int channel_count_uc = channel_list_count_channels(fhss_configuration->unicast_channel_mask);
|
|
if (channel_count_bc <= 0 || fhss_configuration->channel_mask_size == 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (fhss_structure->number_of_bc_channels < channel_count_bc ||
|
|
(channel_count_uc && fhss_structure->number_of_uc_channels < channel_count_uc)) {
|
|
// Channel amount changed to largeneed to reallocate channel table
|
|
ns_dyn_mem_free(fhss_structure->ws->tr51_channel_table);
|
|
fhss_structure->ws->tr51_channel_table = NULL;
|
|
ns_dyn_mem_free(fhss_structure->ws->tr51_output_table);
|
|
fhss_structure->ws->tr51_output_table = NULL;
|
|
|
|
if (fhss_ws_manage_channel_table_allocation(fhss_structure, channel_count_uc > channel_count_bc ? channel_count_uc : channel_count_bc)) {
|
|
return -1;
|
|
}
|
|
}
|
|
platform_enter_critical();
|
|
if (fhss_configuration->ws_uc_channel_function == WS_FIXED_CHANNEL || fhss_configuration->fhss_uc_dwell_interval == 0) {
|
|
fhss_stop_timer(fhss_structure, fhss_unicast_handler);
|
|
fhss_structure->ws->unicast_timer_running = false;
|
|
}
|
|
if (fhss_configuration->ws_bc_channel_function == WS_FIXED_CHANNEL || (fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval == 0 || fhss_structure->ws->fhss_configuration.fhss_broadcast_interval == 0)) {
|
|
fhss_structure->ws->synchronization_time = 0;
|
|
}
|
|
|
|
if ((fhss_structure->ws->unicast_timer_running == false) && (fhss_configuration->ws_uc_channel_function != WS_FIXED_CHANNEL) && fhss_configuration->fhss_uc_dwell_interval) {
|
|
fhss_ws_start_timer(fhss_structure, MS_TO_US(fhss_configuration->fhss_uc_dwell_interval), fhss_unicast_handler);
|
|
fhss_structure->ws->unicast_timer_running = true;
|
|
}
|
|
fhss_structure->ws->fhss_configuration = *fhss_configuration;
|
|
if (channel_count_uc == 0) {
|
|
//If Unicast channel is empty use Domain mask
|
|
for (uint8_t i = 0; i < 8; i++) {
|
|
fhss_structure->ws->fhss_configuration.unicast_channel_mask[i] = fhss_configuration->channel_mask[i];
|
|
}
|
|
channel_count_uc = channel_count_bc;
|
|
}
|
|
|
|
// No one would poll TX queue if schedule timers were not started. Start poll timer with default interval.
|
|
if ((fhss_structure->ws->unicast_timer_running == false) && (fhss_structure->ws->broadcast_timer_running == false)) {
|
|
eventOS_callback_timer_start(fhss_structure->fhss_event_timer, DEFAULT_POLL_PERIOD);
|
|
}
|
|
|
|
fhss_structure->number_of_channels = fhss_configuration->channel_mask_size;
|
|
fhss_structure->number_of_bc_channels = channel_count_bc;
|
|
fhss_structure->number_of_uc_channels = channel_count_uc;
|
|
if (fhss_configuration->ws_uc_channel_function == WS_FIXED_CHANNEL) {
|
|
fhss_structure->rx_channel = fhss_configuration->unicast_fixed_channel;
|
|
}
|
|
platform_exit_critical();
|
|
tr_info("fhss Configuration set, UC channel: %d, BC channel: %d, UC CF: %d, BC CF: %d, channels: BC %d UC %d, uc dwell: %d, bc dwell: %d, bc interval: %"PRIu32", bsi:%d, ch retries: %u",
|
|
fhss_structure->ws->fhss_configuration.unicast_fixed_channel,
|
|
fhss_structure->ws->fhss_configuration.broadcast_fixed_channel,
|
|
fhss_structure->ws->fhss_configuration.ws_uc_channel_function,
|
|
fhss_structure->ws->fhss_configuration.ws_bc_channel_function,
|
|
fhss_structure->number_of_bc_channels,
|
|
fhss_structure->number_of_uc_channels,
|
|
fhss_structure->ws->fhss_configuration.fhss_uc_dwell_interval,
|
|
fhss_structure->ws->fhss_configuration.fhss_bc_dwell_interval,
|
|
fhss_structure->ws->fhss_configuration.fhss_broadcast_interval,
|
|
fhss_structure->ws->fhss_configuration.bsi,
|
|
fhss_structure->ws->fhss_configuration.config_parameters.number_of_channel_retries);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fhss_ws_set_hop_count(fhss_structure_t *fhss_structure, const uint8_t hop_count)
|
|
{
|
|
fhss_structure->own_hop = hop_count;
|
|
fhss_stats_update(fhss_structure, STATS_FHSS_HOP_COUNT, fhss_structure->own_hop);
|
|
return 0;
|
|
}
|
|
|
|
int fhss_ws_set_tx_allowance_level(fhss_structure_t *fhss_structure, const fhss_ws_tx_allow_level global_level, const fhss_ws_tx_allow_level ef_level)
|
|
{
|
|
fhss_structure->ws->tx_level = global_level;
|
|
fhss_structure->ws->ef_tx_level = ef_level;
|
|
return 0;
|
|
}
|
|
|
|
#endif // HAVE_WS
|