From 0992be0d4eae81919b57dd5d5359e6f287b7c1bd Mon Sep 17 00:00:00 2001 From: Ludovic-Lesur Date: Fri, 16 Dec 2022 16:42:04 +0100 Subject: [PATCH] v3.0 release. --- .gitignore | 1 + CMakeLists.txt | 159 ++ README.md | 156 + cmake/addon_rfp.cmake | 20 + cmake/precompile.cmake | 118 + docs/images/sigfox_ep_lib_architecture.png | Bin 0 -> 320138 bytes inc/core/sigfox_crc.h | 116 + inc/core/sigfox_ep_bitstream.h | 222 ++ inc/core/sigfox_ep_frequency.h | 157 + inc/core/sigfox_tx_control.h | 215 ++ inc/manuf/mcu_api.h | 339 +++ inc/manuf/rf_api.h | 347 +++ inc/sigfox_ep_api.h | 402 +++ inc/sigfox_ep_api_test.h | 90 + inc/sigfox_ep_flags.h | 101 + inc/sigfox_ep_version.h | 250 ++ inc/sigfox_error.h | 149 + inc/sigfox_rc.h | 294 ++ inc/sigfox_types.h | 388 +++ src/core/sigfox_crc.c | 115 + src/core/sigfox_ep_bitstream.c | 939 ++++++ src/core/sigfox_ep_frequency.c | 422 +++ src/core/sigfox_tx_control.c | 532 ++++ src/manuf/mcu_api.c | 195 ++ src/manuf/rf_api.c | 161 ++ src/sigfox_ep_api.c | 3020 ++++++++++++++++++++ src/sigfox_error.c | 106 + 27 files changed, 9014 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/addon_rfp.cmake create mode 100644 cmake/precompile.cmake create mode 100644 docs/images/sigfox_ep_lib_architecture.png create mode 100644 inc/core/sigfox_crc.h create mode 100644 inc/core/sigfox_ep_bitstream.h create mode 100644 inc/core/sigfox_ep_frequency.h create mode 100644 inc/core/sigfox_tx_control.h create mode 100644 inc/manuf/mcu_api.h create mode 100644 inc/manuf/rf_api.h create mode 100644 inc/sigfox_ep_api.h create mode 100644 inc/sigfox_ep_api_test.h create mode 100644 inc/sigfox_ep_flags.h create mode 100644 inc/sigfox_ep_version.h create mode 100644 inc/sigfox_error.h create mode 100644 inc/sigfox_rc.h create mode 100644 inc/sigfox_types.h create mode 100644 src/core/sigfox_crc.c create mode 100644 src/core/sigfox_ep_bitstream.c create mode 100644 src/core/sigfox_ep_frequency.c create mode 100644 src/core/sigfox_tx_control.c create mode 100644 src/manuf/mcu_api.c create mode 100644 src/manuf/rf_api.c create mode 100644 src/sigfox_ep_api.c create mode 100644 src/sigfox_error.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..44428ba --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,159 @@ +cmake_minimum_required(VERSION 3.15) +project(sigfox_ep_lib) + +# Define macro to manage the options +macro(opt TYPE FLAG DEFAULT DESC) + if(${TYPE} STREQUAL TYPE_BOOL) + option(${FLAG} ${DESC} ${DEFAULT}) + elseif(${TYPE} STREQUAL TYPE_VALUE) + set(${FLAG} ${DEFAULT} CACHE STRING ${DESC}) + else() + message(FATAL_ERROR "Only TYPE_BOOL and TYPE_VALUE are supported") + endif() + list(APPEND COMPILE_FLAG_LIST ${FLAG}) +endmacro() + +set(LIB_SOURCES + src/sigfox_ep_api.c + src/sigfox_error.c + src/core/sigfox_crc.c + src/core/sigfox_ep_bitstream.c + src/core/sigfox_ep_frequency.c + src/core/sigfox_tx_control.c +) + +set(LIB_HEADERS + inc/sigfox_ep_version.h + inc/sigfox_ep_api.h + inc/sigfox_ep_api_test.h + inc/sigfox_error.h + inc/sigfox_rc.h + inc/sigfox_types.h + inc/core/sigfox_ep_bitstream.h + inc/core/sigfox_ep_frequency.h + inc/core/sigfox_tx_control.h + inc/core/sigfox_crc.h + inc/manuf/mcu_api.h + inc/manuf/rf_api.h +) + +set(LIB_PUBLIC_HEADERS + inc/core/sigfox_ep_bitstream.h + inc/core/sigfox_ep_frequency.h + inc/core/sigfox_tx_control.h + inc/core/sigfox_crc.h + inc/manuf/rf_api.h + inc/manuf/mcu_api.h + inc/sigfox_ep_api.h + inc/sigfox_ep_api_test.h + inc/sigfox_error.h + inc/sigfox_rc.h + inc/sigfox_types.h + inc/sigfox_ep_version.h +) + +set(MANUF_SOURCES + src/manuf/mcu_api.c + src/manuf/rf_api.c +) + +# Set the path for the static library +set(LIB_LOCATION ${CMAKE_BINARY_DIR}/lib/ CACHE STRING "") + +# Set the path for the public header of the library +set(API_LOCATION ${CMAKE_BINARY_DIR}/lib/api CACHE STRING "") + +#Options Use sigfox_ep_flag.h +opt(TYPE_BOOL USE_SIGFOX_EP_FLAGS_H ON "library compilation options. ON:in sigfox_ep_flag.h file OFF:in command line") +#Option addon RFP contents +opt(TYPE_BOOL ADDON_RFP OFF "Add RFP addon contents to build it with library") + +#When sigfox_ep_flag.h is don't used +if(${USE_SIGFOX_EP_FLAGS_H} STREQUAL "ON") + list(APPEND DEF_FLAG_LIST "-DUSE_SIGFOX_EP_FLAGS_H") + list(APPEND LIB_HEADERS "inc/sigfox_ep_flags.h") +else() + opt(TYPE_BOOL RC1 ON "Support RC1 (Europe, Middle-East and Africa)") + opt(TYPE_BOOL RC2 ON "Support RC2 (Brazil, Canada, Mexico, Puerto Rico and USA)") + opt(TYPE_BOOL RC3C ON "Support RC3C with LBT (Japan)") + opt(TYPE_BOOL RC3D ON "Support RC3D with DC (Japan)") + opt(TYPE_BOOL RC4 ON "Support RC4 (Latin America and Asia Pacific)") + opt(TYPE_BOOL RC5 ON "Support RC5 (South-Corea)") + opt(TYPE_BOOL RC6 ON "Support RC6 (India)") + opt(TYPE_BOOL RC7 ON "Support RC7 (Russia)") + opt(TYPE_BOOL APPLICATION_MESSAGES ON "Support uplink application messages") + opt(TYPE_BOOL CONTROL_KEEP_ALIVE_MESSAGE ON "Support uplink control keep alive message") + opt(TYPE_BOOL BIDIRECTIONAL ON "Support downlink communication") + opt(TYPE_BOOL ASYNCHRONOUS ON "Support Asynchronous mode") + opt(TYPE_BOOL LOW_LEVEL_OPEN_CLOSE ON "Enable MCU and RF open/close functions") + opt(TYPE_BOOL REGULATORY ON "Enable Regulatory before transmission (DC, FH or LBT)") + opt(TYPE_BOOL SINGLE_FRAME OFF "Send only 1 frame per message (N=1)") + opt(TYPE_BOOL PARAMETERS_CHECK ON "Enable parameters check") + opt(TYPE_BOOL CERTIFICATION ON "Enable certification functions") + opt(TYPE_BOOL PUBLIC_KEY_CAPABLE ON "Enable public key switch feature") + opt(TYPE_BOOL VERBOSE ON "Enable versionning functions") + opt(TYPE_BOOL CRC_HW OFF "Support hardware CRC") + opt(TYPE_BOOL ERROR_CODES ON "Enable error codes on all functions") + opt(TYPE_VALUE UL_BIT_RATE_BPS OFF "Fixed uplink bit rate in bps (100/600)") + opt(TYPE_VALUE T_IFU_MS OFF "Fixed inter-frame delay in ms (10 to 2000)") + opt(TYPE_VALUE T_CONF_MS OFF "Fixed DL confirmation delay in ms (1400 to 4000)") + opt(TYPE_VALUE UL_PAYLOAD_SIZE OFF "Fixed UL payload size in bytes (0 to 12)") + opt(TYPE_VALUE TX_POWER_DBM_EIRP OFF "Fixed the TX power supported by the radio") + opt(TYPE_VALUE MESSAGE_COUNTER_ROLLOVER OFF "Fixed message counter rollover (128, 256, 512, 1024, 2048 or 4096)") + opt(TYPE_VALUE ERROR_STACK 32 "Enable error stack and defined the depth") + + foreach( COMPILE_FLAG ${COMPILE_FLAG_LIST} ) + if((NOT ${COMPILE_FLAG} STREQUAL OFF)) + if(${${COMPILE_FLAG}} STREQUAL ON) + list(APPEND DEF_FLAG_LIST "-D${COMPILE_FLAG}") + else() + list(APPEND DEF_FLAG_LIST "-D${COMPILE_FLAG}=${${COMPILE_FLAG}} ") + list(APPEND DEF_FLAG_WITH_VALUE_LIST "${COMPILE_FLAG}") + endif() + endif() + endforeach() + if(${CRC_HW} STREQUAL ON) + list(REMOVE_ITEM LIB_SOURCES "src/core/sigfox_crc.c") + list(REMOVE_ITEM LIB_HEADERS "inc/core/sigfox_crc.h") + list(REMOVE_ITEM LIB_PUBLIC_HEADERS "inc/core/sigfox_crc.h") + endif() + if(${REGULATORY} STREQUAL OFF) + list(REMOVE_ITEM LIB_SOURCES "src/core/sigfox_tx_control.c") + list(REMOVE_ITEM LIB_HEADERS "inc/core/sigfox_tx_control.h") + list(REMOVE_ITEM LIB_PUBLIC_HEADERS "inc/core/sigfox_tx_control.h") + endif() + if(${CERTIFICATION} STREQUAL OFF) + list(REMOVE_ITEM LIB_HEADERS "inc/sigfox_ep_api_test.h") + list(REMOVE_ITEM LIB_PUBLIC_HEADERS "inc/sigfox_ep_api_test.h") + endif() +endif() +#add DEF_FLAG_LIST to parent scope to be used by a child +if(NOT (${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})) + set(DEF_FLAG_LIST ${DEF_FLAG_LIST} PARENT_SCOPE) +endif() + +#Add Cmake module path +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +#Precompile module +include(precompile) +#Addon RFP module +if(${ADDON_RFP} STREQUAL "ON") + unset(SFX_LIB_CORE_PROTOCOL_DIR CACHE) + include(addon_rfp) +endif() + +add_library(${PROJECT_NAME} STATIC ${PRECOMPIL_LIB_SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC ${PRECOMPIL_DIR}/inc) +target_compile_definitions(${PROJECT_NAME} PUBLIC ${DEF_FLAG_LIST}) +target_compile_options(${PROJECT_NAME} PRIVATE -Wall) +set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${PRECOMPIL_LIB_PUBLIC_HEADERS}") +set_target_properties(${PROJECT_NAME} + PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${LIB_LOCATION} + LIBRARY_OUTPUT_DIRECTORY ${LIB_LOCATION} +) +install(TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION ${LIB_LOCATION} + PUBLIC_HEADER DESTINATION ${API_LOCATION} +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..80cc91c --- /dev/null +++ b/README.md @@ -0,0 +1,156 @@ +# Sigfox End-Point library (EP_LIB) + +## Description + +The **Sigfox End-Point library** is an example of the [Sigfox radio protocol](https://build.sigfox.com/sigfox-device-radio-specifications) implementation. The stack is designed to operate either in **blocking** or **asynchronous** mode, and supports most of **MCUs** and **radio chipsets**. + +The user API is provided in the `inc/sigfox_ep_api.h` file, to send **application messages** and **control messages** over the Sigfox network. + +## Stack architecture + +

+ +

+ +## Hardware + +The stack relies on **low level drivers** (called manufacturer drivers) which need to be implemented to run on your specific hardware. There are divided in 2 groups: + +* **MCU_API** : MCU related functions such as timers, non-volatile memory and encryption. +* **RF_API** : radio related functions such as uplink transmission and downlink reception. + +These drivers are located in the `src/manuf` folder. + +## Code optimization + +Most of Sigfox radio parameters and features are conditionned to a **dedicated flag**, so that the stack can be configured to perfectly match your application, **without dead code** and thus with a **minimum memory footprint**. The flags are located in the `inc/sigfox_ep_flags.h` file, but can also be set through the **cmake** command when building the project. + +To have such a flexibilty, the stack use a lot of preprocessor directives, which makes the source code less readable. If you plan to look or modify the source files, we advise you to **run the cmake pre-compilation command**, that will remove all preprocessor directives according to your flags selection. + +Below is the list of available flags. + +| **Flag name** | **Value** | **Description** | +|:----------------------------:|:--------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| `RCx` | `undefined` / `defined` | Support the RCx radio configuration if defined | +| `APPLICATION_MESSAGES` | `undefined` / `defined` | Support uplink application messages if defined | +| `CONTROL_KEEP_ALIVE_MESSAGE` | `undefined` / `defined` | Support uplink control keep alive message if defined | +| `BIDIRECTIONAL` | `undefined` / `defined` | Support bidirectional procedure (downlink) if defined. Only applicable to application messages. Otherwise all messages will be uplink only. | +| `ASYNCHRONOUS` | `undefined` / `defined` | Asynchronous mode if defined, blocking mode otherwise. | +| `LOW_LEVEL_OPEN_CLOSE` | `undefined` / `defined` | Enable MCU and RF open/close functions if defined. | +| `REGULATORY` | `undefined` / `defined` | Enable radio regulatory control (DC, FH or LBT check) if defined. | +| `SINGLE_FRAME` | `undefined` / `defined` | Send 1 frame per message (N=1) if defined. Otherwise number of frames per message is dynamically given when sending a message (N=1, N=2 or N=3). | +| `UL_BIT_RATE_BPS` | `undefined` / `100` / `600` | If defined, give the only uplink bit rate supported (100 or 600 depending on the RC). Otherwise, value is dynamically given when sending a message. | +| `TX_POWER_DBM_EIRP` | `undefined` / `` | If defined, give the only TX power supported by the radio. Otherwise the value is dynamically given when sending a message. | +| `T_IFU_MS` | `undefined` / `` | If defined, give the fixed inter-frame delay used between uplink frames of a same message (0 to 2000ms). Value 0 disables the delay and associated timers to optimize memory space. Otherwise value is dynamically given when sending a message. | +| `T_CONF_MS` | `undefined` / `` | If defined, give the fixed delay between downlink frame reception and uplink confirmation message (1400 to 4000ms). Otherwise value is dynamically given when sending a message. | +| `UL_PAYLOAD_SIZE` | `undefined` / `` | If defined, give the only uplink payload length supported (0 to 12). Value 0 enables the bit 0, bit 1 and empty messages. Otherwise, all uplink payload lengths are dynamically supported. | +| `CRC_HW` | `undefined` / `defined` | If defined, enable hardware CRC through MCU API functions. Otherwise the embedded driver is used. | +| `MESSAGE_COUNTER_ROLLOVER` | `undefined` / `128` / `256` / `512` / `1024` / `2048` / `4096` | If defined, give the only message counter rollover value supported. Otherwise, value is dynamically given when opening the library. | +| `PARAMETERS_CHECK` | `undefined` / `defined` | Enable parameters check if defined. | +| `CERTIFICATION` | `undefined` / `defined` | Enable certification features if defined. | +| `PUBLIC_KEY_CAPABLE` | `undefined` / `defined` | Enable public key switch feature if defined. | +| `VERBOSE` | `undefined` / `defined` | Enable credentials (ID / PAC) API access and version control functions if defined. | +| `ERROR_CODES` | `undefined` / `defined` | Use return codes if defined, otherwise all functions return void. | +| `ERROR_STACK` | `undefined` / `` | If defined, store low level errors in a stack (the macro gives the depth). Errors can be read with the `SIGFOX_EP_API_unstack_error()` function. | + +## Getting Started + +### Cloning the repository + +```bash +$ git clone https://github.com/sigfox-tech-radio/sigfox-ep-lib.git +``` + +### Usage + +This library can be used in 3 different ways: + * The [original source code](#original-source-code) to used the raw sources files + * The [precompiled source code](#precompiled-source-code) to remove all unused source code and have more readability. + * The [static-library](#static-library) to used a compiled library. + +### Original source code + +Sources files are available in the `inc` and `src` folders and must be copied directly in your embedded project. Then you can customize the `inc/sigfox_ep_flags.h` according to your flags selection. + +### Precompiled source code + +#### Dependency + +Before building process install **unifdef** and **cmake**. The unifdef tool is used to remove dead code and cmake to build. + +#### Building process + +If you want to **precompile** the sources files for a given flags selection, you need to use the **cmake** commands: + +Create a build folder: + +```bash +$ cd sigfox-ep-lib +$ mkdir build +$ cd build +``` + +* Precompiling by reading the `inc/sigfox_ep_flags.h` file: + +```bash +$ cmake -DUSE_SIGFOX_EP_FLAGS_H=ON .. +$ make precompil +``` +* Precompiling by entering the flags selection on command line: + +```bash +$ cmake -DUSE_SIGFOX_EP_FLAGS_H=OFF \ + -DRC1=ON \ + -DRC2=ON \ + -DRC3C=ON \ + -DRC3D=ON \ + -DRC4=ON \ + -DRC5=ON \ + -DRC6=ON \ + -DRC7=ON \ + -DAPPLICATION_MESSAGES=ON \ + -DCONTROL_KEEP_ALIVE_MESSAGE=ON \ + -DBIDIRECTIONAL=ON \ + -DASYNCHRONOUS=ON \ + -DLOW_LEVEL_OPEN_CLOSE=ON \ + -DREGULATORY=ON \ + -DSINGLE_FRAME=ON \ + -DPARAMETERS_CHECK=ON \ + -DCERTIFICATION=ON \ + -DPUBLIC_KEY_CAPABLE=ON \ + -DVERBOSE=ON \ + -DCRC_HW=OFF \ + -DERROR_CODES=ON \ + -DUL_BIT_RATE_BPS=OFF \ + -DT_IFU_MS=OFF \ + -DT_CONF_MS=OFF \ + -DUL_PAYLOAD_SIZE=OFF \ + -DMESSAGE_COUNTER_ROLLOVER=OFF \ + -DERROR_STACK=12 .. +$ make precompil +``` + +The precompiled files will be generated in the `build/precompil` folder. + +### Static library + +If you want to build a **static library**, you need to run this additionnal **cmake** command: + +```bash +$ make sigfox_ep_lib +``` + +The archive will be generated in the `build/lib` folder. + + +## Addons + +### RF & Protocol + +In order to **test your implementation** against Sigfox specifications, you can use the [Sigfox End-Point RF & Protocol addon](https://github.com/sigfox-tech-radio/sigfox-ep-addon-rfp) which will drive the library to perform some test modes. + +The addon can be directly generated from the Sigfox End-Point library **cmake** by using the `ADDON_RFP` option: + +```bash +$ cmake -DADDON_RFP=ON .. +``` diff --git a/cmake/addon_rfp.cmake b/cmake/addon_rfp.cmake new file mode 100644 index 0000000..27a856c --- /dev/null +++ b/cmake/addon_rfp.cmake @@ -0,0 +1,20 @@ +include(ExternalProject) +include(FetchContent) +Set(FETCHCONTENT_QUIET FALSE) +FetchContent_Declare( + addon_rfp + GIT_REPOSITORY "https://github.com/sigfox-tech-radio/sigfox-ep-addon-rfp" + GIT_TAG "master" + GIT_PROGRESS TRUE + GIT_SHALLOW 1 + #SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/addons/rfp + UPDATE_DISCONNECTED TRUE + STEP_TARGETS update +) +FetchContent_GetProperties(addon_rfp) +if (NOT platform_POPULATED) + FetchContent_Populate(addon_rfp) + add_subdirectory(${addon_rfp_SOURCE_DIR} ${addon_rfp_BINARY_DIR}) +endif() +#FetchContent_MakeAvailable(addon_rfp) + diff --git a/cmake/precompile.cmake b/cmake/precompile.cmake new file mode 100644 index 0000000..3958eac --- /dev/null +++ b/cmake/precompile.cmake @@ -0,0 +1,118 @@ + +find_program(UNIFDEF unifdef REQUIRED) +if(NOT UNIFDEF) + message(FATAL_ERROR "unifdef not found!") +endif() +find_program(SPLINT splint REQUIRED) +if(NOT SPLINT) + message(FATAL_ERROR "splint not found!") +endif() +# specify the precompil files location +set(PRECOMPIL_DIR ${CMAKE_BINARY_DIR}/precompil CACHE STRING "") + +#List of precompileInc and precompileSrc files +foreach(X IN LISTS LIB_SOURCES) + LIST(APPEND PRECOMPIL_LIB_SOURCES "${PRECOMPIL_DIR}/${X}") + LIST(APPEND SPLINT_PRECOMPIL_LIB_SOURCES "${PRECOMPIL_DIR}/splint/${X}") +endforeach() +foreach(X IN LISTS MANUF_SOURCES) + LIST(APPEND PRECOMPIL_MANUF_SOURCES "${PRECOMPIL_DIR}/${X}") +endforeach() +foreach(X IN LISTS LIB_HEADERS) + LIST(APPEND PRECOMPIL_LIB_HEADERS "${PRECOMPIL_DIR}/${X}") +endforeach() +foreach(X IN LISTS LIB_PUBLIC_HEADERS) + LIST(APPEND PRECOMPIL_LIB_PUBLIC_HEADERS "${PRECOMPIL_DIR}/${X}") +endforeach() + +#Custom command Loop for all Sources +foreach(X IN LISTS LIB_SOURCES MANUF_SOURCES) +add_custom_command( + OUTPUT "${PRECOMPIL_DIR}/${X}" + DEPENDS ${CMAKE_BINARY_DIR}/undefs_file + DEPENDS ${CMAKE_BINARY_DIR}/defs_file + DEPENDS ${LIB_HEADERS} + DEPENDS ${X} + COMMAND ${CMAKE_COMMAND} -E make_directory ${PRECOMPIL_DIR}/src/core ${PRECOMPIL_DIR}/src/manuf + COMMAND unifdef -B -k -x 2 -f ${CMAKE_BINARY_DIR}/undefs_file -f ${CMAKE_BINARY_DIR}/defs_file ${PROJECT_SOURCE_DIR}/${X} > "${PRECOMPIL_DIR}/${X}" + VERBATIM +) +endforeach() + +#Custom command Loop for all Sources : SPLINT analysis +foreach(X IN LISTS LIB_SOURCES MANUF_SOURCES) +add_custom_command( + OUTPUT "${PRECOMPIL_DIR}/splint/${X}" + DEPENDS ${CMAKE_BINARY_DIR}/undefs_file + DEPENDS ${CMAKE_BINARY_DIR}/defs_file + DEPENDS ${LIB_HEADERS} + DEPENDS ${X} + COMMAND ${CMAKE_COMMAND} -E make_directory ${PRECOMPIL_DIR}/splint ${PRECOMPIL_DIR}/splint/src/core ${PRECOMPIL_DIR}/splint/src/manuf + COMMAND splint +linelength 150 +weak +show-summary +stats -slashslashcomment -preproc -I${PRECOMPIL_DIR}/inc/core/ -I${PRECOMPIL_DIR}/inc/manuf/ -I${PRECOMPIL_DIR}/inc/ ${PRECOMPIL_DIR}/${X} > ${PRECOMPIL_DIR}/splint/${X}.log + # Keep a log trace even if the splint failed + COMMAND cat ${PRECOMPIL_DIR}/splint/${X}.log > ${PRECOMPIL_DIR}/splint/${X} +# VERBATIM +) +endforeach() + + +#Custom command Loop for all Headers +foreach(X IN LISTS LIB_HEADERS MANUF_HEADERS) +if(${X} STREQUAL "inc/sigfox_types.h" AND ${USE_SIGFOX_EP_FLAGS_H} STREQUAL "OFF" AND NOT "${DEF_FLAG_WITH_VALUE_LIST}" STREQUAL "") +#Add Specific Macro in sigfox_types.h file + foreach(X IN LISTS DEF_FLAG_WITH_VALUE_LIST) + string(CONCAT DEF_FLAG_STRING ${DEF_FLAG_STRING} "\r\\n" "#define ${X} ${${X}}") + endforeach() + add_custom_command( + OUTPUT ${PRECOMPIL_DIR}/${X} + DEPENDS ${CMAKE_BINARY_DIR}/undefs_file + DEPENDS ${CMAKE_BINARY_DIR}/defs_file + DEPENDS ${LIB_HEADERS} + COMMAND ${CMAKE_COMMAND} -E make_directory ${PRECOMPIL_DIR}/inc/core ${PRECOMPIL_DIR}/inc/manuf + COMMAND unifdef -B -k -x 2 -f ${CMAKE_BINARY_DIR}/undefs_file -f ${CMAKE_BINARY_DIR}/defs_file ${PROJECT_SOURCE_DIR}/${X} > "${PRECOMPIL_DIR}/${X}" + COMMAND sed -i "/SIGFOX library common macros/a ${DEF_FLAG_STRING}" ${PRECOMPIL_DIR}/${X} + VERBATIM + ) +else() + add_custom_command( + OUTPUT ${PRECOMPIL_DIR}/${X} + DEPENDS ${CMAKE_BINARY_DIR}/undefs_file + DEPENDS ${CMAKE_BINARY_DIR}/defs_file + DEPENDS ${LIB_HEADERS} + COMMAND ${CMAKE_COMMAND} -E make_directory ${PRECOMPIL_DIR}/inc/core ${PRECOMPIL_DIR}/inc/manuf + COMMAND unifdef -B -k -x 2 -f ${CMAKE_BINARY_DIR}/undefs_file -f ${CMAKE_BINARY_DIR}/defs_file ${PROJECT_SOURCE_DIR}/${X} > "${PRECOMPIL_DIR}/${X}" + VERBATIM + ) +endif() +endforeach() + +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/defs_file + DEPENDS ${LIB_HEADERS} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND cc -E -dMM ${DEF_FLAG_LIST} -I config/ ${CMAKE_CURRENT_SOURCE_DIR}/inc/sigfox_types.h > "${CMAKE_BINARY_DIR}/defs_file.tmp" + COMMAND grep -v __ "${CMAKE_BINARY_DIR}/defs_file.tmp" | sort -u > "${CMAKE_BINARY_DIR}/defs_file" + COMMAND rm "${CMAKE_BINARY_DIR}/defs_file.tmp" +) + +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/undefs_file + DEPENDS ${LIB_HEADERS} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/core/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/manuf/*c ${CMAKE_CURRENT_SOURCE_DIR}/inc/*.h ${CMAKE_CURRENT_SOURCE_DIR}/inc/core/*.h ${CMAKE_CURRENT_SOURCE_DIR}/inc/manuf/*.h | unifdef -s | sort -u | grep -v __ | sed "s/^/#undef /" >"${CMAKE_BINARY_DIR}/undefs_file" +) + +add_custom_target(precompil + DEPENDS ${CMAKE_BINARY_DIR}/undefs_file + DEPENDS ${CMAKE_BINARY_DIR}/defs_file + DEPENDS ${PRECOMPIL_LIB_SOURCES} + DEPENDS ${PRECOMPIL_LIB_HEADERS} + DEPENDS ${PRECOMPIL_MANUF_SOURCES} + VERBATIM +) + +add_custom_target(splintanalysis + DEPENDS precompil + DEPENDS ${SPLINT_PRECOMPIL_LIB_SOURCES} + VERBATIM +) + + diff --git a/docs/images/sigfox_ep_lib_architecture.png b/docs/images/sigfox_ep_lib_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..29ed73259fd88f760b8c9dc45434a44377e99f07 GIT binary patch literal 320138 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%0AFUv&7lz)5Gc znTA~3y3O-l?XxLwF2mR<*%0}FdZ(-%(UbS7>2I&NS~`##&R#ytrKL81hn*po&NQY7 zB##}5+R0hYqm}A&i+1w(@-$;>x>h}l8RN@)`ROgU1I4_8;^@ttT4~Na?QVPJcIVCP z9hYcBBhRQLRAN3t_4|Ya-ZLV@Oibe~<}t{Mz!nf>wz;N>Imkr$%Uim4@{Q(Jes>Si zY&!NU>3G#7w%(+fEnn6Zr>QM*{~U6r;9RIn*DzX=@J-mcBk^@R@q?gC|L5D*-#%m& zp(Upja-TZwzd}l<2S~t)4QUL3$liY?WPv!9=vN{I30)8?1{MVjcEiGuCTY|v)is>+ z_uEwCb(-VCKkulIkAhbFuUX9wDc95DI1r~9tdG1GT2bP9?^UsdKFg;co~({5n5cG| zGKPxUOU>c>zH63q-cqZ^d3ARelk`Jv4N!$k_Px`=&G^L;dbb;>@JXuWDL>RV+U5IH zdcqX)hVx>qCxKX4O?JJ8U6+>Wc=ztwt#%yt!uisda$~kY>j;W6o=k0^OTQi&$qJ7*VPyV$Th1R-0EGgD2|T9pzwXK~rC$|Kv55`}&hWKI8O zKjN~n_fk6=_W+yXXBmF`$u||LME~sxl;B|ASCQWZ@CCpDjCx!%ZUOIaZkA!UDK%_= zG+qTq+))#dla6gj{k#pNuesiG&f6Mh_POoY>^Mg5d0K@=Myci#g26hD+=ntlc|tf% zc9ITDzu0ba*2(;8bFbsi6n--CIwgX}6=n5)ys^Ic_AuL#J_cc15N>wR_4yogAHM4p z1y_52`@XH#dsHz-v;A|h}Koa##9I=l|a^b{7DBrJYhz>7$)iXq}sw4tQ zOuhl-lhQQB3ZGR`@6CN#e(yA*U$+EQ zcbc2-e)>QCu=%XcvN9!(A^h?+bzCil@-jvXVK@0dW@v1r4mED`*O~vOo}8R*Stga; z@^y84_X9g3DFazkWt{RTS$r)yavV650_X76 zE4rT)nfw0uilTv{%DEnK5+m=<$Q>Q7E;shbV0&K!%u{8{p6ixMG~W|YGkBfA^LsX_ z4hR^!hFO-Ke?Q3`E4+d!!QDFupsP21pB%lf$MlQgIJRz13F(n`Y$&9@4?c{k%LP`owVRD=vG<@~bTvwhbY= zRhzpXf1Z0{hu>p<{N~pW=PqB*Eq>?1B07gs$5MlHYC0xR-bR#}$I$U7fWv30av$X{ zrl}b190DsEIPD_kZm|sAy1BnbZ}--Xn?Xme1%rNNYc=g4k11t({_uAHCi%B7SH1XR z^47O|c?EDPdbx+)_%?XjCAA>GF~|e%|EcZ!*~`=`fRlnxVz*IHtd>f@_!Dy)8M><` zuVD2c^}FvBO`ZWQbe3%hW}y?B*j5lOXqu0HUs^_!G*3s}4U5JC=Yb@FE$uCD|Mnp> ze33|UU2HCjOmC!8A-cd=l4SelE0T0MYu!Ff-zuPT8}eRIYX(R-&#Mh%0iYR;4AL)p zNOfd-99w}iM+D=PkMzZi5oBF5A6`Kk*$ka>G0NZRurd^~7G3pDT;aKY@nF{5n*2&bi`rW$d z64^FZDLH3wZiv0^y!`aB!Z~rdd<#l)T}c{5h>x7d%onwb4Y&VQB9B=-wB_=Jn~7xP z0&$vUyywZd84p(gU}aoC)TD?)^gr-hWCk56Z4Sru?ly1QcLL$%bM(jP4_+~shrg35 z?F76C@}RPag^=-=qhHCk*>{dwF&E=Yv{?9T3Ye557eP*Z+(>7zPa%TuM;)4bxuOdLKN^OIfH2BfAkFg||>KY?ts(|Gw19I2~x zQ0;24743F^Okh2=0$&{*yOaI})PXZE!_lw`(jJxur0V#YHDP0-xDu>%Gikr5jP(2w z%4RDmQ%ZZMwVcpO#wV)gG%5Meg<#jeN}y&!qOw8kS@T55d&>2*xn#93hN2|X*bTys zjyZ@&j&JfY#nZpox^9&{x>%-q^|$a)R(4-34vPTBIg;>?768;UKw+J{?QJAXA_&Q#IUiDL1<(UV2 z;d$BZpYCHSo>{m?88+IoWUs9CNP1$I>?8nxpe&8XQfwmrpHwK}Ccllgsg0QWkG_D?gGF&sW z>v;2lSD;6h8ZU|=|EG0L9xuK;_o}8HsoXRElu)Ts%)mimRQ$+(9J2bku^W;@rUkJO zoq9}ROyMIpAo5^Mr}V?s6G%W)!13Dy$Ds2Z^Z5Hjb9yJ(%edRfYyb(S>=^$70qYj+ z)!|m17sRsgnG^PPG%^wXMuwLHx|R)a+F`7A#ZbA+OMI5vGpH+6AYxIguXNCoLT8!S z5qqPY&nXb`!;M!)^<{(`g?D?6>`RZ$B31%4A@xh?zZG5fBxFU`bs|rv|+v^7fS}a=v2_YvqSaFyJB~A?O$MKNl*~_`?6lzdN?9E&3kG+0? z9;<%dltT6)xQwN28{(IMGzXDs7YYyK~0 zs&NB%N59PgUE{oCqt`f-$a$&jH^v9`J}HMP+PD$+u7c9!!1q7eQ)GK}G|l4Yu`EZG zC4@g0c_UOqX=q+rF&{_{hn-jg`NhCF0J?!p2`Yl@5#dqTdd~2xkvmqm)uiujFO=T! z8Zq4uA}oJ3#C*i$N?J|+sADlTAe9FD9?okk{xqEm#v|v8=~j=AC*uh^4=$)TsdO91 z7Bza@_CCKKi}KQ8|Hjgq)G$ZD%t4^HkeeOq$Hf9QmvznacVWskA*%iYGwcIVyV zpTC}?yHY!UwWI~7Ly#51s4C3rC&=)OMT}I#JrmE6guL?hzw?m51Xpq;QbIP)xb_NC z^~NuT{&uKSyL=_@dBEKvUw(8#d3TH`yA#&7!iQy;mvbX}aSv3;lJ=5IVtvn&$jr&~ zPFq8_;WQZ<*r1=ez3Avr(6Mm?CBWWOR_f7^L?`&Xxz0>am;N z=T%JsaWa=tgFJK!)cbajK)0`VG6@5}3B6M@OJjS6&hijx>}p5-IQJ>>>^{1%61_jrboJOruaH-iXr1U!WB5KcF)CSi$3u6y(tIIu&l6 z<@6Oj!}bv9@p)g>>szutuq?i6^Tg9#V(q$x2X%%1#}XhWSwyj0Uv%n|j-;WMZM?aT zu4dK`d*8N#N9D%0j4x)0ly56q2IJF_5>j)Ce#eYLzw;U)dXk&uPLMOTQJ<6NQY06F zI%#Nv=`a+5Y&GE!B;ddE3|qmVosWpI1Mx+A7`oM=%|s-zG*`3uGY{HtJG6SR%0PT8 z_1I=?kLngpy8IK-)Z#3*4M)FsABI|X#j2Qp-{d1KgX7YN5e?WP1ib3&4NA~Lu71+q z;v;%^n|e2LroH%ovG-O{Rd-R}D4o*MwP`64=>};KkdTm+kPc}#A>AD!-QBH}l%OEe zsg#0rcY4-Fe4ghU=Uklcy*%f;aX1*T{%g%O=Ug*?)BRl{@8o?)k|UFNtItF34hrg0_7j`uqD{IkHZ47g|5#_UQuRa&=*Urfwrk zbC5eVg3l7_UW9o^c)7X4uyY~md5sF&%$)7$uMf`8NV!<>$?Dl!rByIDm5A28+)4`J z;;=1CY1*+)b#gp*W@6o<#04*_EKVJCk{|IK58uJZ3V3%P+C=ZJ*ZI5?|BC>%%Qo98 zan6U>2~DQq@(r0fyK-T=J%f`>F|i}>t66ajPTW1^9FN0q(g}1*$3NWWR2w(y`HqgO z9`qtmI<5p$W6;{O@9`YJLs4d^lKny|$GNq&S1HCc;~HSD z^O(ZP%0|jmBio?K;OIZ{viJ>77Xqty3GjmjmrDmhFN}-h2z5iz?>X*|@&wimv0L9t zM;?Y4cNoxsxUyx6j|nryV;UTgD5+T%@0=|~YVOekb!05M2$lz(m};#`soYJp@n1KN zHcGDuCYrPl)^jS8?L7_#>Q>;HiG#xGLy}VcPKsH=}M@x9_njO3EB&OZJan{<^c` ziS!xb4o|Vhd zlZHM%ZU)4c&6f|GS8&Qk8^PFFA7j|GQM}znFg8GtdJ0K9M>cat+DB(nrwKZl-Mu4+ z88*fhbDMKf|29ddEQC#6p$44^Rqk2vuKTGHQ*!_s-tNAbnwtAPt_45d9v+|!D~=58 z3T7otZ<*MA*(*!sC#RQZUXO%{gX=D5=<6&b*Zfm)0Ujlefae*; zqX8kW`_~m7vDvPS!tdCI)z_iF3pNf_lkT$=iV1(Gl69{9wH7p{YNCe5-JF8(dw$Q9 zkTXP1Kc>wsn%Z7*Y~9jbwHBUPi(PftUas-3QPW++<6@8cv;8DiV0vl5qpmo$Eg{vPou38c2KKzV9&7(&%#+`;_Z=s3I zCTFkY$+P5#F~}u_Ym7Nmt@7Dtu)`N_M1;&v_@friRem6sdu5iNUcC|S%@2>`65)Ga z&evF4g^uO!aQ0k|hoY`QzywcQkAW>{Ak7phUBB}!|I2tLp{>x!3X-Aa&{5G!;bi&2 z#wK1F*53KhZRJ(n%N2cCEsU?jA0JPQY^iXDD=%;PAh@;-b>y>D;TI|hKhyD6sahR0 zFGUIabtV2LnK}o&?~K(a=uh^kjpu`?9U1r@iiJG228v_N=~Y#Ds;DcP1=RdYL;&_? zeu)2?YLg1B0WQ^N)wJ;%E}_eZi{`j(ileJ~Oi)O5+WY)OE6HPLV6V~PSUfvJ5y_f0 zw2FE$jbxvdaFUPKtgKI%FbgIFyjN-2T|?Pw`LCRMClEmB8i9A% zlZAL94h7DZ%Os|uePl8FTh*-o%ZGX*f`o6!o330F&+dRJ1(Fhn>nmRHDRP~9x1H3C zf4Kj#G+T_JY@}>n;2s?$*~)b_OHNv(l}U66^1I4Xa!C)A4%*^$df~~xIbjUB=Q-tU zJg%l#d4y(+m8z(JsCRyjLU4iwdI0b^mGNF`3IUXoxHT2A@@s-6)$%CHs}9PT(x*@DG1kY>k}z9C`hK!7JQ`DWlRBccBKV>IouZyQ z_Pm$btC+HFt_hOPI@0tg21?5Ow)JCIf1M20ny718FF=O)TJ?P0zD@0ImP`jIh#hlr zt;a8u5osCtHK-K@|7JDZpf#jv^qtT&Q-y3Fwv>yp)8#JTd|q+CRekZ~inOiV;V^#d zj&_!D_By*9yNGap56ehVoys0tw+kdeg2MmOk809+J+EahjcB%QQ}-mRhijKj=nPaK zKjrHDmJC1q!JfyyB1e={6u>R0;T_7whj$^_&_2l=6%0WjMIQmk61J`6EDU>LgmDb! z%Cdbom|?X$4l~r=wX|rQ_%~!#C}IC$CYnt4d{ClNUF${ zDc)pzvugd~@a#IlLi3x5xWdfjlPU?1duIOooVfhUIi{$`QP*lzGi&&pRG(<6aF5Xa z5T^5D^j9kVQ4>wrd+uV7YYNKjd{Kl>lkeDYE^hli5=eq${QD*}9SDWUJxbdXe?qoW z(qNk|l#)R2(03BmHa56GrBnB5ZxaOz)l)QIK>AdO;4R1< zbHlm#HjiRwas1*8_*cg09=GnOFzCm}nJ&aJ6uYEvW%xTcUhvs#N}ws5e`rH6KUC9! z^-7nVP=u`FHBTS>BJ`Z6;U9dr70EY}mxq%#Al20Kr=+y(&5Z-{HtYktE@ONhn!LW3 z=h7a0VAdzsT3M><$hH}fV=XyAljuXu^R9{?q<$sktt3D%3snf0+m{oV&I^S|N2RY~ z67DpSzMErqp_jvVuX%JS?UZxO&>}|TJ2uzl0{swELKtgzO^+}5+y+1?W%24#Wpo|aSxM_aPmm-{5tCal+otznac@PC1{Wc=c=#?F0EiUNq8RTPxFucUx zl$4~;qAr3QLO>e9dxT7vgC9%F7fH{=S}BfeCnuKgk-GRyWMrI~?1}y8XVK~+Pf#-8 z!mP%)Hk>vm*wICr@fFuz#l)~)qukY5#Y8BF>bS~is%q?n3Kh)(&SI*$QDuKfchJqQ zNj^3~E(ybCbEJMXSbom5$$+kRzFDIL>hiQ%qY?)mpGuk1VOyM=S(w)=)P+B=u0`&3 zPT}OZI@U!=s14rK?wIH0H+zxuP1bVu5i>dwLi(5#x*ONPLd-No@71W1WmZ?3|9ey? zTwY0iwe?@;j!1$H%li9CbG-soc5J(F6Tic}HA> zMLdZNu~VfWu`UcSf+FNTJN#rFL0#5#@UhXl|IuXNd$xc3@VIj4M-y=WnVK3NZC$#v{}XjtTd_UUy|MV z2Js`$d}wG-hY49JHhyP&Gah~g#Vh-0qkILL^vN6)E^$0cEb8n$K^zZBt}vH$Lgb`O zX}g7#YWGV@4Go{1^k{Ks1?F>HNG;uOL8WnB%RSs1QZI$(Y4543XnhdFZT4ppYPL zEJ^+wV?roId8xyL(qOY`6>mdsG{T?5UP9wuj^x$|`UROsoj;l>MVW*r-tno(x+{`7 zHg!EunfC^-by7utW$dnB(QPn!?6E)AP5Za!O)zO)FYmKTt9Oq23$kx6pz>*bfj<(s zn+0c*5gn=2savUWZXrH9?|5)B*T*v}uVvp$Q6nG#mS^Dqq8tP;mwsnvSQrH|e zJW0jAAfh{YU0{kDfbjwxzZK`_-PN7D&464U;puOX4xK80-w%a|eK&L39ZaUiG(oN+QS3mwt z1(DkRG#vpoI;_%kBdQ*XfcHo%75uO3=>dBVU3aP?n}57#|3bRPd>~ofY(a9Yj3MNQ z6gN}1)~|b)+k54EFON6w^8aKdNcrGRvx?dwhMS<6B4irUXAr{0h~x)SF7t*2=vU1* zzyv67Ibjv(pC1sa*5cM<(iA1B<{3*zQ)yE_zZ_;}K|h-)PY_WlGuT>ewcsh3?Ac|E z(~KZv2$zj`lH^Lp*}0c%lHXHLHwCLti%_Pq!z(VB6dD_)6|OV#g_y;rFZ_&h7SkU3 zW-F5xbUeOMsl_aCl|MGV6CI#z&7`D8Z6lWoFx>!5N1iGk)Hk661=BR&Ek!%8T3Ya% zxbq~j>F$HkQ4C)+sf6^(lt4&<#1lkAskg?y&(YcYm@uQLLzJ*NwEwNfMZcN*<0 zN0^^;MuaHjz4BZ2l1*?;O*rhBQ-T0xNT!24=anCH{&OYM4AgAo6}-X9%XH5$*t`an zD^I)MrLUR_S`GZb{TMfPLdVQJo((rQ%+H3g1d*(y8(WOz)4+JhvfWQS6mGn{caQWL^1klF;xfXz(n8ci1+-Im zpY526xFYHXad#5TMTOd_bEE$FnyP$wqBmU*Mj%)Nd`)!#OVR7R#d5)Gr!gR8R#1(o zVK{Fx=>M2OUS7Ur%s=CK_h*W(8xIrk`7DojOG4Xv33thsL8Ed{VS-yVYtp_nob?NC zvirQ2cNLZ2Wd8bshZ{T<{V0us2=UdG*Oo8ndu9AYf+$f;zUdMtrLjav5qTAApjZhU zKp0L+v~K4tVxS`|X+q!U9pIEBX3e|f)*67{_iI6zL3?;GB*v>`W`IwK7w^m2*iaA_ za(?xtz#iQUrHk)4h4aZQ3;t-F_{xIOMTGOJAr5KWI>k;pA=c8phb_ntPbx@6o)Hx$ za2&WCU&wR?HIPmnp7k0+jiSB%LQr~`l=DzH7Ve-1-218^u|9Y5kr>`=50gl&QE%su zH%TN9>lAn>Cq3&yFsu=BdB8~2!1jz@UCr@_5T3N4#QEX*Q3H7s42hJKx!7J@!B?(Y zDq2RePbKgAkjQsT$Q#z$ z)N1ro&hpM2eA&(}{i#<*-QU~ZifhQg_>*Toz%wr+U+JZID2+olT-etNYiM0RO-#w zGT)*OPlmLt_zal`{YaS%$Y0$W3oS}ju%^5aamb%uDiZm9^?6oQk0%wCJNCDASCFo( ziqLKYhAEzcwn}G5B7?|%5@u3;;fF(chhLs?t*{&D3EUBs=bt)7*aMhyOSKm}T1oCg zx~O-&`vR3cAhVxgY48LhW_qctt;*Dg(!W{`4tN+@4Zx2L)<-Bu-^As zy#OF|t&HgV9nt*VW%g%93sFYWnXDIIlj+VB-Z)0N{kqf@g*E`Mx5d_9FfHbfw#X$p%#` zTgpi?5`r6UJ0kj+0QKa06zlnk#P5nlo2rYqoDI|1lAT}7_-?RUEq$Kfw5{Dy!+Z5z zY$I6=6FS=yMLgBesj80JNiB`EIiExW zl(jIb>tE!F*Atmij|xGhP`o-o2b0sZQQPX)N|^$+q=`Dr!?CWZ9IYZ=*{fU6gZzDI zCJjpO_x`{M7Z)1tooHds znryPQIm|eKQuzIg$X*_6(BY5!9t}NDIhW&Nj4y^A1v_D*$yF(rH^&L@EMiaQ8TJ_S zIB_C$K7CH2)xZLDPsV~S$_p;_BO$flw2^EW9dzuaV#6XSUU*Q=?p*!4s>qB zYAJ^t_ouh)H>SnI&?4dg%8ZBFGZUERFRVEUl?uN*u6|QcJEaf z{aNu&BH~N6(du!W)~m$&U8;Dw&8L3)ChJlD%E`)yDb92Gb_n`hxs{7XKR2)@)JM)s zbjeoPj2cl#2I)arkYXY-y3FB~L(<9ILD4~&>;I}ujUT+R?WqSP2^y9)G|FqOS4Z7~ z{*qzVxIS(4LkG-tzBab^di}o`0OScf57GQV>bWJ`2ToNVLHdr^(P7aQCqTD8guWa5NiKUnS>6CtA?&I7N zy<{%vmkt*_B9<|Lik&dSn}=yMq%Jh|N{;3SJ`#5KHXOBTrkuaga_9-8`I8-NmmSEb z_t7)NI*z{{Q~H7&DY{xaBuO|=6=bvO^X|8`q*b+dJVG^2^7mdjnJ*P|)3)(}TLX0( zj+VG*bHjX|k7>f*c^pr3hZDL-(=uRN=|AcmN9U2Le(!ra^WFD*PtR=B6bxk5CQU%S zeo=|UBlSZChZ-M^ZTWr8lE;?=%~bcUgOtX{0NOEKvn%sMykSo$C8Bhefm{0iJ;cu_ z7xG$Sa}gH>Z#u;4kO^ed_>sQznT5$x8oAg0+(kN~f-F}w zD=!=shKsHwy!(N1q_>kI@Qx}r7go&%nw0bBqRtS8ONw`LqYr!Kydo^LrwsR4p5}qj z9u@STnqJ+~-&ZOF)N_7C&UsEUD0LaGMbm4jXboB#6DJ6p2}Xk_1clRB#@}p;&%Jj*CYg=A61PQh^)J_1>NLElV!H4)n*$adR**N)mKm ze#_|>aUa&tDL;Fc$-a;i6Qq8wO>Coi1iPEL!;aVzoPC`*6xLR2uERmSC)!Asn|*!s z7(gAJz`PzY ziA`RlHsy4|zXmIyJj4;L<9!gr&8 zR7+2Y_Jgc#tLz%Hr?Z4pQ#3$UPjpa zTMO_Vf9I2m2#*Ye`SIZ)5#+fl2b`*3NZF;?T+7(UyBSFUwM&9sSO0LV;vIFCJqb3z#r?uis`)fE#Y^mW z**d3g!%Ahj!xq|L<1Zw`;@LxgaUt0dpF07>(pZ1mQgdIB+1$WoE&vcRkeFSemG_2W z`(y>6bu)X!a6DQ3UV5BNYlWi^E4P78o5cxOYu5LC6ujH5UMk@~!ao(*tcCs}68-8> zjd_Yy!+uo8ELp&7^KD<2DJ8e=)e$2$f)3Y)cAy`Yf_sbC&qyn3Bw0PFE7Q-3id&71 z<=QkMM1DRtJx#Ooi!PuT(aUC6cue-=bFqx@)ltM$zEUofO?|spM^AAGI^MU-GSds% zV@l2h}}=qxpfBCp&Fj5nI80bmX7} z3CGQI4@}BmE?+Na3d!Of6yty-J;EC^3E+#BR+|QIV0S?7XqkCtB6We1di2Y_(#n;t z6J^3XJB)cKfv0qszNEC@G|C6S>;<SJK^zHqy!rKATV4+Mjw9$90 zkDnfWOmtBk&X3fzI(=bQ>gusOs>g>yV6=_MY=nD>h@cVG>|8)lvoVS02T<>z^?jqn z)Q_1yH57vH2oBr>4OYd-%vO8Q!sMXI`EG#re;7+A+B@>C`N?ZOqtYrJCLNv^5$)jB zNopfeCY)O8QhzDlx0vXr%PMv*M+88!&uiV33dMgFyq63rT7@#bvKeRTpdgdeU7W(> z#Q>l~PpnQAw%$N--zLyU-`9OX9=lu2rm^1{o%cH zHzIr6mrWlg5@WX_v14sBh71jG$_n#;B59h#K(%s5y%1{JxNpZbb-?_+()>m3D4yz0 zBQ~X#t~%G0z*MtW8u21qJBHlLPPBX<2Rn=vEJX*Ia2sT3dziOapI~-NJcDi$8{QZH zH;C`N+1qOCn_c2}n3OflOO`P$9NWP9_Oc4%a3^ma zllOld6IHwn$y{|bTKB%JUjZD$t#=hQOAFRrXS^0Bm6LPOSfnUvb8rokL&lJXeaC8c z>wJmx^q=X(JY$uKCK|gFFiD@&s-RVQf6{p^2TdiWa0t<$=xDo@?`Thub3^}7cM@-| zem;u25{;uSx~@c9T%6dQ4q2^qlvoCGt@--0S|J~v7Jg3gJqD46r2s3R6}?RNdb>Ej0iK)kB>in`^YRBqo>0iDf{=J++ zNVDO-K^g5OB;*>YjhI94$&`2+M(R(#yoZmVnz*weIY{2x&OlPgwr7XJxaF(N(yoegkN47W>_Z7RNzW+~$~zZtgq*j$ubO+e1HFdQwxnp=bt{2S zpvQfnFq6fza%WZ0E9W2j8UYO0 zhBoW(pKWaztQ47-c!X^5yx_1Nk+M^iciN2X>K64%XcWlsKjhG~veqO1G-WazElP{b zbJ+8G`n+=1$0zty^1}iVfV|EEi}C2QMFA3ysD6ZBcdw^Zt=GV^J+gZ|GZj1 z`!kBLKsOi+fB>}K-=A;X6=apU z+A7=K1q;vuffEecQ0+spR@>yNCYG2WCDYx^t}=%=;8zhRD%!8N z?k8l|EhvDFxjgy#@VQ^#wtxNWc>$*psp6YLdc4i|QdtdgaXf(m%dFF<&{su2% zJmxT`^clCg;;#}8QK4M)!o>5`LLIO0<neGF+8#j~b{Jj&VSm`7EurTdGqT|&bMW3qs2aCjhI)yK z9=OnuV?E;Xr&n;SpPp@o`zHq^26i99hso6Z*C*rXChBAWhdn%PeE9H^n`T~?k zOL|k;(<3OOwfD%>S>>ytbWy4Pl&F-JSOPn_#WkVTX1;*I@(JH089EbUGxsd?$3oHG z1_RuTgPsU77;c8hEpA3eq*R$%Tqd4>92TB$fD~N4vTTEOp;eww01_#wGR6!H(Kedv zg&YjfS)<-6u2~N6X(sO3nW`*u;4=IBwY9>hTgn)CAt(r}GLdlrtgEO%7l(>XYu-_j zo2rW!AdvvDM_3`fUMg`StcUI6&7A}VEft1GCg{^4tcAd>bUP!ZMuW*4knLM2=4~aV zx5pDcoB*dCl7V-+KPEIs=ilJNVqpq3+;-x6f$+myRp2PJQ`dpi!uw2l+Ya zOC*r=`~ZQH?$9?&0P0EoLD%7;{e?R#Lvlu{?>Q}q=fH(PtemN-ngVMRiA@1hYVcg$ zqBp-YlLOQ$%6Ts)S=Y^&gF+QW*vJi!xfZoLM>ALsC+zS`3ai%CpfRFUJGw7`^TN2k z0!?7t9;-C)i^?8pSGleu>INK5+$p74}6R!4$VjmF8=%NU0LCJQpSAcWkP4@}`yc^`t7#H~CRh0c&<7suZe1uj`tOtDm z`2=sSj05^T`(+hu@GKz24~US@oW6Z zHomgl9TGckJlO0=Fw^l4-c++#;g`$&1e)@Cd&b4W;aSa(|+w5zTnbBOxZ?=Mv#xXnJ)b6parq!;{IDBvvZp+1?NDswMaOp&Uv z5zvs3E*&1HH}IV)o7#ZUrfdk_U_R?*gc-f*aFaK>*uj!0O*zM0nzWS-$}J^ucPo7i z3vy^K4|hn+2El^g5WF$Oo~Bq*wTjGHP$C!*?)G!KKn>dnS_OYt5F^YH?S6yNwy`&L zTo4eeiDPAaj)Aa`f?GVDk@oNP%@LlO=-}xzb7s1clRWQ76Qc_-=7eq64E{r4W@2Cd zCjlO}<}xW4FsaoI{sV=2;SN(9!Ih}vv>4Ju*>?N+(16TjE@i#)ag{~$Y=qu2p@+p1pJfutK$F`G(o=l9L_g+xrIjH#8Y+b(O-VoYA><6d&+mJM7!Y*NW#I-W1Mnc zj(u&)np0-h-H)-qh@=ERbwj#zeN6c{a7W6m2ebZ$V%|)Erye+75qYz`D*h3?oa5w1 zlBGg`_xB6-XcVGx`Y*1YsU7}4&&TyX&u5y(LBf5Y)Lyx0zOBc!6o=UudTj`s4Qs@- z&}*`8Br@Pe`x+65sdD=uj6RFddBn^M&Pxr@r9Z<_KRCAm|2*aI{i3-WF02H8G`pzy z4jB9{JK!;daB0}riIFeKyjfI3bAt2tUVXRO`r(kxg5=2n`@|E zYrvS!Uhm!+W*c^ao`zj@$tVArRO8)BNcs^tg@GWU?)k(Kx`ydm2K;|Ba zstp0S1l&E3Q+c?)d$O=; zzDk^yLS1&I)ssnv<)b6osTL&m&j)%#alNhh?y`DzV~ArC^1l$T9FqFOAAqqAka%tT z>0X!LV}lz1Sb221wlZfn4lZGW44mWgLNjTrfxN-?5>lUj( zOUZ*B%l zhQ4#MAVWYqeFll`92A#bPl)DW*@^dj6@AfUKP>>LCtqh`#pLzXEr)<-LZbb#uM`+Q z4p4hq7@wUM>&K-Xy91;*oN9x1!PZymH~7@y z@oC+&q>x4P89QeEA2A8{y|u%L1!!L#I0P5KOTPK5h{TZ)ptLn!T6WVlBGH>OsT3V# z*SZ0_qp!XCsAznAND)FQyiuS<7TLKuljMh#?k(QE5ib>4@u~~PBwiW>71 zZ$Um)zBI(a_3rWDmZ`du!Vw$AUu!u;-lt3wejlx?68ys4>N}bYZusr<|X zl4|=qBn3aKs6&^;(BLHp9wgoCHS31>GJ8VD2UPs)Etmy zAYC6AO^V!a=XCkyhC3gcZ2yG_*Fn)|r+XnTAk!|g=5bl@E*HVeVzR#)TpVJD5RmyY z*Srfb-~#wBx+vU~*I!0zUjaWsaee@v!*r>CuN^6e2yz*b5yJVeSlf3Fb8zbe?cj8YEr7Iq&q2CG__=zksnwZE8C+v%N zlwi}7|LD9Bd7oV_Mo~=}@DmEKfM9`%^uHfN1fR!RrO&wJ&Wit6$b7`d!1Ai`)d*nu zg)q3qLe>b4e_z4{Rd}E_T_4Z9!1E>>lK)qGi$kp7I|qwCYr})DJ4pNNjuOhSNc^*v z-6F7(tWXd*S5)?Y&c%ri?xwr94|XmSuyn6;S;#-nu8|M;Blz_0cyKQK|DNj^IG5E! zO_+PvFDePQqVClC}g#Rdfq&v6}82#LV+xx$NUzVxT zr3C`8~uUdukeG8b)|E90R!syo|K-yM;^2>blz)IQ`v3lY<=GPb zkAw_%E|UMAD;}Kdv1eHXI0ZkDQs>9LVgE7*3Sbw&@4{#QgZp}0H$eM85wbhrT$KNL zGmwn&f$|8APK2>vFSymHHSNEqH6SY-U>86?CYVtF_gw#P?km1Q2c|s!w}p%boXh0D z%HuKE7Uy0yY)blqHn>$zO4fhOQ#2BYIE>%-KzaB7QHnsS|Bq7qe|W_Hk5c^el>Prf zDVBL2h5oGt_#apy|KC-R=1b`btY>*k{T`3^mjr3ff3JK3IPY<^hM5fi*dB!l#l^I} zDyH$k;{BXWH_fnYq`dLF5Q!LaY3dAnk@tXU6!ATM<4&w@HT|uz_>Q&R1!W~8AoJ^1 zjg3<0;KJD1K1EX8M{LDvTG>3gj@&q=lTfw|V%`Ao4BkBJ0R;Tc{LPzlNo+!HD{;0$ zPKtfm6pNbjMT6*K1SQSWIzW%bz%4ROk_vNESpIb;rD)n+f<9G&5QJ0MhFU|zjkdQ+$8Dd5xUq<0T#ONyo)QZvz<$lO9)RC^Y>BdWsk?ujS*KE>HODbsg9jyhVET_=D|OFd-!!gFTU}hUjGbhW?D|>6?x^9_=Q&`-3V;_- z&-{UT-H6)X?aDxgE4h%$fMrL@^YR&QY}X*ZbhKQ$l}Db@waLp7^V+|5hWIbcO`#Zd zqDnTlZ*I$dmyxe5FoDIlekTQ3;OGo7pI~Ihw<@Z={R{ds@f-tlV1ZhEVzN5b$=w$% zRyE6%5Rq2@%O$>3NcdaE+>7QmKLhJwKP*HDJ@z1Q_#x;&iq{3b{jgDqcn;MG>|Lhg z9qt^okuM&}+P)8qh5Rp0s~a-RIsS0#O8O}%p;&8qJ1af)1@f&y0`C6u*-%_*+PcC8O4zO6TeoLW?9R=j6UQwzQ z!F3c$bj26k+8g4G=$o^dY)-^yLU0V1U-CL6>%nQoNqoUyy#Lys3G;7YHtdbJ`|c~S zOol>K0(R94vn)r9gj77qxbIuC-2RSfo6lUbz)B;2TRKA0F4sXXt}Y-Y(k2o<&E=9H zq6zh1X8Uw+;pApxk>^rqVjM+{@or!t#(x(|_i0Ntxz7ApW9P5)j-hjqt^0*KY+Gc$ z;P=6{Vr2iiOq?j!he)qLq`=4bVVV+*O?{=msE_E(3^x*ijJT3WRG27&VIUAeTKKD5Jq1pNR zcxh^Co0~VbnBrX#+Q`9gc2MGB?^{xfLq?Ik4YVIM?DQqBef!*ph3owypZD7%u+3|x zg_Ur9J#(WxnB#Fn%~I?9pE=6cWa^fUTU>0 zqqAh(cmE-3w~o>DJhIux8NZiyfgY($m(GK6Dc}+HwLk(PEy>0RxC#GWV@PHD=Xm*t%Y_demn<4idAsw=3A~$-iGzzW<853^987mq_ALm##OPe7@DYTYEh1vAkJ(Og@JB?~=Pg|HP&(9!Hb|&5Rc_ z4kJkLpXC-D9#p|D5q4_&7N5*Nn@j_tMlex~#g=259)u~z0E5T;WdI-9n|Q~Ld14{~ zQ@rp9?aBb~TI_mLV4DOnXiV59`z?(0Z#OCD3@@gCqb#^D*e}aK^mVMXx;Kj|tN0(< zG$LK=8E8+~eA+3O5wO5oEJWnig!%A#xVyTrdm%mfe8d4`Eej$pf*nnPc=F3M@Hr^GoiEMw1x;0$4)co~KX38u+DI)v)(r!Ov&DQ>B9I-Gh5Y2)881 zf7Z@1NeX;2vK|l!KUfyHpTsk593!eE{Z}4Zm>;+(TgnS0bfrdyS3M2L#Cudt0$Z&H z7K;a~34<}Ae^=9N$N95b8(1wltS8G?_OD88A4Twf8wUarqmmIlARGnw>n>oDj$Xt> zeGv795zCj2aJS0^1xP35!)n_vDvK+~N3IIfalyuj&u#x+IUM}-pJ4LaVf=th88_-k zAank=i!J!djoxy5yE=k5OueiX(-1asg6-%Uw&?6Hi9Y{d59hGz;F`RzPHNA4zf%*> zTL$f;F`%MPKP)c*Ucdc?EJ36k+*7Acq+}t_cQ%Ht&m4*GL3jXY6Oq@2!dz!yZc*S( zC>W`Qb|Dz1gA7QO@anoa7R@V`;zt^$3($_=O8*-rp~XA(cGVGZM{(!ML5WL?zm=u}1SB8Ysy^D%^77>OvZWm`2u z@X1b+mTp}PL2uObwq!Uc`Y&jOk~Y8QrT}*#X9_=Bc_XgSD}3)atS(Q!RyOxTUZsSI z+aO>9;D((HFWv@)k@DFV6>7_g=EhPNHBuP+`MgKKyk=n`Z~VpMii#!{xg|&jtc@Vd zQwE^0{TC5qe>KxOTz7)T@(d#t3CNbwnD z#og>aKh5&F%l1?OBZ-~W3^b!*V3ElcTaD8*>zZmJ56MrKHlfj`e(+-SiHc5GD8Zpez!zy5kZ zb=n;d_|**?7W@ckB=;ARV6R^gj#SD{SbG$YwdpF)WVfEyqT}*XXN7*g#LCyWG`_x2AsyKOEP zn2KdFI(G>Y=P-Ed6sq^OrTbL5TrIyZVxnBPafnhLV{;&ZS-qgrptb4!ng{HjZ}jiF z=xDS5F&!=le*Zb>ND<-1oeYAd)}(_hA zrTxc~DI>~*KEt?xM-zS4CP@*}B~C5ds}2=!O`kvzMWT@<&p({Dl6E2KCoH8ZLw_5I zssfg`srHQ$G;Q!%V`2b#HIYX}Ff*Zw_`%hQ|89J2WczQVhjf=?Ge_xJZm+NEm%hX^ z-8=d9G2Cp9DO20`#Wom^Bh&xJQ6lMn9J?&`HVV7)YGdvPn8=R;Gta_^68DcbbX6P~85rcPtV$@vJhibmXBfU|mB-PcVh7k* zeha346ExfOzJaeGAgrKnv_2}E76>~e(#J6BCUrA$8$)WBfoPn*+8)>HA0HIv(cD_Qz_C-S?tJzAE%f#Cu@BUcUq*a1}k6#596(-9_9j)lK z$RC-Z(ZBOqBd!F_mnU591+e<~;74DJAvm-gaM?+QV0p;3Tb`0m1#XpUg$H@XrKCKs z{O(O`t{D&pwQDh3Q=9$(#*WF$3wYV3Rcdo?2+Dwtk}{^}l8I#&E7`3cX1(jc0ABLHnj#{2oPTwup9zN$_)@=xpO;r|aaruZLKV2=UOd<3_znS?-PQcM&rLy8? zorQk$^;vJzF|Nb7HI5*IW5wlJ`38^K#u%R02>%<$^YflJ?2ZnT_XSEO`LHQ#DlpgD z5=u0c6rNVv9Tn>jH`tzDos3R)Ig8YfS>Kgr(Fn=6EDINZgbrhM5Y;4U_?Nv(J5{i< zHLp%{%TmP*uXWz-en35W9!B+c9gHXbR)^k{mmxlIwt%R^Yx``d*x@iB+bW)Mq)v;2 zS-siy-N1~uH%#^OyOv$i(^?OVws zkyp*`BlM&fyIl<%`Ul^g)GEC$LXNq(F2`gppjY-EJ{Nopm%cDS9F$Y&7|Abpt6WWx z&YqjbFS#!gdie@KSc!TyS$v`@vzRi-_V(&$d7xAqv|?}IlDNeJ#pgAeD63*}ncP?%ej1bRTT> zsKlr$-cKk^uDETp9SOPA*cszIJv{H}o31V2&`wRdpJ+=qvpqxTN-oGazLZ05U+1TV z6;(ff)D+jir9C`!5ht`ABe*paIE#V_iCIPP7P!fF(i7Y!GRS>_M;&~!$-F_#=CJg7 zHcLXS)~x@|#yX{!I%q9C%askrp&c#rGU$zRJj;z%I9^%``BrPXZ<>5LqS(Ns6RVkn zx+@|Z^`*+arE~Dhp-`1c@SPEYM3OL+`AJZEXSOjB`HpC|a7{Ak6GyI0XoiN&v%~E^ zgIw86gUhWbO6eVz2G&MwYP;D;vbwy2zV=@O(ic2lpLO3=9cGG(*tnBfHtS%+$0I!! zV+QZJW!#o=-o(58FJVPIbcweq$SE{a>C-dKVC)*Ft!J{=u5QNbhxxhZhp~~b zFBglx?yqF9&dz)BZQgw-nErKa(y%duEwbJnIQupqv{kSXCeD{ul)8eX@A|CA=VzX5 zn9oL%+735cjLu~XUPbaP^*-;iDIG2h*8T3JGNiN`U$Nu1@#5k=%8r)knQ7gC3FzMf zg`hA&;0U4-@&$r`0TToW(5zosfXgAYD49$pOQ|f#Ei*!;4^m zEp{Z{I6qcwUiWP0n8M{`w&un5_qXQd23OU!lDt@Js0SQ1II0$tRy%Y*0yv}-|k`d7y!aK2`wKW9m%(K29$*%A$3 zJpjZuMiQ&}#Wbo2bV@X~YzOa^(XYqD%;ERY=yU$qYN7mA)8;@{0kq!-YIw;`6^;nT z8C-kcCyRxn4Qn2pM=26j2hei9xkdB7ujvnqo|Z0DZU9Jdgpc96JAq^ETJub=EM!;`gzCz zL2^gupl~3Dj@ttwW-VoI3lYZ!pCf|-FYtIPQ$O2lIauvd+OVl0A++4~Oq{sQ3N3@89?S{{HTd zysmRy&+!<~r=hB#6sfR(se?np)uG7Eg6bZ+53=z*%pogtC23AV5KbphA=RUzjgZgEf&2-7J8R#OB*Zl(XbaB_uU(-Cf>>tKfhT5g@l zEHgb_WQr_{JBKbm-mOxw&Mx&jTtDW&&#&p{vcxK!^TjZ0pzxc4V;h~?QM^;G(}q}_ zKu04_eS{DL)XYOX@4)GJx+7V2e&7^;_);J+zC&|cp8n0p~Qf{}}0ks7|`yxTJ0%PK&Mc5UJ=|qS6 z;5P`b8+`JfNAoP`8GSH*p05@MK_lv*i|qIs31b0Sy>2Mv0e?&RyEX*tDGyamHFTTl zQ?i?wn)F@c%{hFWaPz#!>dXjO*hKaStJ*Z<57w;KvhYEvT~LM_yYWJB=r{>swU3b_wix92{f&l8 zcG{JmYtOUBYuxw-6N+q4f6f7Dyqqa*>D;E6bf_DpyqqCCG&50bARZNjtFQwgT1k|%7m=Ff+Fp7Rk6LoKvW zrKwayEOG^o0@=)ZD_+B02CdLq!nIp!KZ~Yk@W-1hCPeVO&TA^Tc%h))*6!dF1J4M; zDoET)=&9;;yYz?1qNIMJ=qy9@`UK7cWz3@=#nn{-=%THINq-{OiADALp!#F+rVaOU zt7EJFuN%nMs)fm5=LNnj*=o9D#sI_v(kD&C><1WYi1^4d96Tb6B&d6dO>~0~Z#>UM zao20}mDC)5?i=c`e+Jkjq(3i;#Go;W8deF$D$3)en~+8MCCgT!{0{0jNaZ z(Zq3xo6bd>IB|vCf(ixWc!PnOXUUM#o&NErPnF`KWl6bxRl?dMlh{%r#D+F0t=umqIVgeBjmVD_8tGb*(3E%yC|4V^oFVU>ZSusJWmZt@By{(JN zby&T~Ds2NM?8*ogM7OUGIKgga`@!+X$RHF-ZJanaVHfx|S;qL*2h*C7#e zz<6#c9+eetu-{Bh4M8a+%|S9BRyi2!-gkQ0p8s>#7im4H*n|1`h{M?Vne^B!L?l5c z&0&6>MQ(Z@bXV41e1sGL%iAiGY53^p?+#V+k*l_{YqC=^F5he)t)!-Oa)g@Bjg)PG zsuvz-JCqk!3f-F|g8AynWkBjg+nv8p?gSzJw^aNW_#iKdqu*NaHo#s}_1j!n{@$SW z1}0o}1PcLpo-o-H0PDnJ6P2qS+o+7?0ch}ozS@+*Q-|`BTSib_fmT~#)N{5#WVIHn z%5??BkCj~DL3Nx)(O6NerN~)=uXmqgSLMDZuJunplShwT1Hg-{V}9)c%u5=95a2Sh zMc*POz4RlVo#sUv7r3_NQZjbcb+a=+$7{)6M`ars#%`~0mFbm}RMwy|&MzYCyoH0J zdeUbgmnl^i!^?zOBK#7It_wD)LMJlDkhH#bD^l}%r+?ynZ$nr9E){m1Dw8y}aTI+f2@7LIyEGM2__%mRt3CoiHg($_9} z@E`u*knnY)@jN=(Mr{OlE4)Jv??-Ash~I-kR%?+F{E)QOR0U*&Kv9&-=3G}>`PfT+ z(3^4~}^OqV`>p#iwuuyvI zG^vh3K&`idorw+-H?-8Ye|un;)mO1j%^-AnYm|hd>=C}9y^q6QfVPB;9nIG{zn#2* zZv`<&Rbwh_S_`!42ST|vC>_6#m?~ixjA=a%(rO{6u4blFZVwkk%c1`PhsK7Ij?X2> zh!=~V^(6n>h=u>x83CkENX$XcZ?QYBYuo1%>?ngSN4x812%TNq$2y;ssZE6cO}Sn_ znE%c)nPFRgD(s6O<^h&3d3@ViJKt?ZsMn=GDtDvbPFTz`RN#6W;Sp@3KkJdp z$DgGwClB+6C8F2o(#%|__!nb|gS)>oB)8ud&#}RV>PQDL5aA+43GoL@WBFL|0sq+_ zLM0Aho2ci(hLX15om1(lWYm>YtW(K9xFnd&Q`pIr>-=vn5U48u=J`qv!#Cd|^0X0T z<_od>X)0)V28x(sbOGW{L`&%?0uH2&gdvLReGMW7n-<-!P6>mMPy!AP|||GL#e z+@*32WIU2GEGvb#;cyFYPmQ`^$ZMElrmt2Qc)btWUAbl zqk{V9s)QZ%Ut(x~kB~qVQa2*x<#@W7e$fH%2N0>dTRgd72O8@WF>F0KQ-q4mc7FZI9(l3c$~56l8V{ClZ9)QP z=7Gu=znu$HzbYx&eDeBa?*pj#D#J-Bf@ct@C2R-v&)WOKgw?})hRn2=dm7@;yE8bi za?>6k@;0#oScEJXekT>g5^bJ2ssxcA&V?tu4(^^L6l?-5|(8^r`Es82wv zW2UcN!FDC#1I)p|vvPU3l)Y(Zt2zC+)anZuIY0!Hp#?O$v6e3^j*169zG|ZVWpgIH zx-FPotkN;%2iSKQ6FECpIJr3BT0AOY{?SZTg{1rrjOFvy(`e4xe9xQ>IH!EhrXPEr zm%l6__OQZIEsC&$%+oKnSKf6#YO*QqF%E0792YLhGcZtvXBe*MXmq&L*TxBUwU5{0 zA#S+K5~udoAg5Ij{)Eg?L%q<0&W|y?cDpH zz9;Oj5Uudy|>kK3r^CSWQx{f3GseTVaPzQeR9pu3PUGRLVKR zHHFAfN-{n>t#Y44gmroXzXJ+P@WK(i0axG7tT*uuK{NPM88Zu5Vgwyi`Jn)VwmFMS zSLwc|9v&{aT5SOcNwx+ z&Qo_auPcAPdnpgQ9quPMeq%?}Ambd8wq;xR33j8ce4-Qe=%q*1Y~@_4(ZLQJ&2~s6 zLZti88PmU5(r1=`0TNblDL^hUF|77mmIEg!8$_vAtjFJ>UQt-K3__{-G29MG7ZG=a zRYLL@Ske((ilcP-Q>{EIPhsOycHfCmB&Jo&f4gsXjLa)G#pv})s%01zan39bh9|Pf zjd-TBy$S~v@BB_ru|oTpG|UbC6|!<^ZVOMda5t1KgSf?MBD1ZpCEhU*feAL!jPn*S9cDW21H~LIzsFU z*teU?e$|dm_U|rST`M{TSH$V#mx0jXjUGN){=+?)k>O7r+{KIsSH=so=WMar)H$i? zdg|X7-QrYUS34{r3-gEsz}i4bhokx42kVt{w&huZJpxq=tXci(!PA^Z1|c^eJ}-0H z{Bpb4{^)J*aB}fX`)l_xQ-6|gRQ}7X4{L@jZYJfv>ZN-AMH-S z(PbVR{h*DJP0$(U*D|Ktbgza)DHSHG79s8XWw+AJYby=9bHyhr)$s(eeiV1)c0G=@ z^2+tk)pb}7DSn&Tp2#rQQ4{@IBoqM9$pzOGjR_>0j@XO-Ioo>5(cvjcyy_XK{B6%a zxgt>pnvA`qc@4p$sE@=n?cEHXH%eolxsKiTLd1|Sy;Lb+;5Zy8Tm!4#WMGocDIco3 z?sFa%KR6t)`aH2R&~c?afii(aE~DkLwlhsKz_qBiRHnXTHRJfg?4|tuazYSf@^?MMuYFRJwV_PI<%UZPpB)Wx_I*9!uzS%yc7NMXvT$vR(_!>4@#$gXvFUK@<=)_4~ zDCylQ7iY(|*SICG>MQGSvF>_z$N9%6WWAZ`RLiv0?MRTQFUuZ(QfI#J*qbB-olJoB zZe8hO{zd#J7nzFs+FtmtT*P%K3gnIe3ueVSzr1@}_1ohIfGA1QoNrhYNf6O@rLTGh zNK3urR4`T6SOPZ?tgHaY&y$1FxK~TNHHEf!a(p+!@b=4)(n~r7U3)4nviZN zIB+1tJbbZ>@t54`IpH3146}j!4Ls~1f8U?XxSSF!@Dh%18{U>T>~;4ckiH%8_HB zNKk86Sdf6a54rkS*JA6!1PiJ-=KEo3HUpoHU|uV!mvepI6`a;CTia`9bD;lkI!Pp! zAd!f+Tk1t!Hj~}cU5R%}1tq0Y*v5>{ihsYP!$cGplDHSE@scm?Pn1G1nBMTOP-rqT|-;GFhl>Prx73fnnnEP7p4oZSn$n=pk#QHa#7y&tGO+#qt9 z=d>#g-J+4s1t5~vBcvST(rG@4**0Zcqd-Iz5nhs{X1= zFx=T|*D&jCuMAlp<>H5X*A4;%8XptvJvlA?VZ`k}#uElKhATaBUK7vY=0V#luM=J> z^JLF(84OiP=JZc4Xt-WKf0cj>-$FfKC6J(AAPA_S@LUuT8d_y249y?^j!B*G&owlHYF>%rrgL-=9Lb^$XyMQ}=Mt3@BjvA$ z5{-`l@)rdlgDEo9oFEmha+vt)a0^J<`gn7FB;TGM5cS0C>%B;zv3v1?>AWc0bL&0o zcLQwevz~`Vx$*o;JGP@YkmZscU)1n$FW?ALA#no9tHT7^Y7op9U59!ufCm|oV1`Yr zE9mdQpzAHgi){>c8J)qm4v92!y>nLjTP2P^&1b2P`Crq_mdDrBZ*f?)6gMZ3gkDvQ z6^kQ?>fiXnyy=VpwRyNR6Ac3IVWH~##WxSDt<`z)w?Ol9ptbKe~a(L7&xUs7$;agu39dWCLQ}bG_WOf@1MUMz# z-%pqP3m z^uE2nUZ5s}FNHOE>V~Rf?(|)=Ny=FL9lts0j}%H5x4MyQ8P|o(VgWJl+jMpfB=HREI9Q@&jduS$qezqog?MtUesC;T9 z02o+ukb#zLx&+|cPWl@#vsm#@o_NchG&u)^05iW5#gGbt^#3fKDM`Fb0ZOKi2`*9F zo^ly7q&?fx%COmo6*kfH*SRYRDN7dzgQ932*Euev!OvI$!EX{2Te{#k;S~+Q>aK6l zgWT}O|I{*Z7>-n(D}5UCfDCrkcVzd+j??0bb1e{I;n{5S31pt5<*vO=6*Dob%aiLI z`dW$_dDg=+>c}5)Y%CtTaKp6icW3%CxNO8)XXi8DJNfWtWW9qD;Bb@Qc<6i9MEnvuo*v_#m_wjVPE zhmIi2rMOsu!d;q=c3>I!j{We9!EdPR0F%v45`1O%ZF<;3$+7NjPlJJF#%8b_Jbk4) zF(-TV}`0uh~OHwU-doZAWzrKy*>8`KfC?kg64J7}o z@?eAuh#cA=av)DMF?gX`^=Tq-b~P|lv;#V!M#VHd*bO{#CUh_RNAc`)O&Cj_mBW>Y zJ0@I3fP`J@8F-$Ct3T!<7Iqcznj1cM{(-{j`9Gn#aZx*Gb*0bhr%UVQaz%uCy_o%= zoEX90Np1r>=RC0{e8r@H{_|hA|0G{4jSE(Uf08d=@Jk@UoA9t-@LsYukM2Pi51E6U zRG+@Y(bok>W=hir>C-ReC6lvTgNnPaCA}R=@we44>*T+YFrJ6lThAYqlOfoX0qGP7 zeCj_*@01=NX^@rJioP&W<5~&lI*AJ`vs4Tt%PZ`c0m-m zTxepECxCiVbw9f*`j1^DWc+1%QUe(_g~P<@Q}OlZi8!lS<`e;aO)cJQ7qP}G%o7E? zvR=T28#`8@AI%f(TI~!T=@u^0(8xnMo6r7c@5bE^lFX<0Z%lj2?hoj(>EWAwkl}f5 z$**@t&iqE(hwJ6(DfEcCJ-F+!vG;no_-*CgIAKjn)%ZIe0uE6gV3la&Oy?P(M_a(R zBS0x8|F3>JhUMgd5I?9H@{x*r3mj}f2mS6FuoU<~Mzdb>+~Ae11H*C)MV{3tQY@@3 zm#D#25(>MDfy67|1e5N#qjiRuYhv-|BT%qjy`Fl+kHYugti$2qERXxCFCn>f7$doB z?j_IkbC+67$qpCW#-uKfT8U}KW;|u;&@Ro4P~PlW%;Z!qZ>;0eH*1UKFLqecveEhg z!d|j!=`5F;^p!|%t`-jMD3I-CYGT6KRN6thh%j72>0^4L#Hy#+4Rk(6@Ho`kh)dBIEJ+`*9aOY7gv7Y4M+0v}*dKA3uEYT}Yoc!NF{|k2 zlb>U`W9h*L2OC{6ok?e=frg_426pF#4CeUQD|r?01=#(pbkgwtjh9>vhDvl+?QhP& zdo9S0&FVgg>cH_L008ds{$jnbO@#pZSLI+ z2H)(m{8ah?3%qYMtn^0$+b(?izFv-feO+_=znh!)K>vfM?2Sng0j~B##DeX!x0P(8 zQEW`BxD^qFo;bnZ*kr?}=MWt{^|rNwe0^%5V7EHno6^d>kx#Tbtd8W-xoUqOjVPc6 zm=zd0H(7!Y-VtE+0BDnvRV!undX|QsaeHm`2EdYnOO2vN;1snM#hj`i%c0pXs^DCb~gb1XoBU#KmL{3i2iV$S~UF<>Oznsf=%K9;tiBoMg;_%M#r;y-NR1AE@Cx z4Wz3fMg3fVp@TnB{pzLDU^#WqQWUNLT~vo1m2&dy!6Sgo$6PVU-vz?~@P)EG5&fNI zwR%%IK#*2WbE--2*o|=nu_=GK9nN;iPALI`N$n5f8S$jQd-y{=^K&WvJ;{#|oNS=f zhyZfX_9F~hCgbdQ>6;=A(N)pPBpXz}3;j@$lwYX>4?tVtV?FyQLklkBWJQW4u_Syf zW$-5sH}6j$<~CkvBur~7G>Q4Gls1l+!?G|w*;~LB$rApR%-=nIH#GNF{)hX#8RRkW zY7h^JXhQ8a`o*DU;NgzI7i&W3W;)qm&b2K$z%HS>TG<9Sf_~ft8ktnJCO>L%W58qM zQ!;#M;Xq>b2y4TxSMXN%bvS|FxRP;IY_A-=tGr^@$jbcy1BY3~m0=() zD+$SDf&mwTDe-^lJJmg_t$V5Ke>3Q#KN3PG7s12Ri%RYPXsSp#WO+>A z`HEh(BakBW%{E*zbXYG3T@hhDwi7-+3&qNpjMw_uZw8$?XSNW*4u_Rf-;)Fi0G1{T zfPXp|Ld%v7%DJU*G!=_m-?7cvhC-zlp+vU5jqWsj(B2Hq?JP~j-zO5m`TLECX4>jy zJeG<#lbGe73PyNc<15qvIQ^gF9QIUBgU|$VN2crirYFwK?X#6GVrf7j|LyO<%q61o z#$N(Og`S9kJ7i5C>Ti%bfy*N1ApcRn1_IUgkXL} zBc%E-N$XhtKS^4(YbRkyGL+1db$rM?^v_Su==7)fs+?+$9J&18=3;zz{UZdhmzS-T znu5J~ObIq&R2#jl+1!*@{8%%P8i?8H9LVP?_rhAWLm#^R{!lYiQFK$n@m7_zh10&W+nGB;vz_(og5#s=r^wwaw{yYx5xn3QKM*@!I!)$wc@*B^eF zoERgJ@H(EQ-JhK}CPBk3sWOqdG0t=kZjB7;jDzKJ0Me}j-uX*DVTSms3I7MFo~U5g z`qi5{>rUo;{D)QlzlR{NEz6i2%N%aWa7J?g7>(g`Z@0(5o=+&j0>FKlW+3p!^gO6cB^p#p3 zP(xgr%-C(a!S#1?1n=pcvHHIE|NMyn-8U1d#2waju#1N0z8WreL{+>_R`0BO7LuOY z9`tB@UpE_UGQ{oAF$p^T9=h-L?HP-Z+u<9(8PKG2?Lv>CfKi|7K!Cv56-_E`iH75~ zQRYj7PR;67FO{?U;dv=8x*fJBCxitfB6c38ZvPDsVt)dJgRQwG+5a9O8k9pov7sw! zrS>z*@Ml?$!BK%%C2|9WE(-^My+tNaUkg-vbzTQ$$(x^cc`<^jvO6nw2X6Q}1pV_H zwYU?@zw8YLvQ%ormSFU@m!E+{k?7>ls+Hc0xiJ-4`{(Gqb5H3-3uG{Ln?4w|^&i77 z{sS8N^kz-~w;f#rv?ol_s4NG4iM4lTU12Q0-M86Qp~)Js_9yyv&TdT6qC>I#*A#zx-Z@ z&flkZeiY0;!%I-Brz*I+u%7C@7%RnksoXwfWQ9tLtY`L;P&G{ABEG2x)K=meE%DgakZ zqRr17pJ-9%fqUvWuG)60I*po zYUQv3)fqfW3^Z@c6L4ino-q$L0)zD>Oat_UV~c61Z_2FGe+ONN|MYObPu(tv3L@CN z0QkBDDy4tFE)dMR#D87#r-Akzr-rZhH^xd3vhY{em0ai_KD92hc4?&nFgHueO5MSy zB)*Rv2pv8Ud61YEY=KL)j|*Rsx7VjuibA-@<$&KzT- z4X*wUd;+NW1mrHxe#{K;D)-Ao@bEKZd$p2j_SeW@u-g_Gc>2G7la=~guiX1vudMy= z^~yN`p*bH?Gwf$3q%+(;;%23zuKL8{^$IJc#g1@1tsxD2=%R<0S9Bk2U9PN(o6NAc z1dfpnfCXOw!v*`G3xY1$z+ocV*P`2FahfH3*d(9=_lD{IG#mldr@4MgJfcw$G}Sd^ zN`E3XFk;;~{cV^-M%s7Wx{A#o?9W7-j~(uZVUPBTL?~0Y61OUZp@xB&y`sda1=iBPzM+E)H=p#mw(d!W&h*bju?KT0p=Rqq=Mf&+pjf0BiKo}_ zB^L0+IY?tVKCST2$Mxng4I;l=!D~a`;rC#XFf{6QS|%SOkCzo%V0UZBHg*&WS+U@ogl)&5})K@Qxsl zkegrPfg`9BHWAxcrm?-4tZLq>2FW$zLTlxHiAWI1%XqYT{qCLpj}+m3{5Fcf&-Wrr zSRFOErVb@p8D_cUx(ZJxMrL^q%AyUIb4zk@NsLMOAN{K5gOES>?w|i#pCrndM3n={ z)nx&d?JqGY?%g(^dolwkTQP36K20+4n%eg7Aj9#;dy89*=&B2Z(mXejmSCPXs(`^W z%`#eEInvJSn5!C@7oKv7RF7)oEY)^$-p3g-dCp(H^-Kl30W=G)ZxSE-rULBH$RU3f zZE>Q`U-iEu0*rw46sNBvNmoq(24bL;$csmF)W*)*j* z+6-VKV!fagc`c#E+to-U1>SQ3l^^*#lEck@CqT+$w-Y(jm1pxTl13onw`TUkkq$uW z=^kVX4n^i_bw8ua1=C;u>IH9|>ILt=@GfBRcyhG2qi?hC!KMghUpQGD%8MuRXbuV{Ydt`IPLvi{n!hove)9&9NLHnE`4y zCPKX))~joO?rg0f{6T5JDJf4zc%HhXfWsz(1@K>syo()*!3OK!WIYrzru_h2DI6$9?vK8fGWCPXtv@01uz4 zSMoC7KD_gq665H^XDb?E+~g2&37!*i?bK8iWddF2Co8J)+7;oY1xKh{eB#IgwjPt26z+yK5wd590HEQN>dr6os49sFv?F=xgH9N@G zbbNE{xl}6(j6dwP;n`1zi`DY&4YT_56Ey4x+9$PYO7up2(0|&AxrEmJ)Z5;#DnWJN zcpSk_y<@%7#?S>I8P(8|Hp;uNw(Z3dHgfSLmblwzP&alzUN3 zE1cb~BG=muXqbK!@<>3}yr0mD4q8>XpuqhRrGlI zx+tn}&ABcxYsU*)kZ1*|3}gFRsDi{BG6mVi@0rRv(-M_9-1| z@T;jHo!D#G)Y4nXAM7BB{iA*RhON=aLx(8iaxp;aVWlHYm$#!=~$I7$+?*OmEf1DEwK8yItR zRP)Tc!lXhN#P1%IJ4Ew?mG=Ph6*cr27IsOUYY)p8&zdV-2+=y2U2mJ&EvSbhyFa-; z61I*A)hZrWUYia_(iua_!2|qGE{K=d$VzQ3)bgm{I!iE zVg97$M{_`y#|S(s64NWnP)ylz-Ku)CJ0AQ>Z3e{%9yHnuWjb}FpP?N2d9ALHM(={&HtwUIwGkx7V0SSmf5q|NpFqVe2iz^9q=sc*;o7(>?z)2u;ZL#fa-&XRkRr zbe0_oEKz`sJB7}N)=(zMYGjb}7ZFu%C=TNg5-}SOJjlgH?MvuW7aC%tMCuOJ6ceFv zbR2>H1W<_LjlEw$58<}f#$ZUe=#p)e@LReougPC&AG_TI_q}j6KyPcngy;&$$8~m41xu%K1r^re3E~RsM8UlW z#8uGLUU7<_)b-A@JY2iSYd+WSJY&A~BXqTvt>2%hD9Y;Kr&QlsE3KjLr(L(uy69&U zh)kI#1!DFwjwbtxM*=*$%pLn19q$0clQ>mTW+;IQ-`Qq!=aMbxc#~H%o}aKh1}11w zavx5xrO|y@z~1v7zmasCH$8SImKyVj@!yy+_a|mt94|^*-5Vh6=y2N{LGqnowCZ4! z{VwDD!PO_6*jr0Bd3w6tkOT9bbX_wo4;Ocmgmf0rg>59&O(7Z@jPBa?@7mWnE{d^Q z2->YdAKr#icSMBAy@j4xEwtrn`&IRz{k z1!3;VygIbbom_@S8NW6bFFR;q-K_La>@DtAe7%%_8@zSW)<9+UV&qehKcM&q7VipG zZqh4#A+x~i`F4nIAA}b3ZG?n=*Fui7rV909zEs?%aGrq%lARU%SJR(%#{`%Sbk2~xsWK^ z1IQm>Tpc?ptBn5&(*jPMdpG&+V?-#ygoNAWUOT!!^_&+kCNw@$8tdYT4&kYJUY%N)9(vHHCi5=JNJ~gL*G)hr9dh zDF(0hBRY{=?Cc77KAhsKHEk(d5Ebkl)h{wdm!t@7S0*S zVas>c3j)dWtbFH>6wH(wUDY3a`G&km^&@0J5Y0w7U3Py`)QGTM{ zoFKmu$8NIy&JN&a9Y7sm21L@uz@e36H)1@qh{XHnRC?@@Kj8L;TE;)}Zx1~>`n_5o zjW@?}rL5xA5t6;opCZp(h~~Y(@CbdtW;26g>y|~LDDprM?^0NFoxjSbF(`g~cyy4* zp+D{lYqPo6T zlFqj?sx1A|pjAM>3U97!%A#SG-s7=>dmtC%SwE(Xb82y#5=WL8pPiJDOWQx|=@Qfy znCVbLPA`WplppP!LmNMEE8f2KZG1V&%fpS3qPWh(`2Y;0DQvAjqO-KxZD($et`{%#%Va!3u0FP`HA*+krjd}y0Tuj_H#1FiL#@f)!P!=t4t2P$w=3lqt^M| z?3I#Ot-AT-B$q^bhagH_LEh!DQUwwW+Cn)dmCAWbM9LxlXMILXIBN%>oR35jU6(%_ zxNYirg(`dOk{s;bSbwi~bRbjk{@s%2bQP-k$tbRn zGc11hgK+4psgZ{1XyYh17CF7{C7~A|B1Gomd1GUDoIqOz#$(X+dWdKP=SIrG4)J0f z8vqzdFwwYhzpzwFStOCPWwxy9yC zSjk!WN1<3q_9!51H%h`UIREu=-K_GF1r^ur&KHeOonFJ($U z*B4&8t}1Hc3vE!0Yz>}i{U(LDfeO>Hf=(Xo@HfY<4X77et^ZC!qrzpydY^bSCc6e% z7uss~sa*C18cKO!>0+VgKt5PZV(YS+h_)?v^glk7>#5x6>}QbK5KJYsc`Aen7R+6( ze(qhvtqp|Bm7tt{VC#ZH*lAe4n%Pva?bwDxzwGCb-%*pzFFb|Wmeu_0ASq6uLh>2pR~sKX+(Bc z$vfOsGdBqY^XZ6|kjer)MqpE(Fkx(>(+`hdS!#Fzi!8-E&!4p(Lz^~ZMnIOHcZs`? zN-!V#p(>GoGR9)MTzzhQp}-~0E@-C`J5SvHO|g)+Z2M8@WmO5>L|nH|1m*51i*moA zevo^v%!47_i{4L8pN0i1>2<+uAPki1^>DG&DFTYK9Fqm9u&unoh1**T%v=kkQQPk~ zD%$8v5+$!(C+5r+uwr(9nj%B`&o*INJe18GJ-NK2ndpnL`-X&t9~xUfC4(6f9vb@!@VzciY| zg#*d5`JHWcDJaKNnD~It4Px(7zWLE%_w_f}ueY^+mY*#dI}8-krNd^K*`LKqGxL!qfB50~ zAWefN>`U?&z92pYa*B>8)KYhs5=LetHfmas@IZ#rkKhr}Cv~2>hi!*1(mogpdDhg>KX}fV9q@`ntE%*Ha}HMQ zwr$%b;x`COFWO(kG}g=UD!LDwNsDno^%;_7Svm7{8l(6>NH6v!yk znE;6m)F`_>TM`{_GDFgB~}n@84b|2&?^`PYLdCSX2l%n=7A) z9Ur}DC5pHz{d=0*@Dn}Lyt^*|0-m&d^c(PhGjs1u780m&L|;8`{!u?(*dVPhEbTF@ zu5v&1)ua?DKiR_7Y*97>_m3ssT!HiD2^$@sCrn$Vw`OX1-ALCA!3sWJ!8V0=guT2q z$xL@bv-C7OwevVy6r-+>AHxa^tj6mJaNBx?jEA__7hRTUFA0gpN$+|UT86Dxm9byjEr;qQLpS~j_!0tAAYjq&b=9&rf z`+VOcq5Gs9T&T)QuFS{O*fYd8^p2h+MZL;Z@EL4*f=MlKtvT_Rfv*#jt@MYI5PGM% z*-Rdqu9ZM@xm0(^^XLgi#j>Vw)fHbB5&c9FJKCM^{aI^qbDU7 zq#Z|gy_H>`lv%&L`#=>};Z{;4)kB&P*;0xl7j^9-)(?1h_U1?tCz>TsRSEA?hEbmo*|Faw-tkO0tSGt+o%cb{~ ze^YqLG2-3x%QxTAd5`An6eJ3_nqOx%IO%r()YV> zC}T#k!^5*vZc{l0v3KZ81P@mwVn*}yza~veevK%GWXlMMPmPq`$J8bVE)&0_$}(rZ zwm#a~vvZ62VWx%8_F$FEOQOCM0qTeMm={BXqXLb(dV0dVU`uQUPRc_!-c{TpCZZ*f zW6YD~cd)narhZ}9@-bkb^f00O2CW)FK@ZclpOxbwH$M=WIAO*=4NtQ)=b&VVRf97% z&$xZ8xJ)q@8xT=g>)|ih+p}u&UWBu2V9D8GZ6Xoq^N487V&!fSd?UGuc(O)a=xF|7 z?v^s+7y8SC)axKxH`4|y8`PV3eOfmou)TWr8VmW z%mQ>hK7Gn*4c=6)pYrEve&{aWjQU4@@Uib(n58}pO+x!ppv1Fy8kqfM+J83gS-Q8; zkv;1bHI`nw_RW9t4QI2e{BO4%OWq25Ln*A(&`zv`#0w5l^3TFXDwkEHM8-(NP@nrk zK6kn}cSW&2CV8>isBeECQNfk!nn?Zex~tsz4D7NGAsK=s%-`RlmR^iG(NJ(l+FyLE zu6Dn~*mf{SFA`$KDNJs!?Zk=|wB-D4$mysLq^QKQDsHfvh|>z%$N4 z?oKV`l3KOX+$-Z9k?>5tWSf-Bt5)MhH#Hb#~n=mlRs_o zOTz&cTc~_9M!wOP&~&^WbGQOOgZUz222YL;;W`Y#$+glo+g(~K*MX?=iM(G@7ZWM$ zau`?`8H`|SJVTk`+4-LFp`OuwM>B)8==g*3A~@pK;Xke`=g_$f7WbYN$Ir}G>g(Bl zTegjPGv+esB=o@BJI7GqV;^5Ah$g?*KHk}B>M-KTuLh%9(%Mb@W%ytotF^6RRFekp z2%hSzi5&z$63tYx>l3+a zx&5p`&h+axg-wrD{xoOXKuRc0L4M0yAFt<#;4wAHKi{f^Q(WSL^ zTDAJRm?SFp#);vIT3jO`*}}uPF{e-syI$tErjU`x1bOgK33^u91X+kivPd_JT|nb+ zsUCm2)ms8*?Y%`^YT7R58#9pIm#?De%V(6PdX)6r&OFEv7$L5OWj^WC{z|Nqr`KoS z9B`9N{_rJ3u9W=ZAZjC?uG2!WXjCs3~VlW1}~uNOY#=_ z+~z~Bs>0)DxAvUe#m|7(-kvi1DVf`L@|EeKQesIg6 zZQHiFhJZYlEQa5=J2qfop-*NJvPxe!ewCKrh9&w5MS<5vFV-hWq zKcsJw%Z#y^1+43fA`%lwVz#nHy+Uj!YIIvaB+gix9jIeQVz9Lyzk2Ft4WOXl4iWnBe#g`=9s9zZ7@1FM&s$6#)rlEEdA^_>rJu#NnrHzy1k|GcuB7?R4kg zAK_j-eYU~tx+UeIOnTN#g~{j24|SW)yUA}Ib@dCM8)`5*FqUQE$;R3uMZt% zC03_d6TXI2{A#$=9Wgd1)&qUYY@?G&q$*l9?{e%(GW$?Altk!7bpodU^$rGd)EYNT+QT<9ywOGmYFkN#=_NcJIqEF-e#ZGqj!j^0aj^a9Cu=_to2Zz+k8 z@BR_&;Zhgo06J<*V9vHEJRm=9E-$6_&e?@WjOsgqik|6m431?~w-;*LC!(3W#yIEs> zf2eZ5z-#hCIDM1t*`s|qKLP9O+}tXPMy~hWEW{xUD3$mdlji1fcG$FB-&ZDetQc-1 z1N)Ais7u+JytLuPn%V8!-_GsmTqy>fl-WR9bnHv}_g!^SmV(LSHOW+gcbdA0)^itV z7Ta0l?7a>!uQaZ^s4@AIwK?^PIe0XY^jeUcr_M8q5jjH8@V@CCv{U2egx5s|=hmQ` z@RX{6H>Uj-%nDLw>~UtUd{%dD_s5j8e^)NDkzs)du#bP*ZGLc^DeK-H4<3Vg1uS}3 z`n*X=^{Rkj%fa&S#ZOFKdSzEseIq#DTx6COu4WO~uf~kcw~iuXVo^pmYQL*|uYbF$ zl3G7Ae0yUcFhXg2xV#?~D_p6C{c5??S>e+o)Fygh#D72V1N87c;SZ1CJKCZNriu`_ zo9Df3`LeEqh8)>UyU&jE?;7Uhb1m9c#C)l^o|PKCzaQvXQCr1?R_OOxJK_xs7kBZc zQ?*_3-OXbOoEJo0Rp>Z#c6yJCQ(N{8ZX|5<1|;%v&qGh0IOj&`S3&Yi-=vhSP0NQ| zg2sfYL+gfJ3USuCLkP2Kw#0kv{+bbo8gx(3gM=eVCQsu1L});5gST=Um9ug2Ox(PU zJIXMTBx2`@ZFK{uX}GdunfvnnteYRFOAfw!?=>tAcK;vJ-ZCo6u^vo{o=>0H9GfwUFYnx z&)(-`&+tBb+{}xoITV;*1YxD zHp9p9>K8%i3Ebz16R!`qSa_xH4quq|_`;BrT`tBF-Obc0Rn2`a_FDR$%ESw$`wcgJ ziSV3kO!A0a3t|X~)dcb!D!xV2+V1_3EEq7ut{fIhVrTcHn3M?gy4z3VU(1c?5AzR7 z*(3??Uf6M0P?;Bq)yjXyGI_jK`1aA(btlQMsFK#bXqmGoNd`2dWcOsOW!0-G``FO~ z(=u6uK5Fxk;N^S<0KIhT`O~ismS5klsFD`BRFKD%WIy@(KuGo?lBl`hR;>_3@T zFnqmAykV!v|1qK|CWM?=<=$N@j8yBw@;!YBih&&pFx=aom3#dA5sS&Rl4ypd>! z^H^wU zr_O_BsD^iMgCAAQ_7}I<6j9Etfa$va*jKR$wm&lyYqmEu{EoL6Wvo9Kn|@;%I1fI# zQTx)aQe&LA{rajEYH5sdduC*=Rk_%5rxGIwc*$t2)n$C%{z$-6PD`D3YC)$uJK*MgWmrF%Sz5vz(9XU`b_h`--196CtB)R zi+)ZfuG~9tsSH9YQ;jc;lS#?;jdU5XF$W`i3*dw0i*j?v?TwZ@+WZv#dQm?pEpDPD zgOHn?;}h4pVhhXii+eq9GOY@I-A-Q4hh(D)k9U}z7yg=IeW=$>zs+9=ovHY6Cy#QG zlt-jpx80DpKi%Rg>gV%&Yos`zNsBuYgIE^hl(C#FJQa>MgG6#OEri{^JuDbt-B7v1 zIIUIRt11m)3B{Q3yz#q3S+u&K* z?5#J}X`t=j4OE(BU`C{B6DNG(2k8RE@J8zhg1@=2YXx;Wqa!e);`r7#O@cio34ozo)S?y@dTl^@2) z(|I|JX%yq!I#e(hk1L$d-kUQn-ID0IAE#(vIXpzGZs9ogDX{8^6!SR|mwTdqDHsNF z_Ce5hfOOZDR+#CFu0&nqx3aBYZK@2^BT}A~Js$aFb@#CF-HrhOoSp7?YjmlTUR01| z+^>@P#V@6(Ot)4kFNZbn=9-|1O!QfPm{8Ko@~7E82cs6EAt>w2x^5gM{kK$g*3+k! zOohBv?;10_s|@3P+fmiRPSr!%JA-}e*mtw(!ANNZ8D&vm1Qos_$0HHf%YYv(ONbXn?WJtgdh&??`Grl!*jLeAQE2EOkmM*E*S35WiF*VjZLPc zYb@K>oTtTVf-4(wdVP&~Wt4oGA#16q{DN8mufEGpXg+PyV~%NOAj_`oL{F9>?cL6& zSI>%b$Spg(c{$fk-jmR!9rGsCsO?DOJwGH?~#e&%h-l~e~=x}*91uJMN}99ktZ z1!4B5j`N%RmE2wQYu_{d&*oOU7>(yc3Q_%5r#x-1T{D{VjWH+T)@lxdEWM^I&7MC$ zky^<3kH^VlkYMd8R`)q1u75C%Ngl}eMgk#B2KTDTy%I08=rq4^0|`w#Zjyxg46&e~ zZ~~ru3sU0=?D)|9AkzSA_i=+SjZoITb={JyBM)iH@_%(gsepWB;gZGu_YZ$6ZpKtk zqD<$>xz`Cz)|JMtqlGctjbRkexz*ZHR=DK4@GPB{}ZYP!d@ z?%~+hq*I13YFyO!WoQUCHHqA#x{Wle)MtW~OX3A3!)svbmh(C+Z_G&Zs*oUA>1#|r{3k7Jmw%wXf3K~VC^=dYl; zQPr~+(Ky#JW54+uu4^i5A<+kA04|b}5?AgSycnV&K)GVA z^5E)hi!TA%!>>&4`Qr5cG_+ZFW6XJv%)`^&V)~{#Zzd>zA49(IpN zQ+{BA;2Pu(L{TC8?#YoKMHV;Z-Hj_CkjUEa*J_~kO~gkDL}90#OpD3D+lCw4$&JggTA-!@O5_4=Ap4{Vg70No<7c~MCP^Z1Nub6{)_(b(C z7SS1}+xgW!iYIS7%e;Oi(+|_rT~I3(x^9@mB0JM>#|s!IiVaXnE#qav>D>D@J-$yG z)!T6~J&&`r;uPGT5Ao$Ln5+f)OedFa6lG2lD8T5*)vem6SN@ zs)Z8pmixOs%4}QkJGNDtk;)>IR(!xQ^vV9S%fp-yIWdoZtn^6go5^r?#g14Rf4n!_ zR~lZ&c2y9qChKyZiCvf?=gFK;zpBbdOCg>oQ$J)|SkJ+LeZ;k=>5oZ^bnfyY>!Yci zJM}b3SWUnE!>_!lvu_x>v7xeKSDtJD8QYj!0fPS$%sB6DZ)$L~FWY_;C;NilJ<%63 zW&6U(w!=X5$P)+dmm+5b~H{>F3j$f6G++H-R|HcA?!G&LvLKE^yV&X`5R%YHUmVj zv)0bWd5xwfx0S}dA2WwwPs}wUtl)B$Msh;jupt*0Uj>zj_){kbG4w{5^kFi@`kj3d ztNbr7G!u;6ZdSed0Hdt6AD>@HYZ{f`!u&$Qnx3-D^=jt!TLb)2dWng{YJ^9`9)p%bBH4RT^4kfQl&EFzT5*+yqTxE+bLO1T3sWi-L17z1m$L^ z_d)b9cT|h552O`pRFDgdX$<`DX`T`DB_(}0`)~nCn#Bhgx@$6U@qtw`_2-_rO+MTY ziIG$&pG2xdWlgenh9pHW^I4F^RU3Bk?U1!$Gym0=Pdiw_?p|)&Ngr94GK$G`pz$-Y zotymbUNp8cwzBM%cjP<@-h z*c`eKZ~|i@*=s3ULj%P;Rl8!0rH6mZ#By%>H%_2TywP&g6Kb%V6?KQ?LH$JVgcCf2 zX2iSVn@ZFwT)ew;57flU+ddwjoz8ScP2To3?}!ZB0ScGfpV_Ar{uibls zmRsD0GL8i`ntxD#qM(egx`$vND|=||Ky7&RG=$fdai*~OiU#lX$b%a`k%V$7p1Fue z8*j3NqdtoZPWksHlL+r6VEDDc$u$~4%<$4+X-RCB%W{f`L`O4(FPEco#Q zaGKO|mMrM4^XG=!B2fms_oO5@dmlYa^uYlbUiph>qD^3<>cG6KrqYg;=t>+$N zOb{1!A*ssgB<~t`yM)w@?gZ5&5(zr33vO6jOqL7$tl)Kju}i(C9TmJU>OQ}e1-a+$ zXDhEqOls0l=8mn{Ev!$Cw>*sp)g?mr+4(mi@#~Z5mr{_yg^ch6=B2t?8=CC$Oe*2-XVJRw0mx@ z=KiygE>aRKUS0zuZ+V487Q*eDIhZ(%uo6l`w6zl8&0m~pTshgUHka>NjP4Cg$}ISn z>HYu~@BN@J&{Ac+9B*Cxx?D}NjF^wj7%Ls)c;8l^%PPJFbeD*N($Z|_ z1YUV8!5!M3tntJ$6B?LW=QZ8h5p6-iYiHE)$=xFKTgz97{faYI^wJvfvhOg3i-Ecp zgGb~!I`u3rMx*(MRo+6;!V19+xXS3+uN>+9??E`k#zQ403G|()BLqHNR^z5_Qxj;c zwCNCP&a8!l-mb_vvs~G9wpk|AO+orX9y?|!&Z&JMpQ!SN(H<09uEousR;{cOdb>+~ z3pd9nQ>H%jI<}=+=vUo*P?VRLe;lrD&{qzI-O{jVM{|(dwsyTYHQcV`1=A?F99l=Q zRD~5=(CrWx{e1U^-e?|De&x^&t6LP1of-P@0`*s)%l1(-7eOb*hH9&(p1R-hT11gk zq;^NlCo|jfle^>m!PK4TL&ey8ZsP6!&i0I>P9wMK{Yl3Ql=(SH2hHWIWI0@ae6H0f zND(DdZ>pJTuvC*8ugs#FP9I@m#)C%-oXp^>a7{a1cnTEg*6u!PVc_UQ1i(=C+G=wd z5Rf>iiYwjG^B+Xh9EwuyTjzWGrv7afst7C#e4HG-*{N0=?G)e6HDf78Vqx8nY==ax z#|k9mF>L~8Ki_FhD?JFg3Cp|QSM1|n+gn$ruf_axi>z|zdfQ&3%wv)sPGDYut}lYZ zEgf-9Uq8>bd1si8bYao7+G&7vvequ$Ny1n)$}Wf2#+nfwG{0tA;F< zf=`1v`3PsdZKUX#N&DqXeV_ZGSaV zO+SL)ox5BhH7A5Hfe$#2k3e44RDF{Vi$H%cBx-2v zb31;%&3Lv6ub=Tk`G#X~Wh#TqFAme;3&byFk@n^df_=Vb^IIW=7Il_XvYJINu;-zu zziibhJ)wp2AMfhTWeV$=gXI8-1c>@~E7@7}by3P#{gDJ0MX9N=H<&aSW6!OZ@@^H7 zq49KJ9++)YfPu*^Y-CQd{t_zU;T1ZxuK_V+OC2!TG%Cd}{#fv))1A0qB1@A^RtZ$` zxN@u2(=~W4jC-C^wm9l1<~TH1CRlsnAKg0r%UmobtIVS&v6?ixTMqVnIpN#Q${Osb znD;Z%Xr!XWZN)~K`GO4}1zhiSNt{_MZPwZKi|caV*>C|?6OE#`p+@Jz0T(ZY9LeA} zU6T*#23jab+dVf}H5k72KB1J@QA~_=nW}py8O{Giia(*M^U1`c>x+%Dgxjq?vP!pZ zz6&*j-Jw~f9k}u6qbDn89qV?ts(*@>SnLGN5vww7UMDKP|YBT2Pon`zYc5H$`D8=c}u`oaS2y5!OBkPU?hSk_f z&>PIJw5*pu@A=pi5tI%)fb>-+fYs>HNtc}Bp?2JUeV6H#le*&=ET$#5lc`n(Z#@s! zYVgmh82C*avg@V6grtv8*R3-Y97%sc#H4mC z9NMhrs8(6;??tyRVpDO>u~x#~cER}O@aRjyzv5jmYA)1lZ_No!GFf~2<8M~`!SfW} z&QATpqR4T+-ALg&&++>}QqOGD8!nm&KTk7FdFx2O)13DKq8a%b`z{n(3n(*9b}?KA z8CjLn{9LDY=k8QNk$&zj&Ox_f-rcdJleYX6Kd~@=_5BJ?)?i^snd~o&wSEdSvk~bm+fz@js@Z6 zXu{gIUr2EJ(~6e9c8Aj1>XAYPa9m*Y(>xF_-U}OvoQ1v)> z^f2@WqLZ0!dU7f`-5xoNhES7Y-yf~@9G9MEy6N>stY8!W-9;Zul>AFlI#CUj@>d4d zdW_@ALC=7g);GS7%~x4TFNc2O5JKY58S<2glG46W#9xD;r=G45R25mF8+wunMDhI|6uUgL- z-7fZ=5kEg?xvdr9b(>@C6&t|wnuEDF+l}8?40$jpheGUZgw|2pq$&hSL{E5PlWei~ zWLygiD|6K^JB`2UwtF~8HhAVAED5GZF3!7K>o<>mfV`fduhuh$nA@Y&e-yqH91n@H}l#PlWBOW$;h$G4#a zZnD`B zS__qADa*SDpZhG^(5r9{XfyXv;U4aUxQv7FTz}>LY>w(hAYJ#8(!c z!sLUZW_Hp665#m9E_7-(s6jHNQi226-HejK`rPQg&21(nE-3IeAXp=*8@;gpAZWLB zzU$2CZjv?)w_mGz!=FHPkcKb3nQyT=`pIoW=!iwur#MlyhT#oLm<(uq^Bia~TTp;D zR|?^cTdH+-EF}pv?oi?ZjRL^gWFT6Dt6>QwJol@DsJ0)gT-cp}{XC$LOezI!7WL4b zk#b|*nUfQYr^coDTfwv)4UB6k_^+^BSdq;fKUzPWak(k*NI4^CtHw&yy9PRIg^Awl z5J_f}<3{OJR4A9GqAu|BUVm3ox01T5dQjQZIO-K=2&={74OFF$0zK;RbnPNa12B8R zx5Mi>SBIM20?h_O26{@EKdSMxnbREld1Kj)7%eytsYBn4*=1Nla&px5vDHl+ams)o& zk0k{nIBxVOo_Z1@m3Vahw2y#pg3eb|Q6`eZss$HDDaql?{s^^%hD2U&^EnU6#E&@Y z6z)!`hlo+h~)t5 zSY_-m1Qb9dp)q%5E0(+lajBos*(MLcT%~7NPP1*&knmXGRMAMowb9mm)o09G-LbW*;KhvPXYch8G@Y{l2mmXGh5@OW%hP`-&~d8JHKI}@XTpEpZ};=u zsw!TBVGZW8L(U97@2JVq2FW?ec}ytG^8TP-L-}C8OTFuqL|(Sh!Dcmm!h?-FysMAu z*x|&5-1d*r2s`K|C*MBp3`{AK8N+E^ED0~s&ftkP3wH_eq@Ape@f=9^%$i{5xD6NB zu2!`581SoL2sqnU%vB9}$HMTAz2!&ZPpaJENImD-=ym7RGbx|@VB?$g|v_oz(249Fxx?ji{S9oMT z`HFie{s0nXE2P3I-Xfdn@>zy*RYsmZKAZGYs&1%?a0jbi->+E6J#X=$lK1`f?|;^@ z57K~R#Rpv21UMAin6*YL@qS)*_Zv@;Xu$`teR0pm>%^r#@w+YZH(98TIP-|N)N8`~ zyzau8<%EtzG{{uTwUKQ93!}dZmD^$_?=G-DQLjCC7>I`V$tBXY>Jew77;)_*InTnRP;f?)Z)0)b#d;bT3V7Ez1Y3%CMPm#U zPS^9{&3oDJ4!*bu{MpN1c21k0 zVu-^izCOkypW{SbzM0qv8v1!2rVmB;6z`g{Q{_ZxuHe28E%VrazVQx9h3lG2o6xNa z3lP<+FN41(klN?6JW$?}A1WRWLte8!ug^g^Z*80=PsHSLtQVoQG05iNKqdH>*<;(~y{By}0n`JCd(HW&}%$z^LD8EMQT?RmD?xx61b)%yF_o@4*bEGUA- zOBN^u@`Xhhw4tBATV%zJQv}TDFFzaP`Mk|pAkaaDM!ldNQ%5%1 zoDOkE%QRvFm{~8H?9v~w>uG%ih0()}QB1)|gvd2GzoaMt=y^PY-C5$iIe4;4m0kLr zO6Pow0Tm3fK*bT^PnN}!IRS#`pcQ3eWZSR~)m8XWB%A{bKR*lauuC z_Ke!E*}laHsRJTB#VaE56Ip-2&l@^?cDm6lcONoQ6CQ3bSQz+w-E3X<(|~Qp*|VoP zqNpu?XHoWExx^9T*kORuFyExU{~Od?XqeE69^jn6kI5JZy@x&KqJ!VW)iXd`eS9fc z?n?QpdikfIn^I~N4YO9_z;IzHVDoY%rL`J&DwB(YRq=gGfc z%k}qA_lL)k%z|G_Qe>U|_j>>SgGLbu81GwQ7acl_=QyI=P@b@bFEgj6WU@l)?Rl@)^^T_l0u~d+Z{Hye!3Gv(`}~1XM3cGr z_X8wjWYl&&$Dcs@9DI?$CI^$>LYTAEo4Z~~ycwJKUo>o-@0g9_h;|#bUz$5g0ldKm zWPmiw#acWm-aYLB*LLgT4(AsxE)+UCI_eElcNTxTj6e(NGs9#31J2HD5Maq`#nol2 zPyOe$_r~Ayo&(d4<~wHf!`-iohV#$weO^F95u5!kRngR%!^n`fWWg)Xgk!SH{dmhI zcs`EM3+$ZwfUCxfI`GUc$Bl05jivigLSzgvPt39nT!-wzi6gFmV)6y4H) zAqCv5KhZT@OoS2{;M#iz15P&M>J2@`d4UU1YF8G&8?!qu!Fn_v2=Q3*_Uh<}Qy(ev zFpmQ5q(tqft-4+?yN4C*4?3X;cvwSH0gd{cYTkCnGUqg4rs|sq`9MP+w`iNpJ*eB| zQNaqAL@B(!P~}~WVSvHa)q(0DlR9BqAI=fjP$8+19PLH&v)y)t5pkC3fu*(@5;KdJ zivF?In$DummI?hKE3pe!`R)0ZgXoe}p~(?>F532(2h6&fDq!GbLglZ9^-&sKX&TX< z_)jj&3^QK^wx;C4)`UfBVp#)k=;rA{1nI2!;R(1iE|288SZ&50?K=SnTQ{H+xYQ&n z(Ugk0t*C1q8mO$|3CQ$7uh16^v!ohtMf-Bp0VbZ0-Q!!biE2W|K-+g0k@=Hn&A;RO z-^U(C^(U#}>z(*ofw$83sVs>3f8Gf≪WPXi|&MUV_!i_mkj;C<-aVRdL@ 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 literal 0 HcmV?d00001 diff --git a/inc/core/sigfox_crc.h b/inc/core/sigfox_crc.h new file mode 100644 index 0000000..783ea89 --- /dev/null +++ b/inc/core/sigfox_crc.h @@ -0,0 +1,116 @@ +/*!***************************************************************** + * \file sigfox_crc.h + * \brief Sigfox CRC driver. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_CRC_H__ +#define __SIGFOX_CRC_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +#ifndef CRC_HW + +/*** SIGFOX CRC structures ***/ + +#ifdef ERROR_CODES +/*!****************************************************************** + * \enum SIGFOX_CRC_status_t + * \brief Sigfox CRC driver error codes. + *******************************************************************/ +typedef enum { + SIGFOX_CRC_SUCCESS = 0, + SIGFOX_CRC_ERROR_NULL_PARAMETER, +} SIGFOX_CRC_status_t; +#else +typedef void SIGFOX_CRC_status_t; +#endif + +/*** SIGFOX CRC functions ***/ + +/*!****************************************************************** + * \fn SIGFOX_CRC_status_t SIGFOX_CRC_compute_crc16(sfx_u8 *crc_data, sfx_u8 data_size, sfx_u16 polynom, sfx_u16 *crc) + * \brief Compute a CRC16. + * \param[in] crc_data: Input data. + * \param[in] data_size: Number of bytes of the input data. + * \param[in] polynom: CRC polynom to use. + * \param[out] crc: Pointer to the computed CRC16 value. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_CRC_status_t SIGFOX_CRC_compute_crc16(sfx_u8 *crc_data, sfx_u8 data_size, sfx_u16 polynom, sfx_u16 *crc); + +#ifdef BIDIRECTIONAL +/*!****************************************************************** + * \fn SIGFOX_CRC_status_t SIGFOX_CRC_compute_crc8(sfx_u8 *crc_data, sfx_u8 data_size, sfx_u8 polynom, sfx_u8 *crc) + * \brief Compute a CRC8. + * \param[in] crc_data: Input data. + * \param[in] data_size: Number of bytes of the input data. + * \param[in] polynom: CRC polynom to use. + * \param[out] crc: Pointer to the computed CRC8 value. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_CRC_status_t SIGFOX_CRC_compute_crc8(sfx_u8 *crc_data, sfx_u8 data_size, sfx_u8 polynom, sfx_u8 *crc); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_CRC_stack_error(void) + * \brief Generic macro which calls the error stack function for CRC errors (if enabled). + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_STACK +#define SIGFOX_CRC_stack_error(void) SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_CRC, crc_status) +#else +#define SIGFOX_CRC_stack_error(void) +#endif +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_CRC_check_status(error) + * \brief Generic macro to check a SIGFOX_CRC function status and exit. + * \param[in] error: High level error code to rise. + * \param[out] none + * \retval none + *******************************************************************/ +#define SIGFOX_CRC_check_status(error) { if (crc_status != SIGFOX_CRC_SUCCESS) { SIGFOX_CRC_stack_error(); EXIT_ERROR(error) } } +#endif + +#endif /* CRC_HW */ + +#endif /* __SIGFOX_CRC_H__ */ diff --git a/inc/core/sigfox_ep_bitstream.h b/inc/core/sigfox_ep_bitstream.h new file mode 100644 index 0000000..a9464f3 --- /dev/null +++ b/inc/core/sigfox_ep_bitstream.h @@ -0,0 +1,222 @@ +/*!***************************************************************** + * \file sigfox_ep_bitstream.h + * \brief Sigfox bitstream builder and decoder. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_EP_BITSTREAM_H__ +#define __SIGFOX_EP_BITSTREAM_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +/*** SIGFOX EP BITSTREAM macros ***/ + +#if (defined UL_PAYLOAD_SIZE) +#if (UL_PAYLOAD_SIZE == 0) +#define SIGFOX_EP_BITSTREAM_SIZE_BYTES 14 +#elif (UL_PAYLOAD_SIZE == 1) +#define SIGFOX_EP_BITSTREAM_SIZE_BYTES 15 +#elif (UL_PAYLOAD_SIZE == 2) || (UL_PAYLOAD_SIZE == 3) || (UL_PAYLOAD_SIZE == 4) +#define SIGFOX_EP_BITSTREAM_SIZE_BYTES 18 +#elif (UL_PAYLOAD_SIZE == 5) || (UL_PAYLOAD_SIZE == 6) || (UL_PAYLOAD_SIZE == 7) || (UL_PAYLOAD_SIZE == 8) +#define SIGFOX_EP_BITSTREAM_SIZE_BYTES 22 +#elif (UL_PAYLOAD_SIZE == 9) || (UL_PAYLOAD_SIZE == 10) || (UL_PAYLOAD_SIZE == 11) || (UL_PAYLOAD_SIZE == 12) +#define SIGFOX_EP_BITSTREAM_SIZE_BYTES 26 +#else +#define SIGFOX_EP_BITSTREAM_SIZE_BYTES 26 +#endif +#else +#define SIGFOX_EP_BITSTREAM_SIZE_BYTES 26 // Maximum value used as default. +#endif + +/*** SIGFOX EP BITSTREAM structures ***/ + +#ifdef ERROR_CODES +/*!****************************************************************** + * \enum SIGFOX_EP_BITSTREAM_status_t + * \brief Sigfox bitstream driver error codes. + *******************************************************************/ +typedef enum { + SIGFOX_EP_BITSTREAM_SUCCESS = 0, + SIGFOX_EP_BITSTREAM_ERROR_NULL_PARAMETER, + SIGFOX_EP_BITSTREAM_ERROR_MESSAGE_TYPE, + SIGFOX_EP_BITSTREAM_ERROR_PAYLOAD_SIZE, + SIGFOX_EP_BITSTREAM_ERROR_KEY_TYPE, + SIGFOX_EP_BITSTREAM_ERROR_FRAME_RANK, + SIGFOX_EP_BITSTREAM_ERROR_MESSAGE_COUNTER, + // Low level errors. + SIGFOX_EP_BITSTREAM_ERROR_CRC, + SIGFOX_EP_BITSTREAM_ERROR_MCU +} SIGFOX_EP_BITSTREAM_status_t; +#else +typedef void SIGFOX_EP_BITSTREAM_status_t; +#endif + +/*!****************************************************************** + * \struct SIGFOX_EP_BITSTREAM_common_t + * \brief Common parameters of application and control frames bitstream. + *******************************************************************/ +typedef struct { + sfx_u8 *ep_id; + sfx_u16 message_counter; +#ifndef MESSAGE_COUNTER_ROLLOVER + sfx_u16 message_counter_rollover; +#endif +#ifndef SINGLE_FRAME + SIGFOX_ul_frame_rank_t ul_frame_rank; +#endif +#ifdef PUBLIC_KEY_CAPABLE + SIGFOX_ep_key_t ep_key_type; +#endif +} SIGFOX_EP_BITSTREAM_common_t; + +#ifdef APPLICATION_MESSAGES +/*!****************************************************************** + * \struct SIGFOX_EP_BITSTREAM_application_frame_t + * \brief Specific parameters of application frames bitstream. + *******************************************************************/ +typedef struct { + SIGFOX_EP_BITSTREAM_common_t common_parameters; + SIGFOX_application_message_type_t message_type; +#if !(defined UL_PAYLOAD_SIZE) || (UL_PAYLOAD_SIZE > 0) + sfx_u8 *ul_payload; +#endif +#ifndef UL_PAYLOAD_SIZE + sfx_u8 ul_payload_size_bytes; +#endif +#ifdef BIDIRECTIONAL + sfx_bool bidirectional_flag; +#endif +} SIGFOX_EP_BITSTREAM_application_frame_t; +#endif + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) +/*!****************************************************************** + * \struct SIGFOX_EP_BITSTREAM_control_frame_t + * \brief Specific parameters of control frames bitstream. + *******************************************************************/ +typedef struct { + SIGFOX_EP_BITSTREAM_common_t common_parameters; + SIGFOX_control_message_type_t message_type; + sfx_s16 temperature_tenth_degrees; + sfx_u16 voltage_tx_mv; + sfx_u16 voltage_idle_mv; +#ifdef BIDIRECTIONAL + sfx_s16 rssi_dbm; // For DL confirmation only. +#endif +} SIGFOX_EP_BITSTREAM_control_frame_t; +#endif + +#ifdef BIDIRECTIONAL +/*!****************************************************************** + * \struct SIGFOX_EP_BITSTREAM_dl_frame_t + * \brief Specific parameters of downlink frames bitstream. + *******************************************************************/ +typedef struct { + sfx_u8 *dl_phy_content; // Raw bytes from radio. + sfx_u8 *ep_id; + sfx_u16 message_counter; // Message counter of the corresponding uplink frame that requested bidirectional procedure. +#ifdef PUBLIC_KEY_CAPABLE + SIGFOX_ep_key_t ep_key_type; +#endif +} SIGFOX_EP_BITSTREAM_dl_frame_t; +#endif + +/*** SIGFOX BISTREAM functions ***/ + +#ifdef APPLICATION_MESSAGES +/*!****************************************************************** + * \fn SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_build_application_frame(SIGFOX_EP_BITSTREAM_application_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes) + * \brief Build an application frame bistream. + * \param[in] input: Application frame input parameters. + * \param[out] bitstream: Computed bistream. + * \param[out] bitstream_size_bytes: Size of the computed bitstream in bytes. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_build_application_frame(SIGFOX_EP_BITSTREAM_application_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes); +#endif + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) +/*!****************************************************************** + * \fn SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_build_control_frame(SIGFOX_EP_BITSTREAM_control_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes) + * \brief Build a control frame bistream. + * \param[in] input: Control frame input parameters. + * \param[out] bitstream: Computed bistream. + * \param[out] bitstream_size_bytes: Size of the computed bitstream in bytes. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_build_control_frame(SIGFOX_EP_BITSTREAM_control_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes); +#endif + +#ifdef BIDIRECTIONAL +/*!****************************************************************** + * \fn SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_decode_downlink_frame(SIGFOX_EP_BITSTREAM_dl_frame_t *input, sfx_bool *dl_frame_valid, sfx_u8 *dl_payload) + * \brief Authenticate a downlink frame. + * \param[in] input: Received downlink frame input parameters. + * \param[out] dl_frame_valid: Pointer to the authentication result. + * \param[out] dl_payload: Contains the extracted DL user payload if dl_frame_valid is returned with SFX_TRUE value. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_decode_downlink_frame(SIGFOX_EP_BITSTREAM_dl_frame_t *input, sfx_bool *dl_frame_valid, sfx_u8 *dl_payload); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_EP_BITSTREAM_stack_error(void) + * \brief Generic macro which calls the error stack function for bitstream errors (if enabled). + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_STACK +#define SIGFOX_EP_BITSTREAM_stack_error(void) SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_BITSTREAM, bitstream_status) +#else +#define SIGFOX_EP_BITSTREAM_stack_error(void) +#endif +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_EP_BITSTREAM_check_status(error) + * \brief Generic macro to check a SIGFOX_EP_BITSTREAM function status and exit. + * \param[in] error: High level error code to rise. + * \param[out] none + * \retval none + *******************************************************************/ +#define SIGFOX_EP_BITSTREAM_check_status(error) { if (bitstream_status != SIGFOX_EP_BITSTREAM_SUCCESS) { SIGFOX_EP_BITSTREAM_stack_error(); EXIT_ERROR(error) } } +#endif + +#endif /* __SIGFOX_EP_BITSTREAM_H__ */ diff --git a/inc/core/sigfox_ep_frequency.h b/inc/core/sigfox_ep_frequency.h new file mode 100644 index 0000000..a216047 --- /dev/null +++ b/inc/core/sigfox_ep_frequency.h @@ -0,0 +1,157 @@ +/*!***************************************************************** + * \file sigfox_ep_frequency.h + * \brief Sigfox uplink and downlink frequency manager. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_EP_FREQUENCY_H__ +#define __SIGFOX_EP_FREQUENCY_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +/*** SIGFOX EP FREQUENCY structures ***/ + +#ifdef ERROR_CODES +/*!****************************************************************** + * \enum SIGFOX_EP_FREQUENCY_status_t + * \brief Sigfox frequency error codes. + *******************************************************************/ +typedef enum { + SIGFOX_EP_FREQUENCY_SUCCESS = 0, + SIGFOX_EP_FREQUENCY_ERROR_NULL_PARAMETER, + SIGFOX_EP_FREQUENCY_ERROR_FRAME_RANK, + SIGFOX_EP_FREQUENCY_ERROR_SPECTRUM_ACCESS_TYPE, + SIGFOX_EP_FREQUENCY_ERROR_RANDOM_GENERATION, + SIGFOX_EP_FREQUENCY_ERROR_FRAME_1_FREQUENCY, +} SIGFOX_EP_FREQUENCY_status_t; +#else +typedef void SIGFOX_EP_FREQUENCY_status_t; +#endif + +#ifndef SINGLE_FRAME +/*!****************************************************************** + * \struct SIGFOX_EP_FREQUENCY_uplink_signal_t + * \brief Frequency computation input parameters. + *******************************************************************/ +typedef struct { + SIGFOX_ul_frame_rank_t ul_frame_rank; +#ifdef BIDIRECTIONAL + sfx_bool bidirectional_flag; +#endif +} SIGFOX_EP_FREQUENCY_uplink_signal_t; +#endif + +/*** SIGFOX EP FREQUENCY functions ***/ + +/*!****************************************************************** + * \fn SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_init(const SIGFOX_rc_t *rc, sfx_u8 *ep_id, sfx_u16 last_random_value) + * \brief Init the frequecy driver. + * \param[in] rc: Radio configuration. + * \param[in] ep_id: Device ID (used to randomize the frequency algorithm). + * \param[in] last_random_value: Last random value (read in NVM at device start-up). + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_init(const SIGFOX_rc_t *rc, sfx_u8 *ep_id, sfx_u16 last_random_value); + +#ifdef SINGLE_FRAME +/*!****************************************************************** + * \fn SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_uplink(sfx_u32 *ul_frequency_hz) + * \brief Compute the next Sigfox signal uplink frequency. + * \param[in] none + * \param[out] ul_frequency_hz: Pointer that will contain the computed frequency in Hz. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_uplink(sfx_u32 *ul_frequency_hz); +#else +/*!****************************************************************** + * \fn SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_uplink(SIGFOX_EP_FREQUENCY_uplink_signal_t *input, sfx_u32 *ul_frequency_hz) + * \brief Compute the next Sigfox signal uplink frequency according to the input parameters. + * \param[in] input: Pointer to the signal parameters structure. + * \param[out] ul_frequency_hz: Pointer that will contain the computed frequency in Hz. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_uplink(SIGFOX_EP_FREQUENCY_uplink_signal_t *input, sfx_u32 *ul_frequency_hz); +#endif + +#ifdef BIDIRECTIONAL +/*!****************************************************************** + * \fn SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_downlink(sfx_u32* dl_frequency_hz) + * \brief Compute the Sigfox downlink frequency. + * \param[in] none + * \param[in] dl_frequency_hz: Pointer that will contain the downlink frequency in Hz (computed according to the last data given to the SIGFOX_EP_FREQUENCY_compute_uplink function). + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_downlink(sfx_u32* dl_frequency_hz); +#endif + +/*!****************************************************************** + * \fn SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_get_random_value(sfx_u16 *random_value) + * \brief Return the current random value of the frequency driver (to be stored in NVM). + * \param[in] none + * \param[out] random_value: Pointer that will contain current random value. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_get_random_value(sfx_u16 *random_value); + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_EP_FREQUENCY_stack_error(void) + * \brief Generic macro which calls the error stack function for frequency errors (if enabled). + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_STACK +#define SIGFOX_EP_FREQUENCY_stack_error(void) SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_FREQUENCY, frequency_status) +#else +#define SIGFOX_EP_FREQUENCY_stack_error(void) +#endif +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_EP_FREQUENCY_check_status(error) + * \brief Generic macro to check a SIGFOX_EP_FREQUENCY function status and exit. + * \param[in] error: High level error code to rise. + * \param[out] none + * \retval none + *******************************************************************/ +#define SIGFOX_EP_FREQUENCY_check_status(error) { if (frequency_status != SIGFOX_EP_FREQUENCY_SUCCESS) { SIGFOX_EP_FREQUENCY_stack_error(); EXIT_ERROR(error) } } +#endif + +#endif /* __SIGFOX_EP_FREQUENCY_H__ */ diff --git a/inc/core/sigfox_tx_control.h b/inc/core/sigfox_tx_control.h new file mode 100644 index 0000000..4512962 --- /dev/null +++ b/inc/core/sigfox_tx_control.h @@ -0,0 +1,215 @@ +/*!***************************************************************** + * \file sigfox_tx_control.h + * \brief Sigfox TX control driver for regulatory operations. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_TX_CONTROL_H__ +#define __SIGFOX_TX_CONTROL_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +#ifdef REGULATORY + +/*** SIGFOX TX CONTROL structures ***/ + +#ifdef ERROR_CODES +/*!****************************************************************** + * \enum SIGFOX_TX_CONTROL_status_t + * \brief Sigfox TX control driver error codes. + *******************************************************************/ +typedef enum { + SIGFOX_TX_CONTROL_SUCCESS = 0, + SIGFOX_TX_CONTROL_ERROR_NULL_PARAMETER, + SIGFOX_TX_CONTROL_ERROR_SPECTRUM_ACCESS, + // Low level errors. + SIGFOX_TX_CONTROL_ERROR_MCU, + SIGFOX_TX_CONTROL_ERROR_RF +} SIGFOX_TX_CONTROL_status_t; +#else +typedef void SIGFOX_TX_CONTROL_status_t; +#endif + +typedef enum { + SIGFOX_TX_CONTROL_RESULT_ALLOWED, + SIGFOX_TX_CONTROL_RESULT_FORBIDDEN, + SIGFOX_TX_CONTROL_RESULT_PENDING +} SIGFOX_TX_CONTROL_result_t; + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \brief Sigfox TX control driver callback functions. + * \fn SIGFOX_TX_CONTROL_process_cb_t To be called when the TX control needs to be processed. + * \fn SIGFOX_TX_CONTROL_check_cplt_cb_t To be called when the TX control check is complete. + *******************************************************************/ +typedef void (*SIGFOX_TX_CONTROL_process_cb_t)(void); +typedef void (*SIGFOX_TX_CONTROL_check_cplt_cb_t)(void); +#endif + +/*!****************************************************************** + * \enum SIGFOX_TX_CONTROL_check_type + * \brief TX control check type. + *******************************************************************/ +typedef enum { + SIGFOX_TX_CONTROL_TYPE_PRE_CHECK, + SIGFOX_TX_CONTROL_TYPE_POST_CHECK, + SIGFOX_TX_CONTROL_TYPE_LAST +} SIGFOX_TX_CONTROL_check_type; + +/*!****************************************************************** + * \struct SIGFOX_TX_CONTROL_parameters_t + * \brief Parameters for TX control check. + *******************************************************************/ +typedef struct { + SIGFOX_TX_CONTROL_check_type type; + sfx_u8 bitstream_length_bytes; + sfx_bool last_message_frame; +#ifndef SINGLE_FRAME + SIGFOX_ul_frame_rank_t ul_frame_rank; + sfx_u8 number_of_frames; +#endif +#ifndef UL_BIT_RATE + sfx_u16 ul_bit_rate_bps; +#endif +#ifdef BIDIRECTIONAL + sfx_bool ack_message; +#endif +#if !(defined SINGLE_FRAME) || (defined BIDIRECTIONAL) + sfx_u32 interframe_ms; // Tifu, Tifb or Tconf. +#endif +#ifdef CERTIFICATION +#ifdef SPECTRUM_ACCESS_FH + sfx_bool fh_timer_enable; +#endif +#ifdef SPECTRUM_ACCESS_LBT + sfx_bool lbt_enable; +#endif +#endif +#ifdef ASYNCHRONOUS + SIGFOX_TX_CONTROL_check_cplt_cb_t cplt_cb; +#endif +} SIGFOX_TX_CONTROL_parameters_t; + +/*!****************************************************************** + * \struct SIGFOX_TX_CONTROL_config_t + * \brief Sigfox TX control configuration structure. + *******************************************************************/ +typedef struct { + const SIGFOX_rc_t *rc; +#ifdef ASYNCHRONOUS + SIGFOX_TX_CONTROL_process_cb_t process_cb; +#endif +} SIGFOX_TX_CONTROL_config_t; + +/*** SIGFOX TX CONTROL functions ***/ + +/*!****************************************************************** + * \fn SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_open(SIGFOX_TX_CONTROL_config_t *config) + * \brief Open the TX control driver. + * \param[in] config: Pointer to the TX control configuration. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_open(SIGFOX_TX_CONTROL_config_t *config); + +/*!****************************************************************** + * \fn SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_check(SIGFOX_TX_CONTROL_parameters_t *params) + * \brief Start TX control operation. + * \brief In asynchronous mode, the check completion should be notified by calling the given cplt_cb() function. + * \param[in] params: Pointers to the TX control parameters. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_check(SIGFOX_TX_CONTROL_parameters_t *params); + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \fn SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_process(void) + * \brief Process TX CONTROL driver, this function will be call by SIGFOX_EP_API_process just after the process_callback has been sent to process TX control interruptions in main context. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_process(void); +#endif + +/*!****************************************************************** + * \fn sfx_bool SIGFOX_TX_CONTROL_is_radio_required(SIGFOX_TX_CONTROL_check_type check_type) + * \brief Indicate if the RF_API is required to perform the given check. + * \param[in] none + * \param[out] none + * \retval SFX_TRUE if the radio is required; SFX_FALSE otherwise. + *******************************************************************/ +sfx_bool SIGFOX_TX_CONTROL_is_radio_required(SIGFOX_TX_CONTROL_check_type check_type); + +/*!****************************************************************** + * \fn SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_get_result(SIGFOX_TX_CONTROL_result_t *result) + * \brief Get last TX control operation result + * \param[in] none + * \param[out] result: Pointer that will contain the TX control result. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_get_result(SIGFOX_TX_CONTROL_result_t *result); + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_TX_CONTROL_stack_error(void) + * \brief Generic macro which calls the error stack function for TX control errors (if enabled). + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_STACK +#define SIGFOX_TX_CONTROL_stack_error(void) SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_TX_CONTROL, tx_control_status) +#else +#define SIGFOX_TX_CONTROL_stack_error(void) +#endif +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_TX_CONTROL_check_status(error) + * \brief Generic macro to check a SIGFOX_TX_CONTROL function status and exit. + * \param[in] error: High level error code to rise. + * \param[out] none + * \retval none + *******************************************************************/ +#define SIGFOX_TX_CONTROL_check_status(error) { if (tx_control_status != SIGFOX_TX_CONTROL_SUCCESS) { SIGFOX_TX_CONTROL_stack_error(); EXIT_ERROR(error) } } +#endif + +#endif /* REGULATORY */ + +#endif /* __SIGFOX_TX_CONTROL_H__ */ diff --git a/inc/manuf/mcu_api.h b/inc/manuf/mcu_api.h new file mode 100644 index 0000000..6909c92 --- /dev/null +++ b/inc/manuf/mcu_api.h @@ -0,0 +1,339 @@ +/*!***************************************************************** + * \file mcu_api.h + * \brief MCU drivers. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __MCU_API_H__ +#define __MCU_API_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +/*** MCU API structures ***/ + +#ifdef ERROR_CODES +/*!****************************************************************** + * \enum MCU_API_status_t + * \brief MCU driver error codes. + *******************************************************************/ +typedef enum { + MCU_API_SUCCESS = 0, + MCU_API_ERROR + // Additional custom error codes can be added here (up to sfx_u32). + // They will be logged in the library error stack if the ERROR_STACK flag is defined. +} MCU_API_status_t; +#else +typedef void MCU_API_status_t; +#endif + +#ifdef ASYNCHRONOUS +/******************************** + * \brief MCU driver callback functions. + * \fn MCU_API_process_cb_t To be called when the MCU driver needs to be processed. + * \fn MCU_API_error_cb_t To be called when an error occurs during MCU operation. + * \fn MCU_API_timer_cplt_cb_t To be called when a timer ellapses. + *******************************/ +typedef void (*MCU_API_process_cb_t)(void); +#ifdef ERROR_CODES +typedef void (*MCU_API_error_cb_t)(MCU_API_status_t status); +#else +typedef void (*MCU_API_error_cb_t)(void); +#endif +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +typedef void (*MCU_API_timer_cplt_cb_t)(void); +#endif +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +/*!****************************************************************** + * \enum MCU_API_timer_instance_t + * \brief MCU timer instances. + *******************************************************************/ +typedef enum { + MCU_API_TIMER_1, +#ifdef BIDIRECTIONAL + MCU_API_TIMER_2, +#endif +#ifdef CERTIFICATION + MCU_API_TIMER_3, +#endif + MCU_API_TIMER_LAST +} MCU_API_timer_instance_t; +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +/*!****************************************************************** + * \struct MCU_API_timer_t + * \brief MCU API timer structure. + *******************************************************************/ +typedef struct { + MCU_API_timer_instance_t instance; + sfx_u32 duration_ms; +#ifdef ASYNCHRONOUS + MCU_API_timer_cplt_cb_t cplt_cb; +#endif +} MCU_API_timer_t; +#endif + +/*!****************************************************************** + * \struct MCU_API_encryption_data_t + * \brief MCU API encryption data structure. + *******************************************************************/ +typedef struct { + sfx_u8 *data; + sfx_u8 data_size_bytes; +#ifdef PUBLIC_KEY_CAPABLE + SIGFOX_ep_key_t key; +#endif +} MCU_API_encryption_data_t; + +/*!****************************************************************** + * \struct MCU_API_config_t + * \brief MCU API configuration structure. + *******************************************************************/ +typedef struct { + const SIGFOX_rc_t *rc; +#ifdef ASYNCHRONOUS + MCU_API_process_cb_t process_cb; + MCU_API_error_cb_t error_cb; +#endif +} MCU_API_config_t; + +/*** MCU API functions ***/ + +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_open(MCU_API_config_t *mcu_api_config) + * \brief Open the MCU driver. + * \param[in] mcu_api_config: Pointer to the MCU API configuration. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_open(MCU_API_config_t *mcu_api_config); +#endif + +#ifdef LOW_LEVEL_OPEN_CLOSE +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_close(void) + * \brief Close the MCU driver. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_close(void); +#endif + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \fn void MCU_API_process(void) + * \brief Process MCU driver, this function will be call by SIGFOX_EP_API_process just after the process_callback has been sent to process MCU interruptions in main context. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_process(void); +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_timer_start(MCU_API_timer_t *timer) + * \brief Start a timer. Timer completion should be notified by calling the given cplt_cb() function. + * \param[in] timer: Pointer to the timer structure to start. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_timer_start(MCU_API_timer_t *timer); +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_timer_stop(MCU_API_timer_instance_t timer_instance) + * \brief Stop a timer. + * \param[in] timer_instance: Timer to stop. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_timer_stop(MCU_API_timer_instance_t timer_instance); +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +#ifndef ASYNCHRONOUS +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_timer_wait_cplt(MCU_API_timer_instance_t timer_instance) + * \brief Blocking function waiting for timer completion. + * \param[in] timer_instance: Timer to wait for. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_timer_wait_cplt(MCU_API_timer_instance_t timer_instance); +#endif +#endif + +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_aes_128_cbc_encrypt(MCU_API_encryption_data_t *aes_data) + * \brief Function performing the AES-128 encryption algorithm with specified key (for uplink UL-AUTH field computing and downlink frame authentication). + * \param[in] aes_data: AES data structure. + * \param[out] none. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_aes_128_cbc_encrypt(MCU_API_encryption_data_t *aes_data); + +#ifdef CRC_HW +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_compute_crc16(sfx_u8 *data, sfx_u8 data_size, sfx_u16 polynom, sfx_u16 *crc) + * \brief Compute a CRC16. + * \param[in] data: Input data. + * \param[in] data_size: Number of bytes of the input data. + * \param[in] polynom: CRC polynom to use. + * \param[out] crc: Pointer to the computed CRC16 value. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_compute_crc16(sfx_u8 *data, sfx_u8 data_size, sfx_u16 polynom, sfx_u16 *crc); +#endif + +#if (defined CRC_HW) && (defined BIDIRECTIONAL) +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_compute_crc8(sfx_u8 *data, sfx_u8 data_size, sfx_u16 polynom, sfx_u8 *crc) + * \brief Compute a CRC8. + * \param[in] data: Input data. + * \param[in] data_size: Number of bytes of the input data. + * \param[in] polynom: CRC polynom to use. + * \param[out] crc: Pointer to the computed CRC8 value. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_compute_crc8(sfx_u8 *data, sfx_u8 data_size, sfx_u16 polynom, sfx_u8 *crc); +#endif + +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_get_ep_id(sfx_u8 *ep_id, sfx_u8 ep_id_size_bytes) + * \brief Get end-point ID. + * \param[in] ep_id_size_bytes: Number of bytes of the ID to read. + * \param[out] ep_id: End-point ID. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_get_ep_id(sfx_u8 *ep_id, sfx_u8 ep_id_size_bytes); + +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_get_nvm(sfx_u8 *nvm_data, sfx_u8 nvm_data_size_bytes) + * \brief Read NVM data. + * \param[in] nvm_data_size_bytes: Number of bytes to read. + * \param[out] nvm_data: Read NVM data. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_get_nvm(sfx_u8 *nvm_data, sfx_u8 nvm_data_size_bytes); + +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_set_nvm(sfx_u8 *nvm_data, sfx_u8 nvm_data_size_bytes) + * \brief Write NVM data. + * \param[in] nvm_data: NVM data to write. + * \param[in] nvm_data_size_bytes: Number of bytes to write. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_set_nvm(sfx_u8 *nvm_data, sfx_u8 nvm_data_size_bytes); + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_get_voltage_temperature(sfx_u16 *voltage_idle_mv, sfx_u16 *voltage_tx_mv, sfx_s16 *temperature_tenth_degrees) + * \brief Get voltage and temperature measurements (used in control message payload). + * \param[out] voltage_idle_mv: Device power supply voltage in mV measured in idle state. + * \param[out] voltage_tx_mv: Device power supply voltage in mV measured during TX operation. + * \param[out] temperature_tenth_degrees: Device temperature in 1/10 degrees. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_get_voltage_temperature(sfx_u16 *voltage_idle_mv, sfx_u16 *voltage_tx_mv, sfx_s16 *temperature_tenth_degrees); +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_get_initial_pac(sfx_u8 *initial_pac, sfx_u8 initial_pac_size_bytes) + * \brief Get device initital PAC code. + * \param[in] initial_pac_size_bytes: Number of bytes of the PAC to read (full size is SIGFOX_EP_PAC_SIZE_BYTES). + * \param[out] initial_pac: Initial PAC. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_get_initial_pac(sfx_u8 *initial_pac, sfx_u8 initial_pac_size_bytes); +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \fn MCU_API_status_t MCU_API_get_version(sfx_u8 **version, sfx_u8 *version_size_char) + * \brief Get MCU driver version. + * \param[in] none + * \param[out] version: MCU driver version. + * \param[out] version_size_char: Pointer that will contain the string size. + * \retval Function execution status. + *******************************************************************/ +MCU_API_status_t MCU_API_get_version(sfx_u8 **version, sfx_u8 *version_size_char); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void MCU_API_error(void) + * \brief Function called by the library if any error occurred during the processing. + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +void MCU_API_error(void); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void MCU_API_stack_error(void) + * \brief Generic macro which calls the error stack function for MCU errors (if enabled). + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_STACK +#define MCU_API_stack_error(void) SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_MCU, mcu_status) +#else +#define MCU_API_stack_error(void) +#endif +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void MCU_API_check_status(error) + * \brief Generic macro to check an MCU_API function status and exit. + * \param[in] error: High level error code to rise. + * \param[out] none + * \retval none + *******************************************************************/ +#define MCU_API_check_status(error) { if (mcu_status != MCU_API_SUCCESS) { MCU_API_stack_error(); EXIT_ERROR(error) } } +#endif + +#endif /* __MCU_API_H__ */ diff --git a/inc/manuf/rf_api.h b/inc/manuf/rf_api.h new file mode 100644 index 0000000..b941347 --- /dev/null +++ b/inc/manuf/rf_api.h @@ -0,0 +1,347 @@ +/*!***************************************************************** + * \file rf_api.h + * \brief Radio drivers. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __RF_API_H__ +#define __RF_API_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +/*** RF API structures ***/ + +#ifdef ERROR_CODES +/*!****************************************************************** + * \enum RF_API_status_t + * \brief RF driver error codes. + *******************************************************************/ +typedef enum { + RF_API_SUCCESS = 0, + RF_API_ERROR + // Additional custom error codes can be added here (up to sfx_u32). + // They will be logged in the library error stack if the ERROR_STACK flag is defined. +} RF_API_status_t; +#else +typedef void RF_API_status_t; +#endif + +#ifdef ASYNCHRONOUS +/******************************** + * \brief RF driver callback functions. + * \fn RF_API_process_cb_t To be called when the RF driver needs to be processed. + * \fn RF_API_error_cb_t To be called when an error occurs during RF operation. + * \fn RF_API_tx_cplt_cb_t To be called when a frame transmission is complete. + * \fn RF_API_rx_data_received_cb_t To be called when a diwnlink frame is received. + * \fn RF_API_channel_free_cb_t To be called when the carrier sense operation is complete. + *******************************/ +typedef void (*RF_API_process_cb_t)(void); +#ifdef ERROR_CODES +typedef void (*RF_API_error_cb_t)(RF_API_status_t status); +#else +typedef void (*RF_API_error_cb_t)(void); +#endif +typedef void (*RF_API_tx_cplt_cb_t)(void); +#ifdef BIDIRECTIONAL +typedef void (*RF_API_rx_data_received_cb_t)(void); +#endif +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) +typedef void (*RF_API_channel_free_cb_t)(void); +#endif +#endif + +/*!****************************************************************** + * \enum RF_API_mode_t + * \brief RF modes list. + *******************************************************************/ +typedef enum { + RF_API_MODE_TX, +#if (defined BIDIRECTIONAL) || ((defined REGULATORY && (defined SPECTRUM_ACCESS_LBT))) + RF_API_MODE_RX, +#endif + RF_API_MODE_LAST +} RF_API_mode_t; + +/*!****************************************************************** + * \enum RF_API_modulation_t + * \brief RF modulations list. + *******************************************************************/ +typedef enum { + RF_API_MODULATION_NONE, + RF_API_MODULATION_DBPSK, + RF_API_MODULATION_GFSK, + RF_API_MODULATION_LAST +} RF_API_modulation_t; + +/*!****************************************************************** + * \struct RF_API_radio_parameters_t + * \brief Radio parameters structure. + *******************************************************************/ +typedef struct { + RF_API_mode_t rf_mode; + sfx_u32 frequency_hz; + RF_API_modulation_t modulation; + sfx_u16 bit_rate_bps; + sfx_s8 tx_power_dbm_eirp; +#ifdef BIDIRECTIONAL + sfx_u32 deviation_hz; +#endif +} RF_API_radio_parameters_t; + +/*!****************************************************************** + * \struct RF_API_tx_data_t + * \brief RF TX data structure. + *******************************************************************/ +typedef struct { + sfx_u8 *bitstream; + sfx_u8 bitstream_size_bytes; +#ifdef ASYNCHRONOUS + RF_API_tx_cplt_cb_t cplt_cb; +#endif +} RF_API_tx_data_t; + +/*!****************************************************************** + * \struct RF_API_rx_data_t + * \brief RF RX data structure. + *******************************************************************/ +typedef struct { + sfx_u8 dl_phy_content_size; +#if (defined ASYNCHRONOUS) && (defined BIDIRECTIONAL) + RF_API_rx_data_received_cb_t data_received_cb; +#else + sfx_bool data_received; +#endif +} RF_API_rx_data_t; + +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) +/*!****************************************************************** + * \struct RF_API_carrier_sense_parameters_t + * \brief RF carrier sense parameters structure. + *******************************************************************/ +typedef struct { + sfx_u32 bandwidth_hz; + sfx_s8 threshold_dbm; + sfx_u32 min_duration_ms; +#ifdef ASYNCHRONOUS + RF_API_channel_free_cb_t channel_free_cb; +#else + sfx_bool *channel_free; +#endif +} RF_API_carrier_sense_parameters_t; +#endif + +/*!****************************************************************** + * \struct RF_API_config_t + * \brief RF API configuration structure. + *******************************************************************/ +typedef struct { + const SIGFOX_rc_t *rc; +#ifdef ASYNCHRONOUS + RF_API_process_cb_t process_cb; + RF_API_error_cb_t error_cb; +#endif +} RF_API_config_t; + +/*** RF API functions ***/ + +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) +/*!****************************************************************** + * \fn RF_API_status_t RF_API_open(RF_API_config_t *rf_api_config) + * \brief Open the RF driver. + * \param[in] rf_api_config: Pointer to the RF API configuration. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_open(RF_API_config_t *rf_api_config); +#endif + +#ifdef LOW_LEVEL_OPEN_CLOSE +/*!****************************************************************** + * \fn RF_API_status_t RF_API_close(void) + * \brief Close the RF driver. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_close(void); +#endif + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \fn RF_API_status_t RF_API_process(void) + * \brief Process RF driver, this function will be call by SIGFOX_EP_API_process just after the process_callback has been sent to process RF interruptions in main context. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_process(void); +#endif + +/*!****************************************************************** + * \fn RF_API_status_t RF_API_wake_up(void) + * \brief Wake-up the radio before each overall TX or RX sequence. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_wake_up(void); + +/*!****************************************************************** + * \fn RF_API_status_t RF_API_sleep(void) + * \brief Release the radio after each overall TX or RX sequence. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_sleep(void); + +/*!****************************************************************** + * \fn RF_API_status_t RF_API_init(RF_API_radio_parameters_t *radio_parameters) + * \brief Initialize the radio operation before each individual frame transmission or reception. + * \param[in] radio_parameters: Pointers to the radio parameters. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_init(RF_API_radio_parameters_t *radio_parameters); + +/*!****************************************************************** + * \fn RF_API_status_t RF_API_de_init(void) + * \brief Stop the radio operation after each individual frame transmission or reception. + * \param[in] rf_mode: Radio mode. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_de_init(void); + +/*!****************************************************************** + * \fn RF_API_status_t RF_API_send(RF_API_tx_data_t *tx_data) + * \brief Sending a bitstream over the air. + * \brief In blocking mode, this function blocks until the full bitstream is sent. + * \brief In asynchronous, this function only starts the transmission. End of transmission should be notified through the cplt_cb() callback. + * \param[in] tx_data: Pointer to the TX parameters. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_send(RF_API_tx_data_t *tx_data); + +#ifdef BIDIRECTIONAL +/*!****************************************************************** + * \fn RF_API_status_t RF_API_receive(RF_API_rx_data_t *rx_data) + * \brief Start downlink reception. Could be called multiple times if several downlink frames are received during the RX window. + * \brief In blocking mode, this function blocks until a valid downlink data is received or the MCU_API_TIMER_2 has elapsed. + * \brief In asynchronous mode, this function only starts the reception. Data reception should be notified through the rx_data_received() callback. + * \param[in] rx_data: Pointer to the RX parameters. + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_receive(RF_API_rx_data_t *rx_data); +#endif + +#ifdef BIDIRECTIONAL +/*!****************************************************************** + * \fn RF_API_status_t RF_API_get_dl_phy_content_and_rssi(sfx_u8 *dl_phy_content, sfx_u8 dl_phy_content_size, sfx_s16 *dl_rssi_dbm) + * \brief Read DL-PHY content and RSSI received by the radio. + * \brief In blocking mode, this function will be called only if the data_received parameter of the RF_API_receive() function is returned with SFX_TRUE value. + * \brief in asynchronous mode, this function will be called only if the data_received_cb callback is triggered during reception. + * \param[in] dl_phy_content_size: Number of bytes to copy in dl_phy_content. + * \param[out] dl_phy_content: Array to be filled with the received DL-PHY content. + * \param[out] dl_rssi_dbm: Pointer to 16-bits signed value to be filled with the DL RSSI in dBm. + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_get_dl_phy_content_and_rssi(sfx_u8 *dl_phy_content, sfx_u8 dl_phy_content_size, sfx_s16 *dl_rssi_dbm); +#endif + +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) +/*!****************************************************************** + * \fn RF_API_status_t RF_API_carrier_sense(RF_API_carrier_sense_parameters_t *carrier_sense_params) + * \brief In blocking mode, the function until the LBT condition is met or the MCU_API_TIMER_1 has elapsed. + * \brief In asynchronous mode, this function only starts the carrier sense operation. Channel free event should be notified through the channel_free_cb() callback. + * \param[in] carrier_sense_params: Pointer to the carrier sense parameters. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_carrier_sense(RF_API_carrier_sense_parameters_t *carrier_sense_params); +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \fn RF_API_status_t RF_API_get_version(sfx_u8 **version, sfx_u8 *version_size_char) + * \brief Get RF driver version. + * \param[in] none + * \param[out] version: RF driver version. + * \param[out] version_size_char: Pointer tha will contain the string size. + * \retval Function execution status. + *******************************************************************/ +RF_API_status_t RF_API_get_version(sfx_u8 **version, sfx_u8 *version_size_char); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void RF_API_error(void) + * \brief Function called by the library if any error occurred during the processing. + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +void RF_API_error(void); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void RF_API_stack_error(void) + * \brief Generic macro which calls the error stack function for RF errors (if enabled). + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_STACK +#define RF_API_stack_error(void) SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_RF, rf_status) +#else +#define RF_API_stack_error(void) +#endif +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void RF_API_check_status(error) + * \brief Generic macro to check an RF_API function status and exit. + * \param[in] error: High level error code to rise. + * \param[out] none + * \retval none + *******************************************************************/ +#define RF_API_check_status(error) { if (rf_status != RF_API_SUCCESS) { RF_API_stack_error(); EXIT_ERROR(error) } } +#endif + +#endif /* __RF_API_H__ */ diff --git a/inc/sigfox_ep_api.h b/inc/sigfox_ep_api.h new file mode 100644 index 0000000..0b1aae1 --- /dev/null +++ b/inc/sigfox_ep_api.h @@ -0,0 +1,402 @@ +/*!***************************************************************** + * \file sigfox_ep_api.h + * \brief Sigfox End-Point library API. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_EP_API_H__ +#define __SIGFOX_EP_API_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" +#include "sigfox_error.h" + +/*** SIGFOX EP API structures ***/ + +#ifdef ERROR_CODES +/*!****************************************************************** + * \enum SIGFOX_EP_API_status_t + * \brief Sigfox EP library error codes. + *******************************************************************/ +typedef enum { + // Core library errors. + SIGFOX_EP_API_SUCCESS = 0, + SIGFOX_EP_API_ERROR_NULL_PARAMETER, + SIGFOX_EP_API_ERROR_RC, + SIGFOX_EP_API_ERROR_MESSAGE_TYPE, + SIGFOX_EP_API_ERROR_UL_PAYLOAD_SIZE, + SIGFOX_EP_API_ERROR_DL_PAYLOAD_SIZE, + SIGFOX_EP_API_ERROR_DL_PAYLOAD_UNAVAILABLE, + SIGFOX_EP_API_ERROR_EP_ID, + SIGFOX_EP_API_ERROR_EP_PAC, + SIGFOX_EP_API_ERROR_EP_KEY, + SIGFOX_EP_API_ERROR_STATE, + SIGFOX_EP_API_ERROR_RF_MODE, + SIGFOX_EP_API_ERROR_MODULATION, + SIGFOX_EP_API_ERROR_BIT_RATE, + SIGFOX_EP_API_ERROR_TX_POWER, + SIGFOX_EP_API_ERROR_NUMBER_OF_FRAMES, + SIGFOX_EP_API_ERROR_T_IFU, + SIGFOX_EP_API_ERROR_T_CONF, + SIGFOX_EP_API_ERROR_MESSAGE_COUNTER_ROLLOVER, + SIGFOX_EP_API_ERROR_TX_FORBIDDEN, + SIGFOX_EP_API_ERROR_VERSION, + // Low level errors. + // Activate the ERROR_STACK flag and use the SIGFOX_EP_API_unstack_error() function to get more details. + SIGFOX_EP_API_ERROR_MCU, + SIGFOX_EP_API_ERROR_RF, + SIGFOX_EP_API_ERROR_BITSTREAM, + SIGFOX_EP_API_ERROR_FREQUENCY, + SIGFOX_EP_API_ERROR_NVM, + SIGFOX_EP_API_ERROR_TX_CONTROL +} SIGFOX_EP_API_status_t; +#else +typedef void SIGFOX_EP_API_status_t; +#endif + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \enum SIGFOX_EP_API_state_t + * \brief Sigfox EP library state. + *******************************************************************/ +typedef enum { + SIGFOX_EP_API_STATE_CLOSED, + SIGFOX_EP_API_STATE_READY, +#ifdef REGULATORY + SIGFOX_EP_API_STATE_REGULATORY, +#endif + SIGFOX_EP_API_STATE_UL_MODULATION_PENDING, +#if !(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0) || (defined BIDIRECTIONAL)) + SIGFOX_EP_API_STATE_UL_INTER_FRAME_TIMER, +#endif +#ifdef BIDIRECTIONAL + SIGFOX_EP_API_STATE_DL_TIMER, + SIGFOX_EP_API_STATE_DL_LISTENING, + SIGFOX_EP_API_STATE_DL_CONFIRMATION_TIMER, + SIGFOX_EP_API_STATE_DL_CONFIRMATION, +#endif + SIGFOX_EP_API_STATE_LAST +} SIGFOX_EP_API_state_t; +#endif + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \brief Sigfox EP library callback functions. + * \fn SIGFOX_EP_API_process_cb_t: Will be called each time a low level IRQ is handled by the library. Warning: runs in a IRQ context. Should only change variables state, and call as soon as possible @ref SIGFOX_EP_API_process. + * \fn SIGFOX_EP_API_uplink_cplt_cb_t: Will be called when the TX sequence is done (1 to 3 frames). Optional, could be set to NULL. + * \fn SIGFOX_EP_API_downlink_cplt_cb: Will be called if a valid downlink frame has been received. Optional, could be set to NULL. + * \fn SIGFOX_EP_API_message_cplt_cb: Will be called when the whole message process is finished, indicating that the library is ready again for a new operation. Optional, could be set to NULL. + *******************************************************************/ +typedef void (*SIGFOX_EP_API_process_cb_t)(void); +typedef void (*SIGFOX_EP_API_uplink_cplt_cb_t)(void); +#ifdef BIDIRECTIONAL +typedef void (*SIGFOX_EP_API_downlink_cplt_cb)(void); +#endif +typedef void (*SIGFOX_EP_API_message_cplt_cb)(void); +#endif + +/*!****************************************************************** + * \struct SIGFOX_EP_API_config_t + * \brief Sigfox EP library configuration structure. + *******************************************************************/ +typedef struct { + const SIGFOX_rc_t *rc; +#ifdef ASYNCHRONOUS + SIGFOX_EP_API_process_cb_t process_cb; +#endif +#ifndef MESSAGE_COUNTER_ROLLOVER + SIGFOX_message_counter_rollover_t message_counter_rollover; +#endif +} SIGFOX_EP_API_config_t; + +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) +/*!****************************************************************** + * \struct SIGFOX_EP_API_common_t + * \brief Common parameters of application and control messages structures. + *******************************************************************/ +typedef struct { +#ifndef UL_BIT_RATE_BPS + SIGFOX_ul_bit_rate_t ul_bit_rate; +#endif +#ifndef TX_POWER_DBM_EIRP + sfx_s8 tx_power_dbm_eirp; +#endif +#ifndef SINGLE_FRAME + sfx_u8 number_of_frames; +#ifndef T_IFU_MS + sfx_u16 t_ifu_ms; +#endif +#endif +#ifdef PUBLIC_KEY_CAPABLE + SIGFOX_ep_key_t ep_key_type; +#endif +} SIGFOX_EP_API_common_t; +#endif + +#ifdef APPLICATION_MESSAGES +/*!****************************************************************** + * \struct SIGFOX_EP_API_application_message_t + * \brief Application message data. + *******************************************************************/ +typedef struct { +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + SIGFOX_EP_API_common_t common_parameters; +#endif + SIGFOX_application_message_type_t type; +#ifdef ASYNCHRONOUS + SIGFOX_EP_API_uplink_cplt_cb_t uplink_cplt_cb; +#ifdef BIDIRECTIONAL + SIGFOX_EP_API_downlink_cplt_cb downlink_cplt_cb; +#endif + SIGFOX_EP_API_message_cplt_cb message_cplt_cb; +#endif +#if !(defined UL_PAYLOAD_SIZE) || (UL_PAYLOAD_SIZE > 0) + sfx_u8 *ul_payload; +#endif +#ifndef UL_PAYLOAD_SIZE + sfx_u8 ul_payload_size_bytes; +#endif +#ifdef BIDIRECTIONAL + sfx_u8 bidirectional_flag; +#ifndef T_CONF_MS + sfx_u16 t_conf_ms; +#endif +#endif +} SIGFOX_EP_API_application_message_t; +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +/*!****************************************************************** + * \struct SIGFOX_EP_API_control_message_t + * \brief Control message data. + *******************************************************************/ +typedef struct { +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + SIGFOX_EP_API_common_t common_parameters; +#endif + SIGFOX_control_message_type_t type; +#ifdef ASYNCHRONOUS + SIGFOX_EP_API_uplink_cplt_cb_t uplink_cplt_cb; + SIGFOX_EP_API_message_cplt_cb message_cplt_cb; +#endif +} SIGFOX_EP_API_control_message_t; +#endif + +/*!****************************************************************** + * \union SIGFOX_EP_API_message_status_t + * \brief Message status bitfield. + *******************************************************************/ +typedef union { + struct { + unsigned app_frame_1 : 1; + unsigned app_frame_2 : 1; + unsigned app_frame_3 : 1; + unsigned downlink_frame : 1; + unsigned ack_frame : 1; + unsigned error : 1; + }; + sfx_u8 all; +} SIGFOX_EP_API_message_status_t; + +/*** SIGFOX EP API functions ***/ + +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_open(SIGFOX_EP_API_config_t *config) + * \brief Open the EP library. + * \param[in] config: Pointer to the library configuration. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_open(SIGFOX_EP_API_config_t *config); + +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_close(void) + * \brief Close the EP library. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_close(void); + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_process(void) + * \brief Main process function of the library. This function should be called as soon as possible when the process callback is triggered. + * \param[in] none + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_process(void); +#endif + +#ifdef APPLICATION_MESSAGES +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_send_application_message(SIGFOX_EP_API_application_message_t *application_message) + * \brief Send an application message over Sigfox network. + * \param[in] application_message: Pointer to the application message data. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_send_application_message(SIGFOX_EP_API_application_message_t *application_message); +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_send_control_message(SIGFOX_EP_API_control_message_t *control_message) + * \brief Send a control message over Sigfox network. + * \param[in] control_message: Pointer to the control message data. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_send_control_message(SIGFOX_EP_API_control_message_t *control_message); +#endif + +#ifdef BIDIRECTIONAL +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_get_dl_payload(sfx_u8 *dl_payload, sfx_u8 dl_payload_size, sfx_s16 *rssi_dbm) + * \brief Get the current message status. + * \param[in] dl_payload: Byte array that will contain the downlink payload. + * \param[in] dl_payload_size: Number of bytes to read. + * \param[in] dl_rssi_dbm: Pointer to 16-bits signed value that will contain the RSSI of the received downlink frame. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_dl_payload(sfx_u8 *dl_payload, sfx_u8 dl_payload_size, sfx_s16 *rssi_dbm); +#endif + +/*!****************************************************************** + * \fn SIGFOX_EP_API_message_status_t SIGFOX_EP_API_get_message_status(void) + * \brief Get the current message status. + * \param[in] none + * \param[out] none + * \retval Status of the last transmission. + *******************************************************************/ +SIGFOX_EP_API_message_status_t SIGFOX_EP_API_get_message_status(void); + +#ifdef ASYNCHRONOUS +/*!****************************************************************** + * \fn SIGFOX_EP_API_state_t SIGFOX_EP_API_get_state(void) + * \brief Get the current library state. + * \param[in] none + * \param[out] none + * \retval Current library state. + *******************************************************************/ +SIGFOX_EP_API_state_t SIGFOX_EP_API_get_state(void); +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_get_ep_id(sfx_u8* ep_id, sfx_u8 ep_id_size_bytes) + * \brief Get EP ID. + * \param[in] ep_id_size_bytes: Number of bytes of the ID to read (full size is SIGFOX_EP_ID_SIZE_BYTES). + * \param[out] ep_id: End-point ID. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_ep_id(sfx_u8* ep_id, sfx_u8 ep_id_size_bytes); +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_get_initial_pac(sfx_u8 *initial_pac, sfx_u8 initial_pac_size_bytes) + * \brief Get initial PAC. + * \param[in] initial_pac_size_bytes: Number of bytes of the PAC to read (full size is SIGFOX_EP_PAC_SIZE_BYTES). + * \param[out] initial_pac: Initial PAC. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_initial_pac(sfx_u8 *initial_pac, sfx_u8 initial_pac_size_bytes); +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_get_version(SIGFOX_version_t version_type, sfx_u8 **version, sfx_u8 *version_size_char) + * \brief Get EP library version. + * \param[in] version_type: Version to get. + * \param[out] version: Version string. + * \param[out] version_size_char: Pointer that will contain the string size. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_version(SIGFOX_version_t version_type, sfx_u8 **version, sfx_u8 *version_size_char); +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_get_flags(sfx_u8 **ep_flags, sfx_u8 *ep_flags_size_char) + * \brief Get EP library flags. + * \param[in] none + * \param[out] ep_flags: Flags string. + * \param[out] ep_flags_size_char: Pointer that will contain the string size. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_flags(sfx_u8 **ep_flags, sfx_u8 *ep_flags_size_char); +#endif + +#ifdef ERROR_STACK +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_unstack_error(SIGFOX_ERROR_t *error_ptr) + * \brief Read and clear the last (newest) error stored in the internal error stack. + * \brief This function can be called multiple times to unstack all errors which previously occurred during library execution, until it returns SIGFOX_EP_API_SUCCESS. + * \param[in] none + * \param[out] error_ptr: Pointer that will contain the last error in the stack. + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_unstack_error(SIGFOX_ERROR_t *error_ptr); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_EP_API_stack_error(void) + * \brief Generic macro which calls the error stack function for EP library errors (if enabled). + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_STACK +#define SIGFOX_EP_API_stack_error(void) SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_EP_LIBRARY, ep_api_status) +#else +#define SIGFOX_EP_API_stack_error(void) +#endif +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void SIGFOX_EP_API_check_status(error) + * \brief Generic macro to check a SIGFOX_EP_API function status and exit. + * \param[in] error: High level error code to rise. + * \param[out] none + * \retval none + *******************************************************************/ +#define SIGFOX_EP_API_check_status(error) { if (ep_api_status != SIGFOX_EP_API_SUCCESS) { SIGFOX_EP_API_stack_error(); EXIT_ERROR(error) } } +#endif + +#endif /* __SIGFOX_EP_API_H__ */ diff --git a/inc/sigfox_ep_api_test.h b/inc/sigfox_ep_api_test.h new file mode 100644 index 0000000..76b4e63 --- /dev/null +++ b/inc/sigfox_ep_api_test.h @@ -0,0 +1,90 @@ +/*!***************************************************************** + * \file sigfox_ep_api_test.h + * \brief Sigfox End-Point library test API. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_EP_API_TEST_H__ +#define __SIGFOX_EP_API_TEST_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_ep_api.h" +#include "sigfox_types.h" + +#ifdef CERTIFICATION + +/*** SIGFOX EP API TEST structures ***/ + +/*!****************************************************************** + * \struct SIGFOX_EP_API_TEST_parameters_t + * \brief Specific parameters for test (RFP ADDON). + *******************************************************************/ +typedef struct { + sfx_u32 tx_frequency_hz; // If non-zero, bypass the frequency driver of the core library. +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + sfx_bool fh_timer_enable; // Enable or disable FH timer. +#endif +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) + sfx_bool lbt_enable; +#endif +} SIGFOX_EP_API_TEST_parameters_t; + +#ifdef APPLICATION_MESSAGES +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_TEST_send_application_message(SIGFOX_EP_API_application_message_t *application_message, SIGFOX_EP_API_TEST_parameters_t *test_parameters) + * \brief Send an application message over Sigfox network. + * \param[in] application_message: Pointer to the application message data. + * \param[in] test_parameters: Pointer to the test parameters. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_TEST_send_application_message(SIGFOX_EP_API_application_message_t *application_message, SIGFOX_EP_API_TEST_parameters_t *test_parameters); +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +/*!****************************************************************** + * \fn SIGFOX_EP_API_status_t SIGFOX_EP_API_TEST_send_control_message(SIGFOX_EP_API_control_message_t *control_message, SIGFOX_EP_API_TEST_parameters_t *test_parameters) + * \brief Send a control message over Sigfox network. + * \param[in] control_message: Pointer to the control message data. + * \param[in] test_parameters: Pointer to the test parameters. + * \param[out] none + * \retval Function execution status. + *******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_TEST_send_control_message(SIGFOX_EP_API_control_message_t *control_message, SIGFOX_EP_API_TEST_parameters_t *test_parameters); +#endif + +#endif /* CERTIFICATION */ + +#endif /* __SIGFOX_EP_API_TEST_H__ */ diff --git a/inc/sigfox_ep_flags.h b/inc/sigfox_ep_flags.h new file mode 100644 index 0000000..9cc5867 --- /dev/null +++ b/inc/sigfox_ep_flags.h @@ -0,0 +1,101 @@ +/*!***************************************************************** + * \file sigfox_ep_flags.h + * \brief Sigfox End-Point library compilations flags definition. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_EP_FLAGS_H__ +#define __SIGFOX_EP_FLAGS_H__ + +/*** Library compilation flags ***/ + +#define RC1 /*!< \brief Support radio configuration zone 1 (Europe, Middle-East and Africa). > */ +#define RC2 /*!< \brief Support radio configuration zone 2 (Brazil, Canada, Mexico, Puerto Rico and USA). > */ +#define RC3C /*!< \brief Support radio configuration zone 3 (Japan) with LBT. > */ +#define RC3D /*!< \brief Support radio configuration zone 3 (Japan) with DC. */ +#define RC4 /*!< \brief Support radio configuration zone 4 (Latin America and Asia Pacific). > */ +#define RC5 /*!< \brief Support radio configuration zone 5 (South-Corea). > */ +#define RC6 /*!< \brief Support radio configuration zone 6 (India). > */ +#define RC7 /*!< \brief Support radio configuration zone 7 (Russia). > */ + +#define APPLICATION_MESSAGES /*!< \brief Support uplink application messages if defined. > */ +#define CONTROL_KEEP_ALIVE_MESSAGE /*!< \brief Support uplink control keep alive message if defined. > */ + +#define BIDIRECTIONAL /*!< \brief Support bidirectional procedure (downlink) if defined. Only applicable to application messages. > */ + /*!< \brief Otherwise all messages will be uplink only. > */ + +#define ASYNCHRONOUS /*!< \brief Asynchronous mode if defined, blocking mode otherwise. > */ + +#define LOW_LEVEL_OPEN_CLOSE /*!< \brief Enable MCU and RF open/close functions if defined. > */ + +#define REGULATORY /*!< \brief Enable radio regulatory control (DC, FH or LBT check) if defined. > */ + +//#define SINGLE_FRAME /*!< \brief Send 1 frame per message (N=1) if defined. > */ + /*!< \brief Otherwise number of frames per message is dynamically given when sending a message (N=1, N=2 or N=3). > */ + +//#define UL_BIT_RATE_BPS 100 /*!< \brief If defined, give the only uplink bit rate supported (100 or 600 depending on the RC). > */ + /*!< \brief Otherwise, value is dynamically given when sending a message. > */ + +//#define TX_POWER_DBM_EIRP 16 /*!< \brief If defined, give the only TX power supported by the radio. > */ + /*!< \brief Otherwise the value is dynamically given when sending a message. > */ + +//#define T_IFU_MS 500 /*!< \brief If defined, give the fixed inter-frame delay used between uplink frames of a same message (0 to 2000ms). > */ + /*!< \brief Value 0 disables the delay and associated timers to optimize memory space. > */ + /*!< \brief Otherwise value is dynamically given when sending a message. > */ + +//#define T_CONF_MS 2000 /*!< \brief If defined, give the fixed delay between downlink frame reception and uplink confirmation message (1400 to 4000ms). > */ + /*!< \brief Otherwise value is dynamically given when sending a message. > */ + +//#define UL_PAYLOAD_SIZE 0 /*!< \brief If defined, give the only uplink payload length supported (0 to 12). > */ + /*!< \brief Value 0 enables the bit 0, bit 1 and empty messages. > */ + /*!< \brief Otherwise, all uplink payload lengths are dynamically supported. > */ + +//#define CRC_HW /*!< \brief If defined, enable hardware CRC through MCU API functions. Otherwise the embedded driver is used. > */ + +//#define MESSAGE_COUNTER_ROLLOVER 4096 /*!< \brief If defined, give the only message counter rollover value supported. > */ + /*!< \brief Otherwise, value is dynamically given when opening the library. > */ + +#define PARAMETERS_CHECK /*!< \brief Enable parameters check if defined. > */ + +#define CERTIFICATION /*!< \brief Enable certification features if defined. > */ + +#define PUBLIC_KEY_CAPABLE /*!< \brief Enable public key switch feature if defined. > */ + +#define VERBOSE /*!< \brief Enable credentials (ID/PAC) API access and version control functions if defined. > */ + +#define ERROR_CODES /*!< \brief Use return codes if defined, otherwise all functions return void. > */ + +#define ERROR_STACK 32 /*!< \brief If defined, store low level errors in a stack (the macro gives the depth). > */ + /*!< \brief Errors can be read with the SIGFOX_EP_API_unstack_error() function. > */ + +#endif /* __SIGFOX_EP_FLAGS_H__ */ diff --git a/inc/sigfox_ep_version.h b/inc/sigfox_ep_version.h new file mode 100644 index 0000000..61d3308 --- /dev/null +++ b/inc/sigfox_ep_version.h @@ -0,0 +1,250 @@ +/*!***************************************************************** + * \file sigfox_ep_version.h + * \brief Sigfox End-Point library version. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_EP_VERSION_H__ +#define __SIGFOX_EP_VERSION_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif + +/*** Main version ***/ + +#define SIGFOX_EP_VERSION "3.0" + +/*** Compilation flags ***/ + +#define SIGFOX_VERSION_SEPARATOR " " + +#ifdef USE_SIGFOX_EP_FLAGS_H +#define FLAGS_OPT "SIGFOX_EP_FLAGS_H" +#else +#define FLAGS_OPT "COMMAND_LINE_FLAGS" +#endif + +#ifdef RC1 +#define RC1_OPT SIGFOX_VERSION_SEPARATOR "RC1" +#else +#define RC1_OPT +#endif + +#ifdef RC2 +#define RC2_OPT SIGFOX_VERSION_SEPARATOR "RC2" +#else +#define RC2_OPT +#endif + +#ifdef RC3C +#define RC3C_OPT SIGFOX_VERSION_SEPARATOR "RC3C" +#else +#define RC3C_OPT +#endif + +#ifdef RC3D +#define RC3D_OPT SIGFOX_VERSION_SEPARATOR "RC3D" +#else +#define RC3D_OPT +#endif + +#ifdef RC4 +#define RC4_OPT SIGFOX_VERSION_SEPARATOR "RC4" +#else +#define RC4_OPT +#endif + +#ifdef RC5 +#define RC5_OPT SIGFOX_VERSION_SEPARATOR "RC5" +#else +#define RC5_OPT +#endif + +#ifdef RC6 +#define RC6_OPT SIGFOX_VERSION_SEPARATOR "RC6" +#else +#define RC6_OPT +#endif + +#ifdef RC7 +#define RC7_OPT SIGFOX_VERSION_SEPARATOR "RC7" +#else +#define RC7_OPT +#endif + +#ifdef APPLICATION_MESSAGES +#define APPLICATION_MESSAGES_OPT SIGFOX_VERSION_SEPARATOR "APPLICATION_MESSAGES" +#else +#define APPLICATION_MESSAGES_OPT +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +#define CONTROL_KEEP_ALIVE_MESSAGE_OPT SIGFOX_VERSION_SEPARATOR "CONTROL_KEEP_ALIVE_MESSAGE" +#else +#define CONTROL_KEEP_ALIVE_MESSAGE_OPT +#endif + +#ifdef BIDIRECTIONAL +#define BIDIRECTIONAL_OPT SIGFOX_VERSION_SEPARATOR "BIDIRECTIONAL" +#else +#define BIDIRECTIONAL_OPT +#endif + +#ifdef ASYNCHRONOUS +#define ASYNCHRONOUS_OPT SIGFOX_VERSION_SEPARATOR "ASYNCHRONOUS" +#else +#define ASYNCHRONOUS_OPT +#endif + +#ifdef LOW_LEVEL_OPEN_CLOSE +#define LOW_LEVEL_OPEN_CLOSE_OPT SIGFOX_VERSION_SEPARATOR "LOW_LEVEL_OPEN_CLOSE" +#else +#define LOW_LEVEL_OPEN_CLOSE_OPT +#endif + +#ifdef REGULATORY +#define REGULATORY_OPT SIGFOX_VERSION_SEPARATOR "REGULATORY" +#else +#define REGULATORY_OPT +#endif + +#ifdef SINGLE_FRAME +#define SINGLE_FRAME_OPT SIGFOX_VERSION_SEPARATOR "SINGLE_FRAME" +#else +#define SINGLE_FRAME_OPT +#endif + +#ifdef UL_BIT_RATE_BPS +#define UL_BIT_RATE_BPS_OPT SIGFOX_VERSION_SEPARATOR "UL_BIT_RATE_BPS" +#else +#define UL_BIT_RATE_BPS_OPT +#endif + +#ifdef T_IFU_MS +#define T_IFU_MS_OPT SIGFOX_VERSION_SEPARATOR "T_IFU_MS" +#else +#define T_IFU_MS_OPT +#endif + +#ifdef T_CONF_MS +#define T_CONF_MS_OPT SIGFOX_VERSION_SEPARATOR "T_CONF_MS" +#else +#define T_CONF_MS_OPT +#endif + +#ifdef UL_PAYLOAD_SIZE +#define UL_PAYLOAD_SIZE_OPT SIGFOX_VERSION_SEPARATOR "UL_PAYLOAD_SIZE" +#else +#define UL_PAYLOAD_SIZE_OPT +#endif + +#ifdef CRC_HW +#define CRC_HW_OPT SIGFOX_VERSION_SEPARATOR "CRC_HW" +#else +#define CRC_HW_OPT +#endif + +#ifdef MESSAGE_COUNTER_ROLLOVER +#define MESSAGE_COUNTER_ROLLOVER_OPT SIGFOX_VERSION_SEPARATOR "MESSAGE_COUNTER_ROLLOVER" +#else +#define MESSAGE_COUNTER_ROLLOVER_OPT +#endif + +#ifdef PARAMETERS_CHECK +#define PARAMETERS_CHECK_OPT SIGFOX_VERSION_SEPARATOR "PARAMETERS_CHECK" +#else +#define PARAMETERS_CHECK_OPT +#endif + +#ifdef CERTIFICATION +#define CERTIFICATION_OPT SIGFOX_VERSION_SEPARATOR "CERTIFICATION" +#else +#define CERTIFICATION_OPT +#endif + +#ifdef PUBLIC_KEY_CAPABLE +#define PUBLIC_KEY_CAPABLE_OPT SIGFOX_VERSION_SEPARATOR "PUBLIC_KEY_CAPABLE" +#else +#define PUBLIC_KEY_CAPABLE_OPT +#endif + +#ifdef VERBOSE +#define VERBOSE_OPT SIGFOX_VERSION_SEPARATOR "VERBOSE" +#else +#define VERBOSE_OPT +#endif + +#ifdef ERROR_CODES +#define ERROR_CODES_OPT SIGFOX_VERSION_SEPARATOR "ERROR_CODES" +#else +#define ERROR_CODES_OPT +#endif + +#ifdef ERROR_STACK +#define ERROR_STACK_OPT SIGFOX_VERSION_SEPARATOR "ERROR_STACK" +#else +#define ERROR_STACK_OPT +#endif + +#define SIGFOX_EP_FLAGS \ + FLAGS_OPT \ + RC1_OPT \ + RC2_OPT \ + RC3C_OPT \ + RC3D_OPT \ + RC4_OPT \ + RC5_OPT \ + RC6_OPT \ + RC7_OPT \ + APPLICATION_MESSAGES_OPT \ + CONTROL_KEEP_ALIVE_MESSAGE_OPT \ + BIDIRECTIONAL_OPT \ + ASYNCHRONOUS_OPT \ + LOW_LEVEL_OPEN_CLOSE_OPT \ + REGULATORY_OPT \ + SINGLE_FRAME_OPT \ + UL_BIT_RATE_BPS_OPT \ + T_IFU_MS_OPT \ + T_CONF_MS_OPT \ + UL_PAYLOAD_SIZE_OPT \ + CRC_HW_OPT \ + MESSAGE_COUNTER_ROLLOVER_OPT \ + PARAMETERS_CHECK_OPT \ + CERTIFICATION_OPT \ + PUBLIC_KEY_CAPABLE_OPT \ + VERBOSE_OPT \ + ERROR_CODES_OPT \ + ERROR_STACK_OPT \ + +#endif /* __SIGFOX_EP_VERSION_H__ */ diff --git a/inc/sigfox_error.h b/inc/sigfox_error.h new file mode 100644 index 0000000..41e0d16 --- /dev/null +++ b/inc/sigfox_error.h @@ -0,0 +1,149 @@ +/*!***************************************************************** + * \file sigfox_error.h + * \brief Sigfox error driver. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_ERROR_H__ +#define __SIGFOX_ERROR_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +#ifdef ERROR_STACK +/*** SIGFOX ERROR structures ***/ + +/*!****************************************************************** + * \enum SIGFOX_ERROR_source_t + * \brief Sigfox library low level errors sources. + *******************************************************************/ +typedef enum { + SIGFOX_ERROR_SOURCE_NONE = 0, + SIGFOX_ERROR_SOURCE_EP_LIBRARY, + SIGFOX_ERROR_SOURCE_MCU, + SIGFOX_ERROR_SOURCE_RF, + SIGFOX_ERROR_SOURCE_BITSTREAM, + SIGFOX_ERROR_SOURCE_CRC, + SIGFOX_ERROR_SOURCE_FREQUENCY, + SIGFOX_ERROR_SOURCE_TX_CONTROL, + SIGFOX_ERROR_SOURCE_LAST +} SIGFOX_ERROR_source_t; +#endif + +#ifdef ERROR_STACK +/*!****************************************************************** + * \struct SIGFOX_ERROR_t + * \brief Sigfox library low level error. + *******************************************************************/ +typedef struct { + SIGFOX_ERROR_source_t source; + sfx_u32 code; +} SIGFOX_ERROR_t; +#endif + +/*** SIGFOX ERROR functions ***/ + +#ifdef ERROR_STACK +/*!****************************************************************** + * \fn void SIGFOX_ERROR_init(void) + * \brief Init error stack. + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +void SIGFOX_ERROR_init(void); +#endif + +#ifdef ERROR_STACK +/*!****************************************************************** + * \fn void SIGFOX_ERROR_stack(SIGFOX_ERROR_source_t source, sfx_u32 code) + * \brief Store a new error in the internal stack. + * \param[in] source: Error source. + * \param[in] code: Error code. + * \param[out] none + * \retval none + *******************************************************************/ +void SIGFOX_ERROR_stack(SIGFOX_ERROR_source_t source, sfx_u32 code); +#endif + +#ifdef ERROR_STACK +/*!****************************************************************** + * \fn void SIGFOX_ERROR_unstack(SIGFOX_ERROR_t *error_ptr) + * \brief Read and clear the last (newest) error stored in the internal error stack. + * \brief This function can be called multiple times to unstack all errors which previously occurred during library execution, until it returns SIGFOX_EP_API_SUCCESS. + * \param[in] none + * \param[out] error_ptr: Pointer that will contain the last error in the stack. + * \retval none + *******************************************************************/ +void SIGFOX_ERROR_unstack(SIGFOX_ERROR_t *error_ptr); +#endif + +#ifdef ERROR_CODES +/*!****************************************************************** + * \fn void CHECK_STATUS(success_code) + * \brief Generic macro to check a status. + * \param[in] success_code: Success code to compare with. + * \param[out] none + * \retval none + *******************************************************************/ +#define CHECK_STATUS(success_code) { if (status != success_code) goto errors; } +#endif + +/*!****************************************************************** + * \fn void EXIT_ERROR(error) + * \brief Generic macro to update a status and exit a function. + * \param[in] error: Code of the risen error. + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_CODES +#define EXIT_ERROR(error) { status = error; goto errors; } +#else +#define EXIT_ERROR(error) { goto errors; } +#endif + +/*!****************************************************************** + * \fn void RETURN(void) + * \brief Generic macro for function return. + * \param[in] none + * \param[out] none + * \retval none + *******************************************************************/ +#ifdef ERROR_CODES +#define RETURN() { return status; } +#else +#define RETURN() { return; } +#endif +#endif /* __SIGFOX_ERROR_H__ */ diff --git a/inc/sigfox_rc.h b/inc/sigfox_rc.h new file mode 100644 index 0000000..10174d4 --- /dev/null +++ b/inc/sigfox_rc.h @@ -0,0 +1,294 @@ +/*!***************************************************************** + * \file sigfox_rc.h + * \brief Sigfox radio configuration zones definition. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_RC_H__ +#define __SIGFOX_RC_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +#ifdef SPECTRUM_ACCESS_DC +/*!****************************************************************** + * \var SIGFOX_SPECTRUM_ACCESS_DC + * \brief Duty cycle spectrum access parameters. + *******************************************************************/ +static const SIGFOX_spectrum_access_t SIGFOX_SPECTRUM_ACCESS_DC = { + .type = SIGFOX_SPECTRUM_ACCESS_TYPE_DC, +#ifdef BIDIRECTIONAL + .dl_t_w_ms = 20000, + .dl_t_rx_ms = 25000, +#ifndef SINGLE_FRAME + .delta_f_mf_hz = 6000, +#endif +#endif +}; +#endif + +#ifdef SPECTRUM_ACCESS_LDC +/*!****************************************************************** + * \var SIGFOX_SPECTRUM_ACCESS_LDC + * \brief Low Duty cycle spectrum access parameters. + *******************************************************************/ +static const SIGFOX_spectrum_access_t SIGFOX_SPECTRUM_ACCESS_LDC = { + .type = SIGFOX_SPECTRUM_ACCESS_TYPE_LDC, +#ifdef BIDIRECTIONAL + .dl_t_w_ms = 19000, + .dl_t_rx_ms = 33500, +#ifndef SINGLE_FRAME + .delta_f_mf_hz = 6000, +#endif +#endif +}; +#endif + +#ifdef SPECTRUM_ACCESS_FH +/*!****************************************************************** + * \var SIGFOX_SPECTRUM_ACCESS_FH + * \brief Frequency hopping spectrum access definition. + *******************************************************************/ +static const SIGFOX_spectrum_access_t SIGFOX_SPECTRUM_ACCESS_FH = { + .type = SIGFOX_SPECTRUM_ACCESS_TYPE_FH, +#ifdef BIDIRECTIONAL + .dl_t_w_ms = 20000, + .dl_t_rx_ms = 25000, +#ifndef SINGLE_FRAME + .delta_f_mf_hz = 25000, +#endif +#endif +}; +#endif + +#ifdef SPECTRUM_ACCESS_LBT_M80 +/*!****************************************************************** + * \var SIGFOX_SPECTRUM_ACCESS_LBT_M80 + * \brief Listen before talk spectrum access -80dBm definition. + *******************************************************************/ +static const SIGFOX_spectrum_access_t SIGFOX_SPECTRUM_ACCESS_LBT_M80 = { + .type = SIGFOX_SPECTRUM_ACCESS_TYPE_LBT, +#ifdef BIDIRECTIONAL + .dl_t_w_ms = 19000, + .dl_t_rx_ms = 33500, +#ifndef SINGLE_FRAME + .delta_f_mf_hz = 6000, +#endif +#endif +#ifdef REGULATORY + .cs_bandwidth_hz = 200000, + .cs_threshold_dbm = -80, + .cs_min_duration_ms = 5, + .cs_max_duration_first_frame_ms = 5000, // Could be changed by the user. +#endif +}; +#endif + +#ifdef SPECTRUM_ACCESS_LBT_M65 +/*!****************************************************************** + * \var SIGFOX_SPECTRUM_ACCESS_LBT_M65 + * \brief Listen before talk spectrum access -65dBm definition. + *******************************************************************/ +static const SIGFOX_spectrum_access_t SIGFOX_SPECTRUM_ACCESS_LBT_M65 = { + .type = SIGFOX_SPECTRUM_ACCESS_TYPE_LBT, +#ifdef BIDIRECTIONAL + .dl_t_w_ms = 19000, + .dl_t_rx_ms = 33500, +#ifndef SINGLE_FRAME + .delta_f_mf_hz = 6000, +#endif +#endif +#ifdef REGULATORY + .cs_bandwidth_hz = 200000, + .cs_threshold_dbm = -65, + .cs_min_duration_ms = 5, + .cs_max_duration_first_frame_ms = 5000, // Could be changed by the user. +#endif +}; +#endif + +#ifdef RC1 +/*!****************************************************************** + * \var SIGFOX_RC1 + * \brief RC1 radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC1 = { + .f_ul_hz = 868130000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 869525000, +#endif + .macro_channel_guard_band_hz = 17400, // For 20ppm local oscillator. Could be changed according to effective oscillator accuracy. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_DC, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_100BPS) | (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 16, +#endif +}; +#endif + +#ifdef RC2 +/*!****************************************************************** + * \var SIGFOX_RC2 + * \brief RC2 radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC2 = { + .f_ul_hz = 902200000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 905200000, +#endif + .macro_channel_guard_band_hz = SIGFOX_FH_MACRO_CHANNEL_GUARD_BAND_HZ, // Fixed to 1 micro-channel bandwidth. Should not be changed. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_FH, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 24, +#endif +}; +#endif + +#ifdef RC3C +/*!****************************************************************** + * \var SIGFOX_RC3C + * \brief RC3C radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC3C = { + .f_ul_hz = 923200000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 922200000, +#endif + .macro_channel_guard_band_hz = 18500, // For 20ppm local oscillator. Could be changed according to effective oscillator accuracy. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_LBT_M80, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_100BPS) | (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 16, +#endif +}; +#endif + +#ifdef RC3D +/*!****************************************************************** + * \var SIGFOX_RC3D + * \brief RC3D radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC3D = { + .f_ul_hz = 923200000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 922200000, +#endif + .macro_channel_guard_band_hz = 18500, // For 20ppm local oscillator. Could be changed according to effective oscillator accuracy. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_LDC, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_100BPS) | (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 16, +#endif +}; +#endif + +#ifdef RC4 +/*!****************************************************************** + * \var SIGFOX_RC4 + * \brief RC4 radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC4 = { + .f_ul_hz = 920800000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 922300000, +#endif + .macro_channel_guard_band_hz = SIGFOX_FH_MACRO_CHANNEL_GUARD_BAND_HZ, // Fixed to 1 micro-channel bandwidth. Should not be changed. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_FH, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 24, +#endif +}; +#endif + +#ifdef RC5 +/*!****************************************************************** + * \var SIGFOX_RC5 + * \brief RC5 radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC5 = { + .f_ul_hz = 923300000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 922300000, +#endif + .macro_channel_guard_band_hz = 18500, // For 20ppm local oscillator. Could be changed according to effective oscillator accuracy. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_LBT_M65, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_100BPS) | (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 14, +#endif +}; +#endif + +#ifdef RC6 +/*!****************************************************************** + * \var SIGFOX_RC6 + * \brief RC6 radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC6 = { + .f_ul_hz = 865200000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 866300000, +#endif + .macro_channel_guard_band_hz = 17400, // For 20ppm local oscillator. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_DC, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_100BPS) | (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 16, +#endif +}; +#endif + +#ifdef RC7 +/*!****************************************************************** + * \var SIGFOX_RC7 + * \brief RC7 radio configuration structure. + *******************************************************************/ +static const SIGFOX_rc_t SIGFOX_RC7 = { + .f_ul_hz = 868800000, +#ifdef BIDIRECTIONAL + .f_dl_hz = 869100000, +#endif + .macro_channel_guard_band_hz = 17400, // For 20ppm local oscillator. Could be changed according to effective oscillator accuracy. + .spectrum_access = &SIGFOX_SPECTRUM_ACCESS_DC, +#ifdef PARAMETERS_CHECK + .uplink_bit_rate_capability = (1 << SIGFOX_UL_BIT_RATE_100BPS) | (1 << SIGFOX_UL_BIT_RATE_600BPS), + .tx_power_dbm_eirp_max = 16, +#endif +}; +#endif + +#endif /* __SIGFOX_RC_H__ */ diff --git a/inc/sigfox_types.h b/inc/sigfox_types.h new file mode 100644 index 0000000..87aee64 --- /dev/null +++ b/inc/sigfox_types.h @@ -0,0 +1,388 @@ +/*!***************************************************************** + * \file sigfox_types.h + * \brief Sigfox types definition. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#ifndef __SIGFOX_TYPES_H__ +#define __SIGFOX_TYPES_H__ + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif + +/*** SIGFOX library common macros ***/ + +#define SIGFOX_EP_ID_SIZE_BYTES 4 +#define SIGFOX_EP_PAC_SIZE_BYTES 8 +#define SIGFOX_EP_KEY_SIZE_BYTES 16 + +#define SIGFOX_UL_PAYLOAD_MAX_SIZE_BYTES 12 +#ifndef SINGLE_FRAME +// Maximum delay between each frame of a same uplink message (for all RC). +#define SIGFOX_T_IFU_MAX_MS 2000 +#ifdef BIDIRECTIONAL +// Fixed inter frame delay when requesting a downlink. +#define SIGFOX_T_IFB_MS 500 +#endif +// Maximum duration of repeated frames transmission in case of LBT. +#define SIGFOX_T_LF_MS 8000 +#endif + +#ifdef BIDIRECTIONAL +// Downlink bit rate. +#define SIGFOX_DL_BIT_RATE_BPS 600 +// Downlink GFSK deviation. +#define SIGFOX_DL_GFSK_DEVIATION_HZ 800 +// Downlink frame size. +#define SIGFOX_DL_PAYLOAD_SIZE_BYTES 8 +#define SIGFOX_DL_PHY_CONTENT_SIZE_BYTES 15 +// Downlink preamble. +#define SIGFOX_DL_PR_PATTERN 0xAA +// Downlink synchronization word. +#define SIGFOX_DL_FT {0xB2, 0X27} +#define SIGFOX_DL_FT_SIZE_BYTES 2 +// Delay between downlink frame reception and uplink confirmation control message (for all RC). +#define SIGFOX_T_CONF_MIN_MS 1400 +#define SIGFOX_T_CONF_MAX_MS 4000 +#endif + +// Sigfox operational band width (for all RC). +#define SIGFOX_MACRO_CHANNEL_WIDTH_HZ 192000 + +#define SFX_NULL (void*) 0 + +/*** SIGFOX library types ***/ + +typedef unsigned char sfx_u8; +typedef signed char sfx_s8; +typedef unsigned short sfx_u16; +typedef signed short sfx_s16; +typedef unsigned int sfx_u32; +typedef signed int sfx_s32; + +/*!****************************************************************** + * \enum sfx_bool + * \brief Sigfox boolean type. + *******************************************************************/ +typedef enum { + SFX_FALSE = 0, + SFX_TRUE +} sfx_bool; + +/*** SIGFOX library structures ***/ + +#if !(defined UL_BIT_RATE_BPS) || (defined PARAMETERS_CHECK) +/*!****************************************************************** + * \enum SIGFOX_bit_rate_t + * \brief Sigfox signals bit rates list. + *******************************************************************/ +typedef enum { + SIGFOX_UL_BIT_RATE_100BPS, + SIGFOX_UL_BIT_RATE_600BPS, + SIGFOX_UL_BIT_RATE_LAST +} SIGFOX_ul_bit_rate_t; +#endif + +#if !(defined UL_BIT_RATE_BPS) || ((defined PARAMETERS_CHECK) && (defined ERROR_CODES)) +/*!****************************************************************** + * \const SIGFOX_EP_API_UL_BIT_RATE_BPS_LIST + * \brief Sigfox bit rates value. + *******************************************************************/ +static const sfx_u16 SIGFOX_UL_BIT_RATE_BPS_LIST[SIGFOX_UL_BIT_RATE_LAST] = {100, 600}; +#endif + +#ifdef APPLICATION_MESSAGES +/*!****************************************************************** + * \enum SIGFOX_application_message_type_t + * \brief Sigfox application message types. + *******************************************************************/ +typedef enum { +#ifdef UL_PAYLOAD_SIZE +#if (UL_PAYLOAD_SIZE == 0) + SIGFOX_APPLICATION_MESSAGE_TYPE_EMPTY, + SIGFOX_APPLICATION_MESSAGE_TYPE_BIT0, + SIGFOX_APPLICATION_MESSAGE_TYPE_BIT1, +#else + SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY, +#endif +#else + SIGFOX_APPLICATION_MESSAGE_TYPE_EMPTY, + SIGFOX_APPLICATION_MESSAGE_TYPE_BIT0, + SIGFOX_APPLICATION_MESSAGE_TYPE_BIT1, + SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY, +#endif + SIGFOX_APPLICATION_MESSAGE_TYPE_LAST +} SIGFOX_application_message_type_t; +#endif + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) +/*!****************************************************************** + * \enum SIGFOX_control_message_type_t + * \brief Sigfox control message types. + *******************************************************************/ +typedef enum { +#ifdef CONTROL_KEEP_ALIVE_MESSAGE + SIGFOX_CONTROL_MESSAGE_TYPE_KEEP_ALIVE, +#endif +#ifdef BIDIRECTIONAL + SIGFOX_CONTROL_MESSAGE_TYPE_DL_CONFIRMATION, +#endif + SIGFOX_CONTROL_MESSAGE_TYPE_LAST +} SIGFOX_control_message_type_t; +#endif + +#ifndef SINGLE_FRAME +/*!****************************************************************** + * \enum SIGFOX_ul_frame_rank_t + * \brief Sigfox uplink frame rank list. + *******************************************************************/ +typedef enum { + SIGFOX_UL_FRAME_RANK_1 = 0, + SIGFOX_UL_FRAME_RANK_2, + SIGFOX_UL_FRAME_RANK_3, + SIGFOX_UL_FRAME_RANK_LAST +} SIGFOX_ul_frame_rank_t; +#endif + +#if !(defined MESSAGE_COUNTER_ROLLOVER) || (defined PARAMETERS_CHECK) +/*!****************************************************************** + * \enum SIGFOX_message_counter_rollover_t + * \brief Sigfox message counter rollover values list. + *******************************************************************/ +typedef enum { + SIGFOX_MESSAGE_COUNTER_ROLLOVER_128 = 0, + SIGFOX_MESSAGE_COUNTER_ROLLOVER_256, + SIGFOX_MESSAGE_COUNTER_ROLLOVER_512, + SIGFOX_MESSAGE_COUNTER_ROLLOVER_1024, + SIGFOX_MESSAGE_COUNTER_ROLLOVER_2048, + SIGFOX_MESSAGE_COUNTER_ROLLOVER_4096, + SIGFOX_MESSAGE_COUNTER_ROLLOVER_LAST +} SIGFOX_message_counter_rollover_t; +#endif + +#if !(defined MESSAGE_COUNTER_ROLLOVER) || (defined PARAMETERS_CHECK) +/*!****************************************************************** + * \const SIGFOX_EP_API_MESSAGE_COUNTER_ROLLOVER_LIST + * \brief Sigfox message counter value. + *******************************************************************/ +static const sfx_u16 SIGFOX_MESSAGE_COUNTER_ROLLOVER_LIST[SIGFOX_MESSAGE_COUNTER_ROLLOVER_LAST] = {128, 256, 512, 1024, 2048, 4096}; +#endif + +/*** Automatic spectrum access definition according to selected RCs ***/ + +#if (defined RC1) || (defined RC6) || (defined RC7) +#define SPECTRUM_ACCESS_DC +#endif +#if (defined RC2) || (defined RC4) +#define SPECTRUM_ACCESS_FH +#endif +#if (defined RC3C) +#define SPECTRUM_ACCESS_LBT +#define SPECTRUM_ACCESS_LBT_M80 +#endif +#if (defined RC3D) +#define SPECTRUM_ACCESS_LDC +#endif +#if (defined RC5) +#define SPECTRUM_ACCESS_LBT +#define SPECTRUM_ACCESS_LBT_M65 +#endif + +#ifdef SPECTRUM_ACCESS_FH +#define SIGFOX_FH_MACRO_CHANNEL_WIDTH_HZ 200000 +#define SIGFOX_FH_MACRO_CHANNEL_DELTA (SIGFOX_FH_MACRO_CHANNEL_WIDTH_HZ - SIGFOX_MACRO_CHANNEL_WIDTH_HZ) +#define SIGFOX_FH_MICRO_CHANNEL_NUMBER 8 +#define SIGFOX_FH_MICRO_CHANNEL_USED 6 +#define SIGFOX_FH_MICRO_CHANNEL_MASK 0x7E +#define SIGFOX_FH_MICRO_CHANNEL_WIDTH_HZ (SIGFOX_FH_MACRO_CHANNEL_WIDTH_HZ / SIGFOX_FH_MICRO_CHANNEL_NUMBER) +#define SIGFOX_FH_MACRO_CHANNEL_GUARD_BAND_HZ (SIGFOX_FH_MICRO_CHANNEL_WIDTH_HZ - (SIGFOX_FH_MACRO_CHANNEL_DELTA / 2)) +#define SIGFOX_FH_MICRO_CHANNEL_GUARD_BAND_HZ 1000 +#endif + +/*!****************************************************************** + * \enum SIGFOX_spectrum_access_type_t + * \brief Spectrum access types list. + *******************************************************************/ +typedef enum { +#ifdef SPECTRUM_ACCESS_DC + SIGFOX_SPECTRUM_ACCESS_TYPE_DC, +#endif +#ifdef SPECTRUM_ACCESS_LDC + SIGFOX_SPECTRUM_ACCESS_TYPE_LDC, +#endif +#ifdef SPECTRUM_ACCESS_FH + SIGFOX_SPECTRUM_ACCESS_TYPE_FH, +#endif +#ifdef SPECTRUM_ACCESS_LBT + SIGFOX_SPECTRUM_ACCESS_TYPE_LBT, +#endif + SIGFOX_SPECTRUM_ACCESS_TYPE_LAST +} SIGFOX_spectrum_access_type_t; + +/*!****************************************************************** + * \enum SIGFOX_spectrum_access_t + * \brief Spectrum access parameters structure. + *******************************************************************/ +typedef struct { + SIGFOX_spectrum_access_type_t type; +#ifdef BIDIRECTIONAL + // Common parameters. + sfx_u16 dl_t_w_ms; // Delay between the end of the first uplink frame and the beginning of the RX window. + sfx_u16 dl_t_rx_ms; // Maximum duration of the RX window. +#ifndef SINGLE_FRAME + sfx_u16 delta_f_mf_hz; // Fixed frequency offset applied to frames 2 and 3 in bidirectional procedure. +#endif +#endif +#if (defined SPECTRUM_ACCESS_LBT) && (defined REGULATORY) + // For LBT. + sfx_u32 cs_bandwidth_hz; + sfx_s8 cs_threshold_dbm; + sfx_u32 cs_min_duration_ms; + sfx_u32 cs_max_duration_first_frame_ms; +#endif +} SIGFOX_spectrum_access_t; + +/*!****************************************************************** + * \enum SIGFOX_rc_t + * \brief Sigfox radio configuration structure. + *******************************************************************/ +typedef struct { + sfx_u32 f_ul_hz; +#ifdef BIDIRECTIONAL + sfx_u32 f_dl_hz; +#endif + sfx_u16 macro_channel_guard_band_hz; +#ifdef PARAMETERS_CHECK + sfx_u8 uplink_bit_rate_capability; + sfx_s8 tx_power_dbm_eirp_max; +#endif + const SIGFOX_spectrum_access_t *spectrum_access; +} SIGFOX_rc_t; + +/*!****************************************************************** + * \enum SIGFOX_nvm_data_index_t + * \brief Sigfox NVM fields index. + *******************************************************************/ +typedef enum { + SIGFOX_NVM_DATA_INDEX_RANDOM_VALUE_LSB = 0, + SIGFOX_NVM_DATA_INDEX_RANDOM_VALUE_MSB, + SIGFOX_NVM_DATA_INDEX_MESSAGE_COUNTER_LSB, + SIGFOX_NVM_DATA_INDEX_MESSAGE_COUNTER_MSB, + SIGFOX_NVM_DATA_SIZE_BYTES +} SIGFOX_nvm_data_index_t; + +#ifdef PUBLIC_KEY_CAPABLE +/*!****************************************************************** + * \enum SIGFOX_ep_key_t + * \brief Sigfox end-point key type. + *******************************************************************/ +typedef enum { + SIGFOX_EP_KEY_PRIVATE, + SIGFOX_EP_KEY_PUBLIC, + SIGFOX_EP_KEY_LAST +} SIGFOX_ep_key_t; +#endif + +#ifdef CERTIFICATION +/*!****************************************************************** + * \var SIGFOX_EP_TEST_ID + * \var SIGFOX_EP_TEST_KEY + * \brief End-point IDs and keys used for test. + *******************************************************************/ +static const sfx_u8 SIGFOX_EP_TEST_ID[SIGFOX_EP_ID_SIZE_BYTES] = {0xFE, 0xDC, 0xBA, 0x98}; +static const sfx_u8 SIGFOX_EP_TEST_KEY[SIGFOX_EP_KEY_SIZE_BYTES] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; +#endif + +#ifdef PUBLIC_KEY_CAPABLE +/*!****************************************************************** + * \var SIGFOX_EP_PUBLIC_KEY + * \brief End-point public key used for test. + *******************************************************************/ +static const sfx_u8 SIGFOX_EP_PUBLIC_KEY[SIGFOX_EP_KEY_SIZE_BYTES] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; +#endif + +#ifdef VERBOSE +/*!****************************************************************** + * \enum SIGFOX_version_t + * \brief Sigfox EP library components version. + *******************************************************************/ +typedef enum { + SIGFOX_VERSION_EP_LIBRARY = 0, + SIGFOX_VERSION_MCU_DRIVER, + SIGFOX_VERSION_RF_DRIVER, + SIGFOX_VERSION_LAST +} SIGFOX_version_t; +#endif + +/*** Unwanted flag combinations and values ***/ + +#if !(defined RC1) && !(defined RC2) && !(defined RC3C) && !(defined RC3D) && !(defined RC4) && !(defined RC5) && !(defined RC6) && !(defined RC7) +#error "SIGFOX EP LIB flags error: None RC defined" +#endif +#if !(defined APPLICATION_MESSAGES) && !(defined CONTROL_KEEP_ALIVE_MESSAGE) +#error "SIGFOX EP LIB flags error: None message type defined" +#endif +#if !(defined APPLICATION_MESSAGES) && (defined BIDIRECTIONAL) +#error "SIGFOX EP LIB flags error: Bidirectional communication only applies to application messages which are disabled" +#endif +#if (defined UL_BIT_RATE_BPS) && (UL_BIT_RATE_BPS != 100) && (UL_BIT_RATE_BPS != 600) +#error "SIGFOX EP LIB flags error: Invalid UL_BIT_RATE_BPS value" +#endif +#if (defined T_IFU_MS) && (defined SINGLE_FRAME) +#error "SIGFOX EP LIB flags error: T_IFU_MS only applies when SINGLE_FRAME is disabled" +#endif +#ifndef SINGLE_FRAME +#if (defined T_IFU_MS) && (T_IFU_MS > SIGFOX_T_IFU_MAX_MS) +#error "SIGFOX EP LIB flags error: Invalid T_IFU_MS value" +#endif +#endif +#if (defined T_CONF_MS) && !(defined BIDIRECTIONAL) +#error "SIGFOX EP LIB flags error: T_CONF_MS only applies to bidirectional communication which is disabled" +#endif +#ifdef BIDIRECTIONAL +#if (defined T_CONF_MS) && ((T_CONF_MS < SIGFOX_T_CONF_MIN_MS) || (T_CONF_MS > SIGFOX_T_CONF_MAX_MS)) +#error "SIGFOX EP LIB flags error: Invalid T_CONF_MS value" +#endif +#endif +#if (defined UL_PAYLOAD_SIZE) && (UL_PAYLOAD_SIZE > SIGFOX_UL_PAYLOAD_MAX_SIZE_BYTES) +#error "SIGFOX EP LIB flags error: Unsupported UL_PAYLOAD_SIZE value" +#endif +#if (defined MESSAGE_COUNTER_ROLLOVER) && (MESSAGE_COUNTER_ROLLOVER != 128) && (MESSAGE_COUNTER_ROLLOVER != 256) && (MESSAGE_COUNTER_ROLLOVER != 512) && (MESSAGE_COUNTER_ROLLOVER != 1024) && (MESSAGE_COUNTER_ROLLOVER != 2048) && (MESSAGE_COUNTER_ROLLOVER != 4096) +#error "SIGFOX EP LIB flags error: Invalid MESSAGE_COUNTER_ROLLOVER value" +#endif +#if (defined ERROR_STACK) && !(defined ERROR_CODES) +#error "SIGFOX EP LIB flags error: ERROR_CODES are required to use ERROR_STACK" +#endif + +#endif /* __SIGFOX_TYPES_H__ */ diff --git a/src/core/sigfox_crc.c b/src/core/sigfox_crc.c new file mode 100644 index 0000000..d9aab60 --- /dev/null +++ b/src/core/sigfox_crc.c @@ -0,0 +1,115 @@ +/*!***************************************************************** + * \file sigfox_crc.c + * \brief Sigfox CRC driver. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#include "core/sigfox_crc.h" + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" +#include "sigfox_error.h" + +#ifndef CRC_HW + +/*** SIGFOX CRC functions ***/ + +/*******************************************************************/ +SIGFOX_CRC_status_t SIGFOX_CRC_compute_crc16(sfx_u8 *crc_data, sfx_u8 data_size, sfx_u16 polynom, sfx_u16 *crc) { + // Local variables. + sfx_u8 i = 0; + sfx_u8 j = 0; +#ifdef ERROR_CODES + SIGFOX_CRC_status_t status = SIGFOX_CRC_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + if ((crc_data == SFX_NULL) || (crc == SFX_NULL)) { + EXIT_ERROR(SIGFOX_CRC_ERROR_NULL_PARAMETER); + } +#endif + // Compute CRC. + (*crc) = 0; + for (j=0 ; j 0) { + for (idx=0 ; idx 0) { + for (idx=0 ; idx> 8) & 0xFF; + sigfox_ep_bitstream_ctx.bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + sigfox_ep_bitstream_ctx.ul_payload_size_bytes + sigfox_ep_bitstream_ctx.ul_auth_size_bytes + 1] = (ul_crc >> 0) & 0xFF; +#else + sigfox_ep_bitstream_ctx.bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + UL_PAYLOAD_SIZE + sigfox_ep_bitstream_ctx.ul_auth_size_bytes + 0] = (ul_crc >> 8) & 0xFF; + sigfox_ep_bitstream_ctx.bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + UL_PAYLOAD_SIZE + sigfox_ep_bitstream_ctx.ul_auth_size_bytes + 1] = (ul_crc >> 0) & 0xFF; +#endif +#ifdef ERROR_CODES +errors:; +#endif + RETURN(); +} + +#ifndef SINGLE_FRAME +/*******************************************************************/ +static void _convolve(SIGFOX_ul_frame_rank_t ul_frame_rank) { + // Local variables. + sfx_u8 initial_bitstream[SIGFOX_EP_BITSTREAM_SIZE_BYTES]; + sfx_u8 idx = 0; + sfx_u8 mask = 0; + sfx_u8 state = 0; +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) || !(defined UL_PAYLOAD_SIZE) + sfx_u8 convolution_input_size_bytes = (SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_SIZE_BYTES + SIGFOX_EP_ID_SIZE_BYTES + sigfox_ep_bitstream_ctx.ul_payload_size_bytes + sigfox_ep_bitstream_ctx.ul_auth_size_bytes + SIGFOX_EP_BITSTREAM_UL_CRC_SIZE_BYTES); +#else + sfx_u8 convolution_input_size_bytes = (SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_SIZE_BYTES + SIGFOX_EP_ID_SIZE_BYTES + UL_PAYLOAD_SIZE + sigfox_ep_bitstream_ctx.ul_auth_size_bytes + SIGFOX_EP_BITSTREAM_UL_CRC_SIZE_BYTES); +#endif + // Init buffers. + for (idx=0; idx>=1) { + if (initial_bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX + idx] & mask) { + sigfox_ep_bitstream_ctx.bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX + idx] |= mask * ((SIGFOX_EP_BITSTREAM_UL_CONVOLUTION_LUT_IN_OUT[ul_frame_rank][state] == 0) ? 1 : 0); + state = SIGFOX_EP_BITSTREAM_UL_CONVOLUTION_LUT_IN[state] + 2; + } + else { + sigfox_ep_bitstream_ctx.bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX + idx] |= mask * SIGFOX_EP_BITSTREAM_UL_CONVOLUTION_LUT_IN_OUT[ul_frame_rank][state]; + state = SIGFOX_EP_BITSTREAM_UL_CONVOLUTION_LUT_IN[state]; + } + } + } +} +#endif + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +static sfx_u16 _dewhitening_pn(sfx_u16 pn_value) { + // Local variables. + sfx_u8 msb = 0; + sfx_u8 roll = 0; + // Compute algorithm. + for (roll=8 ; roll>0 ; roll--) { + msb = (sfx_u8) (pn_value & 0x0021); + pn_value >>= 1; + pn_value |= (msb == 0x20 || msb == 0x01) ? 0x100 : 0x000; + } + return pn_value; +} +#endif + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +static void _dewhitening(SIGFOX_EP_BITSTREAM_dl_frame_t *input, sfx_u8 *local_bitstream) { + // Local variables. + sfx_u8 idx = 0; + sfx_u32 ep_id_32bits = 0; + sfx_u32 pn = 0; + // Convert EP ID array to 32-bits value. + for (idx=0 ; idx ep_id[SIGFOX_EP_ID_SIZE_BYTES - 1 - idx]) << (8 * idx); + } + // Set initial value as device ID * message counter. + pn = ep_id_32bits * ((sfx_u32) (input-> message_counter)); + // Apply 9-bits mask. + pn &= SIGFOX_EP_BITSTREAM_DL_DEWHITENING_MASK; + if (pn == 0) { + pn = SIGFOX_EP_BITSTREAM_DL_DEWHITENING_MASK; + } + // Perform dewhitening algorithm. + for (idx=0 ; idx<8; idx++) { + pn = _dewhitening_pn((sfx_u16) pn); + local_bitstream[idx + 0] ^= pn >> (idx + 1); + local_bitstream[idx + 1] ^= (pn & ((1 << (idx + 1)) - 1)) << (7 - idx); + } + for (idx=0; idx<5; idx++) { + pn = _dewhitening_pn((sfx_u16) pn); + local_bitstream[idx + 9] ^= pn >> (idx + 1); + local_bitstream[idx + 10] ^= (pn & ((1 << (idx + 1)) - 1)) << (7 - idx); + } + pn = _dewhitening_pn((sfx_u16) pn); + local_bitstream[14] ^= pn >> (idx + 1); +} +#endif + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +static void _decode_bch(sfx_u8* local_bitstream) { + // Local variables. + sfx_u8 i = 0; + sfx_u8 j = 0; + sfx_u8 syndrome = 0; + // Perform ECC algorithm. + for(i=0 ; i<8; i++) { + // Compute code syndrome. + syndrome = 0; + for(j=0 ; j<15 ; j++) { + syndrome ^= SIGFOX_EP_BITSTREAM_DL_POW_ALPHA_LUT[j] * ((local_bitstream[j] >> i) & 1); + } + // The BCH(15,11) code can only correct a single error. + // If the syndrome is not zero, it indicates the bit in error. + // If the syndrome is zero, there is no error. To simplify the code, word 16 is changed. + // Word 16 is actually a dummy word. + local_bitstream[SIGFOX_EP_BITSTREAM_DL_LOG_ALPHA_LUT[syndrome]] ^= 1 << i; + } +} +#endif + +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) +/*******************************************************************/ +static SIGFOX_EP_BITSTREAM_status_t _check_common_parameters(SIGFOX_EP_BITSTREAM_common_t *common_params) { + // Local variables. + SIGFOX_EP_BITSTREAM_status_t status = SIGFOX_EP_BITSTREAM_SUCCESS; + // Check parameter. + if (common_params == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_NULL_PARAMETER); + } + // Check ID. + if ((common_params -> ep_id) == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_NULL_PARAMETER); + } +#ifndef SINGLE_FRAME + // Check frame rank. + if ((common_params -> ul_frame_rank) >= SIGFOX_UL_FRAME_RANK_LAST) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_FRAME_RANK); + } +#endif + // Check message counter. +#ifdef MESSAGE_COUNTER_ROLLOVER + if ((common_params -> message_counter) >= MESSAGE_COUNTER_ROLLOVER) { +#else + if ((common_params -> message_counter) >= (common_params -> message_counter_rollover)) { +#endif + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_MESSAGE_COUNTER); + } +#ifdef PUBLIC_KEY_CAPABLE + // Check key type. + if ((common_params -> ep_key_type) >= SIGFOX_EP_KEY_LAST) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_KEY_TYPE); + } +#endif +errors: + return status; +} +#endif + +#if (defined APPLICATION_MESSAGES) && (defined PARAMETERS_CHECK) && (defined ERROR_CODES) + /*******************************************************************/ +static SIGFOX_EP_BITSTREAM_status_t _check_application_parameters(SIGFOX_EP_BITSTREAM_application_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes) { + // Local variables. + SIGFOX_EP_BITSTREAM_status_t status = SIGFOX_EP_BITSTREAM_SUCCESS; + // Check parameters. + if ((input == SFX_NULL) || (bitstream == SFX_NULL) || (bitstream_size_bytes == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_NULL_PARAMETER); + } + // Check common parameters. + status = _check_common_parameters(&(input -> common_parameters)); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#if !(defined UL_PAYLOAD_SIZE) || (UL_PAYLOAD_SIZE > 0) + if ((input -> message_type) == SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY) { + // Payload is required. + if (((input -> ul_payload) == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_NULL_PARAMETER); + } + } +#endif +#ifndef UL_PAYLOAD_SIZE + if ((input -> message_type) == SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY) { + // Length is required. + if (((input -> ul_payload_size_bytes) == 0) || ((input -> ul_payload_size_bytes) > SIGFOX_UL_PAYLOAD_MAX_SIZE_BYTES)) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_PAYLOAD_SIZE); + } + } +#endif +errors: + return status; +} +#endif + +#if ((defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL)) && (defined PARAMETERS_CHECK) && (defined ERROR_CODES) +/*******************************************************************/ +static SIGFOX_EP_BITSTREAM_status_t _check_control_parameters(SIGFOX_EP_BITSTREAM_control_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes) { + // Local variables. + SIGFOX_EP_BITSTREAM_status_t status = SIGFOX_EP_BITSTREAM_SUCCESS; + // Check parameters. + if ((input == SFX_NULL) || (bitstream == SFX_NULL) || (bitstream_size_bytes == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_NULL_PARAMETER); + } + // Check common parameters. + status = _check_common_parameters(&(input -> common_parameters)); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +errors: + return status; +} +#endif + +#if (defined BIDIRECTIONAL) && (defined PARAMETERS_CHECK) && (defined ERROR_CODES) +/*******************************************************************/ +static SIGFOX_EP_BITSTREAM_status_t _check_dl_parameters(SIGFOX_EP_BITSTREAM_dl_frame_t *input, sfx_u8 *dl_payload) { + // Local variables. + SIGFOX_EP_BITSTREAM_status_t status = SIGFOX_EP_BITSTREAM_SUCCESS; + // Check parameters. + if ((input == SFX_NULL) || ((input-> dl_phy_content) == SFX_NULL) || ((input -> ep_id) == SFX_NULL) || (dl_payload == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_NULL_PARAMETER); + } +errors: + return status; +} +#endif + +/*** SIGFOX EP BITSTREAM functions ***/ + +#ifdef APPLICATION_MESSAGES +/*******************************************************************/ +SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_build_application_frame(SIGFOX_EP_BITSTREAM_application_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes) { + // Local variables. + sfx_u8 idx = 0; +#ifdef ERROR_CODES + SIGFOX_EP_BITSTREAM_status_t status = SIGFOX_EP_BITSTREAM_SUCCESS; +#endif +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) + status = _check_application_parameters(input, bitstream, bitstream_size_bytes); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#endif + // Default values. + sigfox_ep_bitstream_ctx.ul_li = 0; + sigfox_ep_bitstream_ctx.ul_auth_size_bytes = 2; + sigfox_ep_bitstream_ctx.bitstream = bitstream; +#ifdef PUBLIC_KEY_CAPABLE + sigfox_ep_bitstream_ctx.key = input->common_parameters.ep_key_type; +#endif +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) || !(defined UL_PAYLOAD_SIZE) + // UL payload size. +#ifdef UL_PAYLOAD_SIZE + sigfox_ep_bitstream_ctx.ul_payload_size_bytes = UL_PAYLOAD_SIZE; +#else + sigfox_ep_bitstream_ctx.ul_payload_size_bytes = (input -> ul_payload_size_bytes); +#endif +#endif + // UL-PR. + for (idx=0 ; idx common_parameters).ul_frame_rank)]; +#endif /* SINGLE_FRAME */ +#if (UL_PAYLOAD_SIZE == 0) + switch (input -> message_type) { + case SIGFOX_APPLICATION_MESSAGE_TYPE_BIT0: + sigfox_ep_bitstream_ctx.ul_li = 2; + break; + case SIGFOX_APPLICATION_MESSAGE_TYPE_BIT1: + sigfox_ep_bitstream_ctx.ul_li = 3; + break; + default: + // LI=0 for empty frame. + break; + } +#endif /* UL_PAYLOAD_SIZE = 0 */ +#if (UL_PAYLOAD_SIZE >= 2) + sigfox_ep_bitstream_ctx.ul_li = 3 - ((UL_PAYLOAD_SIZE - 1) % 4); + sigfox_ep_bitstream_ctx.ul_auth_size_bytes = (sigfox_ep_bitstream_ctx.ul_li + 2); +#endif +#else /* UL_PAYLOAD_SIZE */ + switch (input -> message_type) { + case SIGFOX_APPLICATION_MESSAGE_TYPE_EMPTY: + // Empty frame. +#ifdef SINGLE_FRAME + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[0]; +#else + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[((input -> common_parameters).ul_frame_rank)][0]; +#endif + break; + case SIGFOX_APPLICATION_MESSAGE_TYPE_BIT0: + // Empty and bit frames. +#ifdef SINGLE_FRAME + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[0]; +#else + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[((input -> common_parameters).ul_frame_rank)][0]; +#endif + sigfox_ep_bitstream_ctx.ul_li = 2; + break; + case SIGFOX_APPLICATION_MESSAGE_TYPE_BIT1: + // Empty and bit frames. +#ifdef SINGLE_FRAME + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[0]; +#else + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[((input -> common_parameters).ul_frame_rank)][0]; +#endif + sigfox_ep_bitstream_ctx.ul_li = 3; + break; + case SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY: + if (sigfox_ep_bitstream_ctx.ul_payload_size_bytes == 1) { + // 1-byte frame. +#ifdef SINGLE_FRAME + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[1]; +#else + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[((input -> common_parameters).ul_frame_rank)][1]; +#endif + } + else { + // 2 to 12-bytes frames. +#ifdef SINGLE_FRAME + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[((sigfox_ep_bitstream_ctx.ul_payload_size_bytes - 1) / 4) + 2]; +#else + sigfox_ep_bitstream_ctx.ul_ft = SIGFOX_EP_BITSTREAM_UL_FT_APPLICATION_TABLE[((input -> common_parameters).ul_frame_rank)][((sigfox_ep_bitstream_ctx.ul_payload_size_bytes - 1) / 4) + 2]; +#endif + sigfox_ep_bitstream_ctx.ul_li = 3 - ((sigfox_ep_bitstream_ctx.ul_payload_size_bytes - 1) % 4); + sigfox_ep_bitstream_ctx.ul_auth_size_bytes = (sigfox_ep_bitstream_ctx.ul_li + 2); + } + break; + default: +#ifdef ERROR_CODES + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_MESSAGE_TYPE); +#else + goto errors; +#endif + break; + } +#endif /* UL_PAYLOAD_SIZE */ + bitstream[SIGFOX_EP_BITSTREAM_UL_FT_INDEX + 0] = (sfx_u8) ((sigfox_ep_bitstream_ctx.ul_ft & 0xFF00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_UL_FT_INDEX + 1] = (sfx_u8) ((sigfox_ep_bitstream_ctx.ul_ft & 0x00FF) >> 0); + // LI and BF. + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX] = 0x00; + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX] |= (sigfox_ep_bitstream_ctx.ul_li << SIGFOX_EP_BITSTREAM_LI_BIT_INDEX); +#ifdef BIDIRECTIONAL + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX] |= (((input -> bidirectional_flag) & 0x01) << SIGFOX_EP_BITSTREAM_BF_BIT_INDEX); +#endif + // Message counter. + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX + 0] |= (sfx_u8) ((((input -> common_parameters).message_counter) & 0x0F00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX + 1] = (sfx_u8) ((((input -> common_parameters).message_counter) & 0x00FF) >> 0); + // EP-ID (LSByte first). + for (idx=0 ; idx common_parameters).ep_id)[SIGFOX_EP_ID_SIZE_BYTES - 1 - idx]; + } + (*bitstream_size_bytes) = (SIGFOX_EP_BITSTREAM_EP_ID_INDEX + SIGFOX_EP_ID_SIZE_BYTES); +#ifdef UL_PAYLOAD_SIZE +#if (UL_PAYLOAD_SIZE > 0) + // UL-PAYLOAD (MSByte first). + for (idx=0 ; idx ul_payload)[idx]; + } + (*bitstream_size_bytes) += UL_PAYLOAD_SIZE; +#endif +#else /* UL_PAYLOAD_SIZE */ + // UL-PAYLOAD (MSByte first). + if ((input -> message_type) == SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY) { + for (idx=0 ; idx ul_payload)[idx]; + } + } + else { + sigfox_ep_bitstream_ctx.ul_payload_size_bytes = 0; // No payload field for EMPTY and BIT message types. + } + (*bitstream_size_bytes) += sigfox_ep_bitstream_ctx.ul_payload_size_bytes; +#endif /* UL_PAYLOAD_SIZE */ + // UL_AUTH. +#ifdef ERROR_CODES + status = _aes_encrypt(); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#else + _aes_encrypt(); +#endif + (*bitstream_size_bytes) += sigfox_ep_bitstream_ctx.ul_auth_size_bytes; + // CRC. +#ifdef ERROR_CODES + status = _add_crc16(); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#else + _add_crc16(); +#endif + (*bitstream_size_bytes) += SIGFOX_EP_BITSTREAM_UL_CRC_SIZE_BYTES; +#ifndef SINGLE_FRAME + // Convolution. + _convolve((input -> common_parameters).ul_frame_rank); +#endif +#if (defined ERROR_CODES) || !(defined UL_PAYLOAD_SIZE) +errors: +#endif + RETURN(); +} +#endif + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) +/*******************************************************************/ +SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_build_control_frame(SIGFOX_EP_BITSTREAM_control_frame_t *input, sfx_u8 *bitstream, sfx_u8 *bitstream_size_bytes) { + // Local variables. + sfx_u8 idx = 0; +#ifdef ERROR_CODES + SIGFOX_EP_BITSTREAM_status_t status = SIGFOX_EP_BITSTREAM_SUCCESS; +#endif +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) + status = _check_control_parameters(input, bitstream, bitstream_size_bytes); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#endif + // Default values + sigfox_ep_bitstream_ctx.ul_li = 0; + sigfox_ep_bitstream_ctx.ul_auth_size_bytes = 2; + sigfox_ep_bitstream_ctx.bitstream = bitstream; +#ifdef PUBLIC_KEY_CAPABLE + sigfox_ep_bitstream_ctx.key = (input -> common_parameters).ep_key_type; +#endif +#ifdef BIDIRECTIONAL + sfx_s8 rssi_plus_100 = (sfx_s8) ((input -> rssi_dbm) + 100); +#endif + // UL-PR. + for (idx=0 ; idx common_parameters).ul_frame_rank)]; +#endif + bitstream[SIGFOX_EP_BITSTREAM_UL_FT_INDEX + 0] = (sfx_u8) ((sigfox_ep_bitstream_ctx.ul_ft & 0xFF00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_UL_FT_INDEX + 1] = (sfx_u8) ((sigfox_ep_bitstream_ctx.ul_ft & 0x00FF) >> 0); + // Payload size. + switch (input -> message_type) { +#ifdef CONTROL_KEEP_ALIVE_MESSAGE + case SIGFOX_CONTROL_MESSAGE_TYPE_KEEP_ALIVE: + sigfox_ep_bitstream_ctx.ul_payload_size_bytes = SIGFOX_EP_BITSTREAM_CONTROL_KEEP_ALIVE_PAYLOAD_SIZE_BYTES; + break; +#endif +#ifdef BIDIRECTIONAL + case SIGFOX_CONTROL_MESSAGE_TYPE_DL_CONFIRMATION: + sigfox_ep_bitstream_ctx.ul_payload_size_bytes = SIGFOX_EP_BITSTREAM_CONTROL_DL_CONFIRMATION_PAYLOAD_SIZE_BYTES; + break; +#endif + default: + EXIT_ERROR(SIGFOX_EP_BITSTREAM_ERROR_MESSAGE_TYPE); + break; + } + sigfox_ep_bitstream_ctx.ul_li = 3 - ((sigfox_ep_bitstream_ctx.ul_payload_size_bytes - 1) % 4); + sigfox_ep_bitstream_ctx.ul_auth_size_bytes = (sigfox_ep_bitstream_ctx.ul_li + 2); + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX] = 0x00; + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX] |= (sigfox_ep_bitstream_ctx.ul_li << SIGFOX_EP_BITSTREAM_LI_BIT_INDEX); + // Message counter. + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX + 0] |= (sfx_u8) ((((input -> common_parameters).message_counter) & 0x0F00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_LI_BF_REP_MC_INDEX + 1] = (sfx_u8) ((((input -> common_parameters).message_counter) & 0x00FF) >> 0); + // EP-ID (LSByte first). + for (idx=0 ; idx common_parameters).ep_id)[SIGFOX_EP_ID_SIZE_BYTES - 1 - idx]; + } + (*bitstream_size_bytes) = (SIGFOX_EP_BITSTREAM_EP_ID_INDEX + SIGFOX_EP_ID_SIZE_BYTES); + // UL-PAYLOAD (MSByte first). + switch (input -> message_type) { +#ifdef CONTROL_KEEP_ALIVE_MESSAGE + case SIGFOX_CONTROL_MESSAGE_TYPE_KEEP_ALIVE: + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 0] = SIGFOX_EP_BITSTREAM_CONTROL_KEEP_ALIVE_CT; + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 1] = (sfx_u8) (((input -> voltage_idle_mv) & 0x00FF) >> 0); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 2] = (sfx_u8) (((input -> voltage_idle_mv) & 0xFF00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 3] = (sfx_u8) (((input -> voltage_tx_mv) & 0x00FF) >> 0); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 4] = (sfx_u8) (((input -> voltage_tx_mv) & 0xFF00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 5] = (sfx_u8) (((input -> temperature_tenth_degrees) & 0x00FF) >> 0); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 6] = (sfx_u8) (((input -> temperature_tenth_degrees) & 0xFF00) >> 8); + break; +#endif +#ifdef BIDIRECTIONAL + case SIGFOX_CONTROL_MESSAGE_TYPE_DL_CONFIRMATION: + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 0] = SIGFOX_EP_BITSTREAM_CONTROL_DL_CONFIRMATION_CT; + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 1] = (sfx_u8) (((input -> voltage_idle_mv) & 0x00FF) >> 0); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 2] = (sfx_u8) (((input -> voltage_idle_mv) & 0xFF00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 3] = (sfx_u8) (((input -> voltage_tx_mv) & 0x00FF) >> 0); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 4] = (sfx_u8) (((input -> voltage_tx_mv) & 0xFF00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 5] = (sfx_u8) (((input -> temperature_tenth_degrees) & 0x00FF) >> 0); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 6] = (sfx_u8) (((input -> temperature_tenth_degrees) & 0xFF00) >> 8); + bitstream[SIGFOX_EP_BITSTREAM_UL_PAYLOAD_INDEX + 7] = (sfx_u8) (rssi_plus_100); + break; +#endif + default: + break; + } + (*bitstream_size_bytes) += sigfox_ep_bitstream_ctx.ul_payload_size_bytes; + // UL_AUTH. +#ifdef ERROR_CODES + status = _aes_encrypt(); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#else + _aes_encrypt(); +#endif + (*bitstream_size_bytes) += sigfox_ep_bitstream_ctx.ul_auth_size_bytes; + // CRC. +#ifdef ERROR_CODES + status = _add_crc16(); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#else + _add_crc16(); +#endif + (*bitstream_size_bytes) += SIGFOX_EP_BITSTREAM_UL_CRC_SIZE_BYTES; +#ifndef SINGLE_FRAME + // Convolution. + _convolve((input -> common_parameters).ul_frame_rank); +#endif +errors: + RETURN(); +} +#endif + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +SIGFOX_EP_BITSTREAM_status_t SIGFOX_EP_BITSTREAM_decode_downlink_frame(SIGFOX_EP_BITSTREAM_dl_frame_t *input, sfx_bool *dl_frame_valid, sfx_u8 *dl_payload) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_EP_BITSTREAM_status_t status = SIGFOX_EP_BITSTREAM_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#ifndef CRC_HW + SIGFOX_CRC_status_t crc_status = SIGFOX_CRC_SUCCESS; +#endif +#endif + MCU_API_encryption_data_t encryption_data; + sfx_u8 idx = 0; + sfx_u8 byte_idx = 0; + sfx_u8 local_bitstream[SIGFOX_DL_PHY_CONTENT_SIZE_BYTES]; + sfx_u8 dl_crc; + sfx_u8 aes_data[SIGFOX_EP_KEY_SIZE_BYTES]; + // Reset result. + (*dl_frame_valid) = SFX_FALSE; +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) + // Check parameters. + status = _check_dl_parameters(input, dl_payload); + CHECK_STATUS(SIGFOX_EP_BITSTREAM_SUCCESS); +#endif + // Copy bitstream to local buffer. + for (idx=0 ; idx dl_phy_content[idx]; + } + // Step 1: de-whitening. + _dewhitening(input, local_bitstream); + // Step 2: ECC. + _decode_bch(local_bitstream); + // Step 3: CRC check. +#ifdef CRC_HW +#ifdef ERROR_CODES + mcu_status = MCU_API_compute_crc8(&(local_bitstream[SIGFOX_EP_BITSTREAM_DL_PAYLOAD_INDEX]), (SIGFOX_DL_PAYLOAD_SIZE_BYTES + SIGFOX_EP_BITSTREAM_DL_AUTH_SIZE_BYTES), SIGFOX_EP_BITSTREAM_DL_CRC_POLYNOM, &dl_crc); + MCU_API_check_status(SIGFOX_EP_BITSTREAM_ERROR_MCU); +#else + MCU_API_compute_crc8(&(local_bitstream[SIGFOX_EP_BITSTREAM_DL_PAYLOAD_INDEX]), (SIGFOX_DL_PAYLOAD_SIZE_BYTES + SIGFOX_EP_BITSTREAM_DL_AUTH_SIZE_BYTES), SIGFOX_EP_BITSTREAM_DL_CRC_POLYNOM, &dl_crc); +#endif +#else +#ifdef ERROR_CODES + crc_status = SIGFOX_CRC_compute_crc8(&(local_bitstream[SIGFOX_EP_BITSTREAM_DL_PAYLOAD_INDEX]), (SIGFOX_DL_PAYLOAD_SIZE_BYTES + SIGFOX_EP_BITSTREAM_DL_AUTH_SIZE_BYTES), SIGFOX_EP_BITSTREAM_DL_CRC_POLYNOM, &dl_crc); + SIGFOX_CRC_check_status(SIGFOX_EP_BITSTREAM_ERROR_CRC); +#else + SIGFOX_CRC_compute_crc8(&(local_bitstream[SIGFOX_EP_BITSTREAM_DL_PAYLOAD_INDEX]), (SIGFOX_DL_PAYLOAD_SIZE_BYTES + SIGFOX_EP_BITSTREAM_DL_AUTH_SIZE_BYTES), SIGFOX_EP_BITSTREAM_DL_CRC_POLYNOM, &dl_crc); +#endif +#endif + if (dl_crc != local_bitstream[SIGFOX_EP_BITSTREAM_DL_CRC_INDEX]) goto errors; + // Step 4: authentication check. + // Build input data. + // EP-ID (LSByte first). + for (idx=0 ; idx ep_id[SIGFOX_EP_ID_SIZE_BYTES - 1 - idx]; + } + // Message counter. + aes_data[byte_idx++] = (sfx_u8) (((input -> message_counter) >> 0) & 0xFF); + aes_data[byte_idx++] = (sfx_u8) (((input -> message_counter) >> 8) & 0x0F); + // DL-PAYLOAD. + for (idx=0 ; idx ep_id[3]; + aes_data[byte_idx++] = input -> ep_id[2]; + // Build input structure. + encryption_data.data = aes_data; + encryption_data.data_size_bytes = SIGFOX_EP_KEY_SIZE_BYTES; +#ifdef PUBLIC_KEY_CAPABLE + encryption_data.key = (input -> ep_key_type); +#endif + // Perform encryption. +#ifdef ERROR_CODES + mcu_status = MCU_API_aes_128_cbc_encrypt(&encryption_data); + MCU_API_check_status(SIGFOX_EP_BITSTREAM_ERROR_MCU); +#else + MCU_API_aes_128_cbc_encrypt(&encryption_data); +#endif + // Compare received and computed DL-AUTH fields. + if ((local_bitstream[SIGFOX_EP_BITSTREAM_DL_AUTH_INDEX] != aes_data[0]) || (local_bitstream[SIGFOX_EP_BITSTREAM_DL_AUTH_INDEX + 1] != aes_data[1])) { + goto errors; + } + // Valid frame received: extract DL payload. + for (idx=0 ; idx macro_channel_guard_band_hz); + sigfox_ep_frequency_ctx.baseband_frequency_max_hz = SIGFOX_MACRO_CHANNEL_WIDTH_HZ - ((sigfox_ep_frequency_ctx.rc) -> macro_channel_guard_band_hz); +#if (defined BIDIRECTIONAL) && !(defined SINGLE_FRAME) + // Add offset in case of bidirectional procedure. + sigfox_ep_frequency_ctx.baseband_frequency_min_hz += (((sigfox_ep_frequency_ctx.rc) -> spectrum_access) -> delta_f_mf_hz) * ((sigfox_ep_frequency_ctx.input) -> bidirectional_flag); + sigfox_ep_frequency_ctx.baseband_frequency_max_hz -= (((sigfox_ep_frequency_ctx.rc) -> spectrum_access) -> delta_f_mf_hz) * ((sigfox_ep_frequency_ctx.input) -> bidirectional_flag); +#endif + // Compute bandwidth. + sigfox_ep_frequency_ctx.baseband_bandwidth_hz = (sigfox_ep_frequency_ctx.baseband_frequency_max_hz - sigfox_ep_frequency_ctx.baseband_frequency_min_hz); +} + +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) && !(defined SINGLE_FRAME) +/*******************************************************************/ +static sfx_bool _is_micro_channel_free(sfx_u8 micro_channel_index) { + // Thanks to the FH timer, a micro-channel is free when all frames of the message have been sent. + sfx_bool micro_channel_free = (sigfox_ep_frequency_ctx.micro_channel_frame_count[micro_channel_index] >= SIGFOX_UL_FRAME_RANK_LAST) ? SFX_TRUE : SFX_FALSE; + return micro_channel_free; +} +#endif + +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) +/*******************************************************************/ +static sfx_u8 _get_micro_channel_index(sfx_u32 baseband_frequency_hz) { + // Local variables. + sfx_u8 micro_channel_index = 0; + // Compute index. + micro_channel_index = ((baseband_frequency_hz + (SIGFOX_FH_MACRO_CHANNEL_DELTA / 2)) / SIGFOX_FH_MICRO_CHANNEL_WIDTH_HZ); + micro_channel_index %= SIGFOX_FH_MICRO_CHANNEL_NUMBER; + // Check micro-channels mask. + if (((SIGFOX_FH_MICRO_CHANNEL_MASK >> micro_channel_index) & 0x01) == 0) { + micro_channel_index = SIGFOX_EP_FREQUENCY_FH_MICRO_CHANNEL_ERROR; + goto errors; + } + // Check low side micro-channel guard band. + if ((baseband_frequency_hz - (SIGFOX_FH_MACRO_CHANNEL_GUARD_BAND_HZ + (SIGFOX_FH_MICRO_CHANNEL_WIDTH_HZ * (micro_channel_index - 1)))) < SIGFOX_FH_MICRO_CHANNEL_GUARD_BAND_HZ) { + micro_channel_index = SIGFOX_EP_FREQUENCY_FH_MICRO_CHANNEL_ERROR; + goto errors; + } + // Check high side micro-channel guard band. + if (((SIGFOX_FH_MACRO_CHANNEL_GUARD_BAND_HZ + (SIGFOX_FH_MICRO_CHANNEL_WIDTH_HZ * micro_channel_index)) - baseband_frequency_hz) < SIGFOX_FH_MICRO_CHANNEL_GUARD_BAND_HZ) { + micro_channel_index = SIGFOX_EP_FREQUENCY_FH_MICRO_CHANNEL_ERROR; + goto errors; + } +errors: + return micro_channel_index; +} +#endif + +/*******************************************************************/ +static SIGFOX_EP_FREQUENCY_status_t _compute_new_random_frequency(sfx_u32 *frequency_hz) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_EP_FREQUENCY_status_t status = SIGFOX_EP_FREQUENCY_SUCCESS; +#endif + sfx_u32 baseband_frequency_hz = 0; + sfx_u32 random_generation_count = 0; + sfx_bool baseband_frequency_allowed = SFX_FALSE; +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + sfx_u8 micro_channel_index = 0; +#endif + // Call the random algorithm until a valid frequency is computed. + do { + // Generate new random value. + sigfox_ep_frequency_ctx.random_value = (SIGFOX_EP_FREQUENCY_RANDOM_MULTIPLIER * sigfox_ep_frequency_ctx.random_value) + sigfox_ep_frequency_ctx.random_offset; + sigfox_ep_frequency_ctx.random_value &= SIGFOX_EP_FREQUENCY_RANDOM_VALUE_MASK; + random_generation_count++; + // Convert to baseband frequency. + baseband_frequency_hz = ((sfx_u32) sigfox_ep_frequency_ctx.baseband_frequency_min_hz); + baseband_frequency_hz += (((sfx_u32) sigfox_ep_frequency_ctx.random_value) * ((sfx_u32) (sigfox_ep_frequency_ctx.baseband_bandwidth_hz))) / (SIGFOX_EP_FREQUENCY_RANDOM_VALUE_MAX); +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + // Additional check on micro-channel in case of FCC + if (((sigfox_ep_frequency_ctx.rc) -> spectrum_access -> type) == SIGFOX_SPECTRUM_ACCESS_TYPE_FH) { + // Get micro channel index. + micro_channel_index = _get_micro_channel_index(baseband_frequency_hz); + if (micro_channel_index != SIGFOX_EP_FREQUENCY_FH_MICRO_CHANNEL_ERROR) { +#ifdef SINGLE_FRAME + // Thanks to the FH timer, a given micro-channels is always free in single frame mode. + baseband_frequency_allowed = SFX_TRUE; +#else + // Micro-channel index is valid, check if it is free. + baseband_frequency_allowed = _is_micro_channel_free(micro_channel_index); +#endif + } + else { + // Frequency is in guard band. + baseband_frequency_allowed = SFX_FALSE; + } + } + else { + // Frequency is valid. + baseband_frequency_allowed = SFX_TRUE; + } +#else + baseband_frequency_allowed = SFX_TRUE; +#endif + // Check request count. + if (random_generation_count > SIGFOX_EP_FREQUENCY_RANDOM_GENERATION_LIMIT) { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_RANDOM_GENERATION); + } + } + while (baseband_frequency_allowed == SFX_FALSE); + // Convert to absolute frequency. + (*frequency_hz) = ((sigfox_ep_frequency_ctx.rc) -> f_ul_hz) - (SIGFOX_MACRO_CHANNEL_WIDTH_HZ / 2) + baseband_frequency_hz; +#ifdef BIDIRECTIONAL + // Store frame 1 frequency for future computation if bidirectional. +#ifdef SINGLE_FRAME + sigfox_ep_frequency_ctx.frame_1_frequency_hz = (*frequency_hz); +#else + if (((sigfox_ep_frequency_ctx.input) -> ul_frame_rank) == SIGFOX_UL_FRAME_RANK_1) { + sigfox_ep_frequency_ctx.frame_1_frequency_hz = (*frequency_hz); + } +#endif /* SINGLE_FRAME */ +#endif /* BIDIRECTIONAL */ +errors: + RETURN(); +} + +/*** SIGFOX EP FREQUENCY functions ***/ + +/*******************************************************************/ +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_init(const SIGFOX_rc_t *rc, sfx_u8 *ep_id, sfx_u16 last_random_value) { + // Local variables. + sfx_u32 ep_id_16bits = 0; +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + sfx_u8 idx = 0; +#endif +#ifdef ERROR_CODES + SIGFOX_EP_FREQUENCY_status_t status = SIGFOX_EP_FREQUENCY_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + // Check parameter. + if ((rc == SFX_NULL) || (ep_id == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_NULL_PARAMETER); + } +#endif /* PARAMETERS_CHECK */ + // Store RC and last random value. + sigfox_ep_frequency_ctx.rc = rc; + sigfox_ep_frequency_ctx.random_value = last_random_value; + // Compute random offset with EP-ID. + ep_id_16bits = ((((sfx_u32) ep_id[2]) << 8) & 0xFF00) | (((sfx_u32) ep_id[3]) & 0x00FF); + sigfox_ep_frequency_ctx.random_offset = (SIGFOX_EP_FREQUENCY_RANDOM_MULTIPLIER * (ep_id_16bits + 1)) & SIGFOX_EP_FREQUENCY_RANDOM_VALUE_MASK; + if (((sigfox_ep_frequency_ctx.random_offset) % 2) == 0) { + sigfox_ep_frequency_ctx.random_offset++; + } +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + // Init micro-channels frame count (only once). + if (sigfox_ep_frequency_ctx.micro_channel_table_initialized == SFX_FALSE) { + for (idx=0 ; idx> idx) & 0x01); + } + sigfox_ep_frequency_ctx.micro_channel_table_initialized = SFX_TRUE; + } +#endif +#ifdef PARAMETERS_CHECK +errors: +#endif + RETURN(); +} + +/*******************************************************************/ +#ifdef SINGLE_FRAME +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_uplink(sfx_u32 *ul_frequency_hz) { +#else +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_compute_uplink(SIGFOX_EP_FREQUENCY_uplink_signal_t *input, sfx_u32 *ul_frequency_hz) { +#endif +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_FREQUENCY_status_t status = SIGFOX_EP_FREQUENCY_SUCCESS; +#endif +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + sfx_u32 baseband_frequency_hz = 0; + sfx_u8 micro_channel_index = 0; +#endif +#ifdef PARAMETERS_CHECK + // Check parameters. +#ifndef SINGLE_FRAME + if (input == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_NULL_PARAMETER); + } +#endif + if (ul_frequency_hz == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_NULL_PARAMETER); + } +#ifndef SINGLE_FRAME + if ((input -> ul_frame_rank) >= SIGFOX_UL_FRAME_RANK_LAST) { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_FRAME_RANK); + } +#endif +#endif /* PARAMETERS_CHECK */ +#ifndef SINGLE_FRAME + // Update local pointers. + sigfox_ep_frequency_ctx.input = input; +#endif + // Update frequency range. + _compute_baseband_frequency_range(); + // Compute frequency. +#ifdef SINGLE_FRAME + // Single frame is always random. + _COMPUTE_NEW_RANDOM_FREQUENCY(); +#else /* SINGLE_FRAME */ + switch (input -> ul_frame_rank) { + case SIGFOX_UL_FRAME_RANK_1: +#ifdef BIDIRECTIONAL + // Reset frame 1 frequency (for error management on frame 2 and 3). + sigfox_ep_frequency_ctx.frame_1_frequency_hz = 0; +#endif + // First frame is always random. + _COMPUTE_NEW_RANDOM_FREQUENCY(); + break; + case SIGFOX_UL_FRAME_RANK_2: +#ifdef BIDIRECTIONAL + // Check bidirectional flag. + if ((input -> bidirectional_flag) == SFX_TRUE) { + if (sigfox_ep_frequency_ctx.frame_1_frequency_hz != 0) { + // Add fixed frequency offset. + (*ul_frequency_hz) = sigfox_ep_frequency_ctx.frame_1_frequency_hz + (((sigfox_ep_frequency_ctx.rc) -> spectrum_access) -> delta_f_mf_hz); + } + else { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_FRAME_1_FREQUENCY); + } + } + else { + // Frequency is random. + _COMPUTE_NEW_RANDOM_FREQUENCY(); + } +#else /* BIDIRECTIONAL */ + // Frequency is random. + _COMPUTE_NEW_RANDOM_FREQUENCY(); +#endif + break; + case SIGFOX_UL_FRAME_RANK_3: +#ifdef BIDIRECTIONAL + // Check bidirectional flag. + if ((input -> bidirectional_flag) == SFX_TRUE) { + // Check if frame 1 frequency was correctly computed. + if (sigfox_ep_frequency_ctx.frame_1_frequency_hz != 0) { + // Add fixed frequency offset. + (*ul_frequency_hz) = sigfox_ep_frequency_ctx.frame_1_frequency_hz - (((sigfox_ep_frequency_ctx.rc) -> spectrum_access) -> delta_f_mf_hz); + } + else { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_FRAME_1_FREQUENCY); + } + } + else { + // Frequency is random. + _COMPUTE_NEW_RANDOM_FREQUENCY(); + } +#else /* BIDIRECTIONAL */ + // Frequency is random. + _COMPUTE_NEW_RANDOM_FREQUENCY(); +#endif + break; + default: + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_FRAME_RANK); + } +#endif /* SINGLE_FRAME */ +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + // Reset selected micro-channel frame count. + baseband_frequency_hz = (*ul_frequency_hz) + (SIGFOX_MACRO_CHANNEL_WIDTH_HZ / 2); + baseband_frequency_hz -= ((sigfox_ep_frequency_ctx.rc) -> f_ul_hz); + micro_channel_index = _get_micro_channel_index(baseband_frequency_hz); + sigfox_ep_frequency_ctx.micro_channel_frame_count[micro_channel_index] = 0; + // Increment all micro channel frame count. + for (micro_channel_index=0 ; micro_channel_index f_dl_hz)) - ((sfx_s32) ((sigfox_ep_frequency_ctx.rc) -> f_ul_hz)); + // Compute downlink frequency. + (*dl_frequency_hz) = (sfx_u32) (((sfx_s32) sigfox_ep_frequency_ctx.frame_1_frequency_hz) + ul_dl_offset_hz); +#ifdef PARAMETERS_CHECK +errors: +#endif + RETURN(); +} +#endif + +/*******************************************************************/ +SIGFOX_EP_FREQUENCY_status_t SIGFOX_EP_FREQUENCY_get_random_value(sfx_u16 *random_value) { +#ifdef ERROR_CODES + SIGFOX_EP_FREQUENCY_status_t status = SIGFOX_EP_FREQUENCY_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + if (random_value == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_FREQUENCY_ERROR_NULL_PARAMETER); + } +#endif /* PARAMETERS_CHECK */ + // Update random value. + (*random_value) = sigfox_ep_frequency_ctx.random_value; +#ifdef PARAMETERS_CHECK +errors: +#endif + RETURN(); +} + diff --git a/src/core/sigfox_tx_control.c b/src/core/sigfox_tx_control.c new file mode 100644 index 0000000..cc9a119 --- /dev/null +++ b/src/core/sigfox_tx_control.c @@ -0,0 +1,532 @@ +/*!***************************************************************** + * \file sigfox_tx_control.c + * \brief Sigfox TX control driver for regulatory operations. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#include "core/sigfox_tx_control.h" + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" +#include "manuf/mcu_api.h" +#ifdef SPECTRUM_ACCESS_LBT +#include "manuf/rf_api.h" +#endif +#include "sigfox_error.h" + +#ifdef REGULATORY + +/*** SIGFOX TX CONTROL local macros ***/ + +#if (defined SPECTRUM_ACCESS_FH) || (defined SPECTRUM_ACCESS_LBT) +#define SIGFOX_TX_CONTROL_TIMER_INSTANCE MCU_API_TIMER_1 +#endif +#ifdef SPECTRUM_ACCESS_FH +#define SIGFOX_TX_CONTROL_FH_TIMER_MS 20000 +#endif +#ifdef UL_BIT_RATE_BPS +#define SIGFOX_TX_CONTROL_FRAME_DURATION_MS ((sigfox_tx_control_ctx.params.bitstream_length_bytes * 8 * 1000) / (UL_BIT_RATE_BPS)) +#else +#define SIGFOX_TX_CONTROL_FRAME_DURATION_MS ((sigfox_tx_control_ctx.params.bitstream_length_bytes * 8 * 1000) / (sigfox_tx_control_ctx.params.ul_bit_rate_bps)) +#endif + +/*** SIGFOX TX CONTROL local structures ***/ + +/*******************************************************************/ +typedef union { + struct { + unsigned pre_check_running : 1; + unsigned post_check_running : 1; + unsigned mcu_timer1_cplt : 1; + unsigned rf_channel_free : 1; + }; + sfx_u8 all; +} SIGFOX_TX_CONTROL_flags_t; + +/*******************************************************************/ +typedef struct { + const SIGFOX_rc_t *rc; + volatile SIGFOX_TX_CONTROL_flags_t flags; + volatile SIGFOX_TX_CONTROL_result_t result; + SIGFOX_TX_CONTROL_parameters_t params; +#ifdef ASYNCHRONOUS + // Process callback for asynchronous mode. + SIGFOX_TX_CONTROL_process_cb_t process_cb; +#endif +} SIGFOX_TX_CONTROL_context_t; + +/*** SIGFOX TX CONTROL local global variables ***/ + +static SIGFOX_TX_CONTROL_context_t sigfox_tx_control_ctx = { + .flags.all = 0, +}; + +/*** SIGFOX TX CONTROL local functions ***/ + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +#define _PROCESS_CALLBACK(void) { \ + if (sigfox_tx_control_ctx.process_cb != SFX_NULL) { \ + sigfox_tx_control_ctx.process_cb(); \ + } \ +} +#endif + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +#define _CHECK_CPLT_CALLBACK(void) { \ + if (sigfox_tx_control_ctx.params.cplt_cb != SFX_NULL) { \ + sigfox_tx_control_ctx.params.cplt_cb(); \ + } \ +} +#endif + +#if (defined ASYNCHRONOUS) && ((defined SPECTRUM_ACCESS_LBT) || (defined SPECTRUM_ACCESS_FH)) +/*******************************************************************/ +static void _MCU_API_timer1_cplt_cb(void) { + // Set local flag. + sigfox_tx_control_ctx.flags.mcu_timer1_cplt = 1; + _PROCESS_CALLBACK(); +} +#endif + +#if (defined ASYNCHRONOUS) && (defined SPECTRUM_ACCESS_LBT) +/*******************************************************************/ +static void _RF_API_channel_free_cb(void) { + // Set local flag. + sigfox_tx_control_ctx.flags.rf_channel_free = 1; + _PROCESS_CALLBACK(); +} +#endif + +/*******************************************************************/ +SIGFOX_TX_CONTROL_status_t _store_parameters(SIGFOX_TX_CONTROL_parameters_t *params) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_TX_CONTROL_status_t status = SIGFOX_TX_CONTROL_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + // Check parameters. + if (params == SFX_NULL) { + EXIT_ERROR(SIGFOX_TX_CONTROL_ERROR_NULL_PARAMETER); + } +#endif /* PARAMETERS_CHECK */ + sigfox_tx_control_ctx.params.type = (params -> type); + sigfox_tx_control_ctx.params.bitstream_length_bytes = (params -> bitstream_length_bytes); + sigfox_tx_control_ctx.params.last_message_frame = (params -> last_message_frame); +#ifndef SINGLE_FRAME + sigfox_tx_control_ctx.params.ul_frame_rank = (params -> ul_frame_rank); + sigfox_tx_control_ctx.params.number_of_frames = (params -> number_of_frames); +#endif +#ifndef UL_BTT_RATE + sigfox_tx_control_ctx.params.ul_bit_rate_bps = (params -> ul_bit_rate_bps); +#endif +#ifdef BIDIRECTIONAL + sigfox_tx_control_ctx.params.ack_message = (params -> ack_message); +#endif +#if !(defined SINGLE_FRAME) || (defined BIDIRECTIONAL) + sigfox_tx_control_ctx.params.interframe_ms = (params -> interframe_ms); +#endif +#ifdef ASYNCHRONOUS + sigfox_tx_control_ctx.params.cplt_cb = (params -> cplt_cb); +#endif +#ifdef PARAMETERS_CHECK +errors: +#endif + RETURN(); +} + +/*** SIGFOX TX CONTROL functions ***/ + +/*******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_open(SIGFOX_TX_CONTROL_config_t *config) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_TX_CONTROL_status_t status = SIGFOX_TX_CONTROL_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + // Check parameters. + if (config == SFX_NULL) { + EXIT_ERROR(SIGFOX_TX_CONTROL_ERROR_NULL_PARAMETER); + } + if ((config -> rc) == SFX_NULL) { + EXIT_ERROR(SIGFOX_TX_CONTROL_ERROR_NULL_PARAMETER); + } +#ifdef ASYNCHRONOUS + if ((config -> process_cb) == SFX_NULL) { + EXIT_ERROR(SIGFOX_TX_CONTROL_ERROR_NULL_PARAMETER); + } +#endif +#endif /* PARAMETERS_CHECK */ + // Reset flags. + sigfox_tx_control_ctx.flags.all = 0; + // Store RC pointer. + sigfox_tx_control_ctx.rc = (config -> rc); +#ifdef ASYNCHRONOUS + sigfox_tx_control_ctx.process_cb = (config -> process_cb); +#endif +#ifdef PARAMETERS_CHECK +errors: +#endif + RETURN(); +} + +/*******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_check(SIGFOX_TX_CONTROL_parameters_t *params) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_TX_CONTROL_status_t status = SIGFOX_TX_CONTROL_SUCCESS; +#if (defined SPECTRUM_ACCESS_FH) || (defined SPECTRUM_ACCESS_LBT) + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif +#ifdef SPECTRUM_ACCESS_LBT + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif +#endif /* ERROR_CODES */ +#if (defined SPECTRUM_ACCESS_FH) || (defined SPECTRUM_ACCESS_LBT) + MCU_API_timer_t mcu_timer; +#endif +#ifdef SPECTRUM_ACCESS_LBT + RF_API_radio_parameters_t radio_params; + RF_API_carrier_sense_parameters_t cs_params; + sfx_u32 cs_max_duration_ms = 0; +#ifndef SINGLE_FRAME + sfx_u8 number_of_repeated_frames = (sigfox_tx_control_ctx.params.number_of_frames - 1); +#endif +#ifndef ASYNCHRONOUS + sfx_bool channel_free = SFX_FALSE; +#endif +#endif + // Store and check parameters. +#ifdef ERROR_CODES + status = _store_parameters(params); + CHECK_STATUS(SIGFOX_TX_CONTROL_SUCCESS); +#else + _store_parameters(params); +#endif + // Reset flags. + sigfox_tx_control_ctx.flags.all = 0; + // Reset result. + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_ALLOWED; + // Perform required check. + switch (((sigfox_tx_control_ctx.rc) -> spectrum_access) -> type) { +#ifdef SPECTRUM_ACCESS_DC + case SIGFOX_SPECTRUM_ACCESS_TYPE_DC: + // TODO: DC check. + break; +#endif +#ifdef SPECTRUM_ACCESS_LDC + case SIGFOX_SPECTRUM_ACCESS_TYPE_LDC: + // TODO: LDC check. + break; +#endif +#ifdef SPECTRUM_ACCESS_FH + case SIGFOX_SPECTRUM_ACCESS_TYPE_FH: +#ifdef CERTIFICATION + // Bypass check if required. + if ((params -> fh_timer_enable) == SFX_FALSE) break; +#endif + // Start FH timer in case of post-check. + if ((sigfox_tx_control_ctx.params.type) == SIGFOX_TX_CONTROL_TYPE_POST_CHECK) { + // Only start timer on the last frame of the message sequence. + if (sigfox_tx_control_ctx.params.last_message_frame == SFX_TRUE) { + mcu_timer.instance = MCU_API_TIMER_1; + mcu_timer.duration_ms = SIGFOX_TX_CONTROL_FH_TIMER_MS; +#ifdef ASYNCHRONOUS + mcu_timer.cplt_cb = (MCU_API_timer_cplt_cb_t) &_MCU_API_timer1_cplt_cb; +#endif +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_start(&mcu_timer); + MCU_API_check_status(SIGFOX_TX_CONTROL_ERROR_MCU); +#else + MCU_API_timer_start(&mcu_timer); +#endif +#ifdef ASYNCHRONOUS + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_PENDING; +#else + // Wait FH timer completion. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_wait_cplt(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_TX_CONTROL_ERROR_MCU); +#else + MCU_API_timer_wait_cplt(MCU_API_TIMER_1); +#endif + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_TX_CONTROL_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_1); +#endif + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_ALLOWED; +#endif + } + } + break; +#endif +#ifdef SPECTRUM_ACCESS_LBT + case SIGFOX_SPECTRUM_ACCESS_TYPE_LBT: +#ifdef CERTIFICATION + // Bypass check if required. + if ((params -> lbt_enable) == SFX_FALSE) break; +#endif + // Execute LBT in case of pre-check. + if ((sigfox_tx_control_ctx.params.type) == SIGFOX_TX_CONTROL_TYPE_PRE_CHECK) { + // Compute carrier sense maximum duration. +#ifdef SINGLE_FRAME + // Use user-defined timeout for first frame. + cs_max_duration_ms = (sigfox_tx_control_ctx.rc -> spectrum_access) -> cs_max_duration_first_frame_ms; +#else + // Uplink user message. + if (sigfox_tx_control_ctx.params.ul_frame_rank == SIGFOX_UL_FRAME_RANK_1) { + // Use user-defined timeout for first frame. + cs_max_duration_ms = (sigfox_tx_control_ctx.rc -> spectrum_access) -> cs_max_duration_first_frame_ms; + } + else { + // Use T_LF. + cs_max_duration_ms = SIGFOX_T_LF_MS; + cs_max_duration_ms -= number_of_repeated_frames * (sigfox_tx_control_ctx.params.interframe_ms); // Remove inter-frame delay(s). + cs_max_duration_ms -= (number_of_repeated_frames - 1) * SIGFOX_TX_CONTROL_FRAME_DURATION_MS; // Remove frame 2 duration. + cs_max_duration_ms /= (number_of_repeated_frames); // Divide remaining time equitably for all repeated frames. + } +#endif +#ifdef BIDIRECTIONAL + // Override in case of DL-ACK frame. + if (sigfox_tx_control_ctx.params.ack_message == SFX_TRUE) { + // DL confirmation message. + cs_max_duration_ms = SIGFOX_T_CONF_MAX_MS - (sigfox_tx_control_ctx.params.interframe_ms); // Tconf. + } +#endif + // Set radio configuration. + radio_params.rf_mode = RF_API_MODE_RX; + radio_params.frequency_hz = ((sigfox_tx_control_ctx.rc) -> f_ul_hz); + radio_params.modulation = RF_API_MODULATION_NONE; + radio_params.bit_rate_bps = 0; +#ifdef BIDIRECTIONAL + radio_params.deviation_hz = 0; +#endif + // Set carrier sense parameters. + cs_params.bandwidth_hz = ((sigfox_tx_control_ctx.rc) -> spectrum_access) -> cs_bandwidth_hz; + cs_params.threshold_dbm = ((sigfox_tx_control_ctx.rc) -> spectrum_access) -> cs_threshold_dbm; + cs_params.min_duration_ms = ((sigfox_tx_control_ctx.rc) -> spectrum_access) -> cs_min_duration_ms; +#ifdef ASYNCHRONOUS + cs_params.channel_free_cb = (RF_API_channel_free_cb_t) &_RF_API_channel_free_cb; +#else + cs_params.channel_free = &channel_free; +#endif + // Init radio for LBT check. +#ifdef ERROR_CODES + rf_status = RF_API_init(&radio_params); + RF_API_check_status(SIGFOX_TX_CONTROL_ERROR_RF); +#else + RF_API_init(&radio_params); +#endif + // Start timer to manage carrier sense timeout. + mcu_timer.instance = MCU_API_TIMER_1; + mcu_timer.duration_ms = cs_max_duration_ms; +#ifdef ASYNCHRONOUS + mcu_timer.cplt_cb = (MCU_API_timer_cplt_cb_t) &_MCU_API_timer1_cplt_cb; +#endif +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_start(&mcu_timer); + MCU_API_check_status(SIGFOX_TX_CONTROL_ERROR_MCU); +#else + MCU_API_timer_start(&mcu_timer); +#endif + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_PENDING; + // Start carrier sense. +#ifdef ERROR_CODES + status = RF_API_carrier_sense(&cs_params); + RF_API_check_status(SIGFOX_TX_CONTROL_ERROR_RF); +#else + RF_API_carrier_sense(&cs_params); +#endif +#ifdef ASYNCHRONOUS + // Result will be known later after carrier sense. + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_PENDING; +#else /* ASYNCHRONOUS */ + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_TX_CONTROL_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_1); +#endif + // Stop radio. +#ifdef ERROR_CODES + rf_status = RF_API_de_init(); + RF_API_check_status(SIGFOX_TX_CONTROL_ERROR_RF); +#else + RF_API_de_init(); +#endif + // Update result. + sigfox_tx_control_ctx.result = (channel_free == SFX_FALSE) ? SIGFOX_TX_CONTROL_RESULT_FORBIDDEN : SIGFOX_TX_CONTROL_RESULT_ALLOWED; +#endif /* ASYNCHRONOUS */ + } + break; +#endif + default: + EXIT_ERROR(SIGFOX_TX_CONTROL_ERROR_SPECTRUM_ACCESS); + break; + } +errors: + RETURN(); +} + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_process(void) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_TX_CONTROL_status_t status = SIGFOX_TX_CONTROL_SUCCESS; +#if (defined SPECTRUM_ACCESS_FH) || (defined SPECTRUM_ACCESS_LBT) + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif +#ifdef SPECTRUM_ACCESS_LBT + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif +#endif + // Check flags and update result. + switch (((sigfox_tx_control_ctx.rc) -> spectrum_access) -> type) { +#ifdef SPECTRUM_ACCESS_DC + case SIGFOX_SPECTRUM_ACCESS_TYPE_DC: + // Nothing to do. + break; +#endif +#ifdef SPECTRUM_ACCESS_LDC + case SIGFOX_SPECTRUM_ACCESS_TYPE_LDC: + // Nothing to do. + break; +#endif +#ifdef SPECTRUM_ACCESS_FH + case SIGFOX_SPECTRUM_ACCESS_TYPE_FH: + if ((sigfox_tx_control_ctx.params.type) == SIGFOX_TX_CONTROL_TYPE_POST_CHECK) { + // Check FH timer completion. + if (sigfox_tx_control_ctx.flags.mcu_timer1_cplt != 0) { + // Clear flag. + sigfox_tx_control_ctx.flags.mcu_timer1_cplt = 0; + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_TX_CONTROL_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_1); +#endif + // Update result. + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_ALLOWED; + // Call completion callback. + _CHECK_CPLT_CALLBACK(); + } + } + break; +#endif +#ifdef SPECTRUM_ACCESS_LBT + case SIGFOX_SPECTRUM_ACCESS_TYPE_LBT: + if ((sigfox_tx_control_ctx.params.type) == SIGFOX_TX_CONTROL_TYPE_PRE_CHECK) { + // Check flags. + if ((sigfox_tx_control_ctx.flags.rf_channel_free != 0) || (sigfox_tx_control_ctx.flags.mcu_timer1_cplt != 0)) { + // Update result according to flags. + if (sigfox_tx_control_ctx.flags.mcu_timer1_cplt != 0) { + // Clear flag and update result. + sigfox_tx_control_ctx.flags.mcu_timer1_cplt = 0; + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_FORBIDDEN; + } + if (sigfox_tx_control_ctx.flags.rf_channel_free != 0) { + // Clear flag and update result. + sigfox_tx_control_ctx.flags.rf_channel_free = 0; + sigfox_tx_control_ctx.result = SIGFOX_TX_CONTROL_RESULT_ALLOWED; + } + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_TX_CONTROL_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_1); +#endif + // Stop radio. +#ifdef ERROR_CODES + rf_status = RF_API_de_init(); + RF_API_check_status(SIGFOX_TX_CONTROL_ERROR_RF); +#else + RF_API_de_init(); +#endif + // Call completion callback. + _CHECK_CPLT_CALLBACK(); + } + } + break; +#endif + default: + EXIT_ERROR(SIGFOX_TX_CONTROL_ERROR_SPECTRUM_ACCESS); + break; + } +errors: + RETURN(); +} +#endif + +/*******************************************************************/ +sfx_bool SIGFOX_TX_CONTROL_is_radio_required(SIGFOX_TX_CONTROL_check_type check_type) { + // Local variables. + sfx_bool is_radio_required = SFX_FALSE; +#ifdef SPECTRUM_ACCESS_LBT + // Radio is only required for LBT pre-check. + if (((((sigfox_tx_control_ctx.rc) -> spectrum_access) -> type) == SIGFOX_SPECTRUM_ACCESS_TYPE_LBT) && (check_type == SIGFOX_TX_CONTROL_TYPE_PRE_CHECK)) { + is_radio_required = SFX_TRUE; + } +#endif + return is_radio_required; +} + +/*******************************************************************/ +SIGFOX_TX_CONTROL_status_t SIGFOX_TX_CONTROL_get_result(SIGFOX_TX_CONTROL_result_t *result) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_TX_CONTROL_status_t status = SIGFOX_TX_CONTROL_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + if (result == SFX_NULL) { + EXIT_ERROR(SIGFOX_TX_CONTROL_ERROR_NULL_PARAMETER); + } +#endif /* PARAMETERS_CHECK */ + // Update result. + (*result) = sigfox_tx_control_ctx.result; +#ifdef PARAMETERS_CHECK +errors: +#endif + RETURN(); +} + +#endif /* REGULATORY */ diff --git a/src/manuf/mcu_api.c b/src/manuf/mcu_api.c new file mode 100644 index 0000000..57ad496 --- /dev/null +++ b/src/manuf/mcu_api.c @@ -0,0 +1,195 @@ +/*!***************************************************************** + * \file mcu_api.c + * \brief MCU drivers. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#include "manuf/mcu_api.h" + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +/*** MCU API functions ***/ + +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) +/*******************************************************************/ +MCU_API_status_t MCU_API_open(MCU_API_config_t *mcu_api_config) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#ifdef LOW_LEVEL_OPEN_CLOSE +/*******************************************************************/ +MCU_API_status_t MCU_API_close(void) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +MCU_API_status_t MCU_API_process(void) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +/*******************************************************************/ +MCU_API_status_t MCU_API_timer_start(MCU_API_timer_t *timer) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +/*******************************************************************/ +MCU_API_status_t MCU_API_timer_stop(MCU_API_timer_instance_t timer_instance) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#if (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL) || (defined REGULATORY) || (defined CERTIFICATION) +#ifndef ASYNCHRONOUS +/*******************************************************************/ +MCU_API_status_t MCU_API_timer_wait_cplt(MCU_API_timer_instance_t timer_instance) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif +#endif + +/*******************************************************************/ +MCU_API_status_t MCU_API_aes_128_cbc_encrypt(MCU_API_encryption_data_t *aes_data) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} + +#ifdef CRC_HW +/*******************************************************************/ +MCU_API_status_t MCU_API_compute_crc16(sfx_u8 *data, sfx_u8 data_size, sfx_u16 polynom, sfx_u16 *crc) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#if (defined CRC_HW) && (defined BIDIRECTIONAL) +/*******************************************************************/ +MCU_API_status_t MCU_API_compute_crc8(sfx_u8 *data, sfx_u8 data_size, sfx_u16 polynom, sfx_u8 *crc) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +/*******************************************************************/ +MCU_API_status_t MCU_API_get_ep_id(sfx_u8 *ep_id, sfx_u8 ep_id_size_bytes) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} + +/*******************************************************************/ +MCU_API_status_t MCU_API_get_nvm(sfx_u8 *nvm_data, sfx_u8 nvm_data_size_bytes) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} + +/*******************************************************************/ +MCU_API_status_t MCU_API_set_nvm(sfx_u8 *nvm_data, sfx_u8 nvm_data_size_bytes) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) +/*******************************************************************/ +MCU_API_status_t MCU_API_get_voltage_temperature(sfx_u16 *voltage_idle_mv, sfx_u16 *voltage_tx_mv, sfx_s16 *temperature_tenth_degrees) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#ifdef VERBOSE +/*******************************************************************/ +MCU_API_status_t MCU_API_get_initial_pac(sfx_u8 *initial_pac, sfx_u8 initial_pac_size_bytes) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#ifdef VERBOSE +/*******************************************************************/ +MCU_API_status_t MCU_API_get_version(sfx_u8 **version, sfx_u8 *version_size_char) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return MCU_API_SUCCESS; +#endif +} +#endif + +#ifdef ERROR_CODES +/*******************************************************************/ +void MCU_API_error(void) { + /* To be implemented by the device manufacturer */ +} +#endif diff --git a/src/manuf/rf_api.c b/src/manuf/rf_api.c new file mode 100644 index 0000000..c0d29d8 --- /dev/null +++ b/src/manuf/rf_api.c @@ -0,0 +1,161 @@ +/*!***************************************************************** + * \file rf_api.c + * \brief Radio drivers. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#include "manuf/rf_api.h" + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +/*** RF API functions ***/ + +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) +/*******************************************************************/ +RF_API_status_t RF_API_open(RF_API_config_t *rf_api_config) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} +#endif + +#ifdef LOW_LEVEL_OPEN_CLOSE +/*******************************************************************/ +RF_API_status_t RF_API_close(void) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} +#endif + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +RF_API_status_t RF_API_process(void) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} +#endif + +/*******************************************************************/ +RF_API_status_t RF_API_wake_up(void) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} + +/*******************************************************************/ +RF_API_status_t RF_API_sleep(void) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} + +/*******************************************************************/ +RF_API_status_t RF_API_init(RF_API_radio_parameters_t *radio_parameters) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} + +/*******************************************************************/ +RF_API_status_t RF_API_de_init(void) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} + +/*******************************************************************/ +RF_API_status_t RF_API_send(RF_API_tx_data_t *tx_data) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +RF_API_status_t RF_API_receive(RF_API_rx_data_t *rx_data) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} +#endif + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +RF_API_status_t RF_API_get_dl_phy_content_and_rssi(sfx_u8 *dl_phy_content, sfx_u8 dl_phy_content_size, sfx_s16 *dl_rssi_dbm) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} +#endif + +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) +/*******************************************************************/ +RF_API_status_t RF_API_carrier_sense(RF_API_carrier_sense_parameters_t *carrier_sense_params) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} +#endif + +#ifdef VERBOSE +/*******************************************************************/ +RF_API_status_t RF_API_get_version(sfx_u8 **version, sfx_u8 *version_size_char) { + /* To be implemented by the device manufacturer */ +#ifdef ERROR_CODES + return RF_API_SUCCESS; +#endif +} +#endif + +#ifdef ERROR_CODES +/*******************************************************************/ +void RF_API_error(void) { + /* To be implemented by the device manufacturer */ +} +#endif diff --git a/src/sigfox_ep_api.c b/src/sigfox_ep_api.c new file mode 100644 index 0000000..4623f9a --- /dev/null +++ b/src/sigfox_ep_api.c @@ -0,0 +1,3020 @@ +/*!***************************************************************** + * \file sigfox_ep_api.c + * \brief Sigfox End-Point library API. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#include "sigfox_ep_api.h" + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "manuf/mcu_api.h" +#include "manuf/rf_api.h" +#include "core/sigfox_ep_bitstream.h" +#include "core/sigfox_ep_frequency.h" +#include "sigfox_rc.h" +#ifdef CERTIFICATION +#include "sigfox_ep_api_test.h" +#endif +#ifdef REGULATORY +#include "core/sigfox_tx_control.h" +#endif +#ifdef VERBOSE +#include "sigfox_ep_version.h" +#endif +#include "sigfox_types.h" +#include "sigfox_error.h" + +/*** SIGFOX EP API local macros ***/ + +#ifdef APPLICATION_MESSAGES +#ifdef UL_PAYLOAD_SIZE +#define SIGFOX_EP_API_UL_PAYLOAD_SIZE UL_PAYLOAD_SIZE +#else +#define SIGFOX_EP_API_UL_PAYLOAD_SIZE SIGFOX_UL_PAYLOAD_MAX_SIZE_BYTES +#endif +#endif + +/*** SIGFOX EP API local structures ***/ + +#ifndef ASYNCHRONOUS +/*******************************************************************/ +typedef enum { + SIGFOX_EP_API_STATE_CLOSED, + SIGFOX_EP_API_STATE_READY, +#ifdef REGULATORY + SIGFOX_EP_API_STATE_REGULATORY, +#endif + SIGFOX_EP_API_STATE_UL_MODULATION_PENDING, +#if !(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0) || (defined BIDIRECTIONAL)) + SIGFOX_EP_API_STATE_UL_INTER_FRAME_TIMER, +#endif +#ifdef BIDIRECTIONAL + SIGFOX_EP_API_STATE_DL_TIMER, + SIGFOX_EP_API_STATE_DL_LISTENING, + SIGFOX_EP_API_STATE_DL_CONFIRMATION_TIMER, + SIGFOX_EP_API_STATE_DL_CONFIRMATION, +#endif + SIGFOX_EP_API_STATE_LAST +} SIGFOX_EP_API_state_t; +#endif + +/*******************************************************************/ +typedef union { + struct { + unsigned error_stack_initialized : 1; + unsigned synchronous : 1; + unsigned send_message_request : 1; + unsigned radio_woken_up : 1; + unsigned ack_message : 1; + unsigned control_message : 1; + unsigned tx_control_pre_check_running : 1; + unsigned tx_control_post_check_running : 1; + unsigned tx_forbidden : 1; + unsigned frame_success : 1; + unsigned nvm_write_pending : 1; + unsigned process_running : 1; + }; + sfx_u16 all; +} SIGFOX_EP_API_internal_flags_t; + +/*******************************************************************/ +typedef union { + struct { + unsigned mcu_process : 1; + unsigned mcu_timer1_cplt : 1; + unsigned mcu_timer2_cplt : 1; + unsigned rf_process : 1; + unsigned rf_tx_cplt : 1; + unsigned rf_rx_data_received : 1; + unsigned tx_control_process : 1; + unsigned tx_control_pre_check_cplt : 1; + unsigned tx_control_post_check_cplt : 1; + unsigned low_level_error : 1; + }; + sfx_u16 all; +} SIGFOX_EP_API_irq_flags_t; + +/*******************************************************************/ +typedef struct { + const SIGFOX_rc_t *rc_ptr; + sfx_u8 ep_id[SIGFOX_EP_ID_SIZE_BYTES]; + SIGFOX_EP_API_state_t state; + SIGFOX_EP_API_message_status_t message_status; + SIGFOX_EP_API_internal_flags_t internal_flags; + volatile SIGFOX_EP_API_irq_flags_t irq_flags; + sfx_u16 message_counter; + sfx_u16 random_value; + sfx_u32 ul_frequency_hz; +#ifndef MESSAGE_COUNTER_ROLLOVER + sfx_u16 message_counter_rollover; +#endif +#ifdef ASYNCHRONOUS + SIGFOX_EP_API_process_cb_t process_cb; + SIGFOX_EP_API_uplink_cplt_cb_t uplink_cplt_cb; +#ifdef BIDIRECTIONAL + SIGFOX_EP_API_downlink_cplt_cb downlink_cplt_cb; +#endif + SIGFOX_EP_API_message_cplt_cb message_cplt_cb; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status_from_callback; +#endif +#endif + // Message data. + SIGFOX_EP_API_status_t (*sending_function_ptr)(void); +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + SIGFOX_EP_API_common_t *common_parameters_ptr; +#endif +#ifdef APPLICATION_MESSAGES + SIGFOX_EP_API_application_message_t *application_message_ptr; +#ifdef ASYNCHRONOUS + SIGFOX_EP_API_application_message_t local_application_message; +#if !(defined UL_PAYLOAD_SIZE) || (UL_PAYLOAD_SIZE > 0) + sfx_u8 local_ul_payload[SIGFOX_EP_API_UL_PAYLOAD_SIZE]; +#endif +#endif /* ASYNCHRONOUS */ +#endif /* APPLICATION_MESSAGES */ +#ifdef BIDIRECTIONAL + // Downlink variables. + sfx_bool dl_status; + sfx_u8 dl_payload[SIGFOX_DL_PAYLOAD_SIZE_BYTES]; + sfx_s16 dl_rssi_dbm; +#endif /* BIDIRECTIONAL */ +#ifdef CONTROL_KEEP_ALIVE_MESSAGE + SIGFOX_EP_API_control_message_t *control_message_ptr; +#ifdef ASYNCHRONOUS + SIGFOX_EP_API_control_message_t local_control_message; +#endif +#endif +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) + sfx_s16 temperature_tenth_degrees; + sfx_u16 voltage_tx_mv; + sfx_u16 voltage_idle_mv; +#endif +#ifndef SINGLE_FRAME + SIGFOX_ul_frame_rank_t ul_frame_rank; + sfx_u8 frame_success_count; +#endif +#if !(defined SINGLE_FRAME) || (defined BIDIRECTIONAL) + sfx_u32 interframe_ms; // Tifu, Tifb or Tconf. +#endif +#ifdef CERTIFICATION + SIGFOX_EP_API_TEST_parameters_t test_parameters; +#endif +} SIGFOX_EP_API_context_t; + +/*** SIGFOX EP API local global variables ***/ + +static SIGFOX_EP_API_context_t sigfox_ep_api_ctx = { + .rc_ptr = SFX_NULL, + .state = SIGFOX_EP_API_STATE_CLOSED, + .internal_flags.all = 0, + .irq_flags.all = 0, + .message_status.all = 0, +#ifndef MESSAGE_COUNTER_ROLLOVER + .message_counter_rollover = 0, +#endif +#ifdef ASYNCHRONOUS +#ifdef ERROR_CODES + .status_from_callback = SIGFOX_EP_API_SUCCESS, +#endif /* ERROR_CODES */ +#endif /* ASYNCHRONOUS */ + .sending_function_ptr = SFX_NULL, +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) + .common_parameters_ptr = SFX_NULL, +#endif +#ifdef APPLICATION_MESSAGES + .application_message_ptr = SFX_NULL, +#ifdef BIDIRECTIONAL + .dl_status = SFX_FALSE, + .dl_rssi_dbm = 0, +#endif /* BIDIRECTIONAL */ +#endif /* APPLICATION_MESSAGES */ +#ifdef CONTROL_KEEP_ALIVE_MESSAGE + .control_message_ptr = SFX_NULL, +#endif +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) + .temperature_tenth_degrees = 0, + .voltage_tx_mv = 0, + .voltage_idle_mv = 0, +#endif +#ifndef SINGLE_FRAME + .ul_frame_rank = SIGFOX_UL_FRAME_RANK_1, + .frame_success_count = 0, +#endif +}; +#ifdef VERBOSE +static const sfx_u8 SIGFOX_EP_API_VERSION[] = SIGFOX_EP_VERSION; +static const sfx_u8 SIGFOX_EP_API_FLAGS[] = SIGFOX_EP_FLAGS; +#endif + +/*** SIGFOX EP API local functions ***/ + +/*******************************************************************/ +#ifdef ERROR_CODES +#define _CHECK_LIBRARY_STATE(state_condition) { if (sigfox_ep_api_ctx.state state_condition) { status = SIGFOX_EP_API_ERROR_STATE; goto error_state; } } +#else +#define _CHECK_LIBRARY_STATE(state_condition) { if (sigfox_ep_api_ctx.state state_condition) { goto error_state; } } +#endif + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +#define _PROCESS_CALLBACK(void) { \ + if ((sigfox_ep_api_ctx.process_cb != SFX_NULL) && (sigfox_ep_api_ctx.internal_flags.process_running == 0)) { \ + sigfox_ep_api_ctx.process_cb(); \ + } \ +} +#endif + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +#define _UPLINK_CPLT_CALLBACK(void) { \ + if ((sigfox_ep_api_ctx.uplink_cplt_cb != SFX_NULL) && (sigfox_ep_api_ctx.state != SIGFOX_EP_API_STATE_READY)) { \ + sigfox_ep_api_ctx.uplink_cplt_cb(); \ + } \ +} +#endif + +#if (defined ASYNCHRONOUS) && (defined BIDIRECTIONAL) +/*******************************************************************/ +#define _DOWNLINK_CPLT_CALLBACK(void) { \ + if ((sigfox_ep_api_ctx.downlink_cplt_cb != SFX_NULL) && (sigfox_ep_api_ctx.state != SIGFOX_EP_API_STATE_READY)) { \ + sigfox_ep_api_ctx.downlink_cplt_cb(); \ + } \ +} +#endif + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +#define _MESSAGE_CPLT_CALLBACK(void) { \ + if ((sigfox_ep_api_ctx.message_cplt_cb != SFX_NULL) && (sigfox_ep_api_ctx.state != SIGFOX_EP_API_STATE_READY)) { \ + sigfox_ep_api_ctx.message_cplt_cb(); \ + } \ +} +#endif + +#ifdef ASYNCHRONOUS +/*******************************************************************/ +static void _MCU_API_process_cb(void) { + // Set local flag. + sigfox_ep_api_ctx.irq_flags.mcu_process = 1; + _PROCESS_CALLBACK(); +} + +#ifdef ASYNCHRONOUS +#ifdef ERROR_CODES +/*******************************************************************/ +static void _MCU_API_error_cb(MCU_API_status_t mcu_status) { + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + // Set local error code. + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); + // Do not call the process if the given status is not an error. + return; +errors: + sigfox_ep_api_ctx.status_from_callback = status; + sigfox_ep_api_ctx.irq_flags.low_level_error = 1; + _PROCESS_CALLBACK(); +} +#else +/*******************************************************************/ +static void _MCU_API_error_cb(void) { + sigfox_ep_api_ctx.irq_flags.low_level_error = 1; + _PROCESS_CALLBACK(); +} +#endif +#endif + +#if (defined ASYNCHRONOUS) && ((!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) || (defined BIDIRECTIONAL)) +/*******************************************************************/ +static void _MCU_API_timer1_cplt_cb(void) { + // Set local flag. + sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt = 1; + _PROCESS_CALLBACK(); +} +#endif + +#if (defined ASYNCHRONOUS) && (defined BIDIRECTIONAL) +/*******************************************************************/ +static void _MCU_API_timer2_cplt_cb(void) { + // Set local flag. + sigfox_ep_api_ctx.irq_flags.mcu_timer2_cplt = 1; + _PROCESS_CALLBACK(); +} +#endif + +/*******************************************************************/ +static void _RF_API_process_cb(void) { + // Set local flag. + sigfox_ep_api_ctx.irq_flags.rf_process = 1; + _PROCESS_CALLBACK(); +} + +#ifdef ASYNCHRONOUS +#ifdef ERROR_CODES +/*******************************************************************/ +static void _RF_API_error_cb(RF_API_status_t rf_status) { + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + // Set local error code. + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); + // Do not call the process if the given status is not an error. + return; +errors: + sigfox_ep_api_ctx.status_from_callback = status; + sigfox_ep_api_ctx.irq_flags.low_level_error = 1; + _PROCESS_CALLBACK(); +} +#else +/*******************************************************************/ +static void _RF_API_error_cb(void) { + sigfox_ep_api_ctx.irq_flags.low_level_error = 1; + _PROCESS_CALLBACK(); +} +#endif +#endif + +/*******************************************************************/ +static void _RF_API_tx_cplt_cb(void) { + // Set local flag. + sigfox_ep_api_ctx.irq_flags.rf_tx_cplt = 1; + _PROCESS_CALLBACK(); +} +#endif + +#if (defined ASYNCHRONOUS) && (defined BIDIRECTIONAL) +/*******************************************************************/ +static void _RF_API_rx_data_received_cb(void) { + // Set local flag. + sigfox_ep_api_ctx.irq_flags.rf_rx_data_received = 1; + _PROCESS_CALLBACK(); +} +#endif + +#if (defined ASYNCHRONOUS) && (defined REGULATORY) +/*******************************************************************/ +static void _SIGFOX_TX_CONTROL_process_cb(void) { + // Set local flag and result. + sigfox_ep_api_ctx.irq_flags.tx_control_process = 1; + _PROCESS_CALLBACK(); +} +#endif + +#if (defined ASYNCHRONOUS) && (defined REGULATORY) +/*******************************************************************/ +static void _SIGFOX_TX_CONTROL_pre_check_cplt_cb(void) { + // Set local flag and result. + sigfox_ep_api_ctx.irq_flags.tx_control_pre_check_cplt = 1; + _PROCESS_CALLBACK(); +} +#endif + +#if (defined ASYNCHRONOUS) && (defined REGULATORY) +/*******************************************************************/ +static void _SIGFOX_TX_CONTROL_post_check_cplt_cb(void) { + // Set local flag and result. + sigfox_ep_api_ctx.irq_flags.tx_control_post_check_cplt = 1; + _PROCESS_CALLBACK(); +} +#endif + +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) && (!(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE)) +/*******************************************************************/ +static SIGFOX_EP_API_status_t _check_common_parameters(SIGFOX_EP_API_common_t *common_params) { + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + // Check parameter. + if (common_params == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } + // Check bit rate. +#ifdef UL_BIT_RATE_BPS + sfx_u8 idx = 0; + sfx_bool bit_rate_valid = SFX_FALSE; + // Check if the given value is allowed. + for (idx=0 ; idx uplink_bit_rate_capability) >> idx) & 0x01) == 0) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_BIT_RATE); + } +#else /* UL_BIT_RATE */ + // Check if the given bit rate exists. + if ((common_params -> ul_bit_rate) >= SIGFOX_UL_BIT_RATE_LAST) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_BIT_RATE); + } + // Check RC capability. + if ((((sigfox_ep_api_ctx.rc_ptr -> uplink_bit_rate_capability) >> (common_params -> ul_bit_rate)) & 0x01) == 0) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_BIT_RATE); + } +#endif /* UL_BIT_RATE */ + // Check TX power regarding RC rule. +#ifdef TX_POWER_DBM_EIRP + if (TX_POWER_DBM_EIRP > ((sigfox_ep_api_ctx.rc_ptr) -> tx_power_dbm_eirp_max)) { +#else + if (((common_params -> tx_power_dbm_eirp)) > ((sigfox_ep_api_ctx.rc_ptr) -> tx_power_dbm_eirp_max)) { +#endif + EXIT_ERROR(SIGFOX_EP_API_ERROR_TX_POWER); + } +#ifndef SINGLE_FRAME + // Check number of frames. + if ((common_params -> number_of_frames) > SIGFOX_UL_FRAME_RANK_LAST) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NUMBER_OF_FRAMES); + } +#ifndef T_IFU_MS + // Check interframe delay. + if ((common_params -> t_ifu_ms) > SIGFOX_T_IFU_MAX_MS) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_T_IFU); + } +#endif +#endif /* MULTIPLE_FRAMES */ +#ifdef PUBLIC_KEY_CAPABLE + // Check key type. + if ((common_params -> ep_key_type) >= SIGFOX_EP_KEY_LAST) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_EP_KEY); + } +#endif +errors: + return status; +} +#endif + +#if (defined APPLICATION_MESSAGES) && (defined PARAMETERS_CHECK) && (defined ERROR_CODES) +/*******************************************************************/ +static SIGFOX_EP_API_status_t _check_application_message(SIGFOX_EP_API_application_message_t *app_msg) { + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + // Check parameter. + if (app_msg == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } +#if !(defined UL_PAYLOAD_SIZE) || (UL_PAYLOAD_SIZE > 0) + // Check payload. + if ((app_msg -> type) == SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY) { + // Payload is required. + if (((app_msg -> ul_payload) == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } + } +#endif + // Check message type. + if ((app_msg -> type) >= SIGFOX_APPLICATION_MESSAGE_TYPE_LAST) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_MESSAGE_TYPE); + } +#ifndef UL_PAYLOAD_SIZE + if ((app_msg -> type) == SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY) { + // Length is required. + if (((app_msg -> ul_payload_size_bytes) == 0) || ((app_msg -> ul_payload_size_bytes) > SIGFOX_UL_PAYLOAD_MAX_SIZE_BYTES)) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_UL_PAYLOAD_SIZE); + } + } +#endif +#if (defined BIDIRECTIONAL) && !(defined T_CONF_MS) + // Check downlink parameters. + if ((app_msg -> bidirectional_flag) != 0) { + // Check DL confirmation delay. + if (((app_msg -> t_conf_ms) < SIGFOX_T_CONF_MIN_MS) || ((app_msg -> t_conf_ms) > SIGFOX_T_CONF_MAX_MS)) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_T_CONF); + } + } +#endif +errors: + return status; +} +#endif + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) && (defined PARAMETERS_CHECK) && (defined ERROR_CODES) +/*******************************************************************/ +static SIGFOX_EP_API_status_t _check_control_message(SIGFOX_EP_API_control_message_t *ctrl_msg) { + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + // Check parameter. + if (ctrl_msg == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } + // Check message type. + if ((ctrl_msg -> type) >= SIGFOX_CONTROL_MESSAGE_TYPE_LAST) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_MESSAGE_TYPE); + } +errors: + return status; +} +#endif + +#ifdef APPLICATION_MESSAGES +/*******************************************************************/ +static SIGFOX_EP_API_status_t _store_application_message(SIGFOX_EP_API_application_message_t *application_message) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif +#if (defined ASYNCHRONOUS) && (((defined UL_PAYLOAD_SIZE) && (UL_PAYLOAD_SIZE > 0)) || !(defined UL_PAYLOAD_SIZE)) + sfx_u8 idx = 0; +#endif +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) + // Check parameters. + status = _check_application_message(application_message); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + status = _check_common_parameters(&(application_message -> common_parameters)); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#endif +#endif /* PARAMETERS_CHECK and ERROR_CODES*/ +#ifdef ASYNCHRONOUS + // In asynchronous mode, all the data has to be stored locally since the client pointer could be removed. + // Common parameters. +#ifndef UL_BIT_RATE_BPS + sigfox_ep_api_ctx.local_application_message.common_parameters.ul_bit_rate = ((application_message -> common_parameters).ul_bit_rate); +#endif +#ifndef TX_POWER_DBM_EIRP + sigfox_ep_api_ctx.local_application_message.common_parameters.tx_power_dbm_eirp = ((application_message -> common_parameters).tx_power_dbm_eirp); +#endif +#ifndef SINGLE_FRAME + // Number of frames and inter frame delay. + sigfox_ep_api_ctx.local_application_message.common_parameters.number_of_frames = ((application_message -> common_parameters).number_of_frames); +#ifndef T_IFU_MS + sigfox_ep_api_ctx.local_application_message.common_parameters.t_ifu_ms = ((application_message -> common_parameters).t_ifu_ms); + sigfox_ep_api_ctx.interframe_ms = sigfox_ep_api_ctx.local_application_message.common_parameters.t_ifu_ms; +#else + sigfox_ep_api_ctx.interframe_ms = T_IFU_MS; +#endif +#ifdef BIDIRECTIONAL + // Force Tifb in case of bidirectional. + if ((application_message -> bidirectional_flag) != 0) { + sigfox_ep_api_ctx.interframe_ms = SIGFOX_T_IFB_MS; + } +#endif +#endif /* SINGLE_FRAME */ +#ifdef PUBLIC_KEY_CAPABLE + sigfox_ep_api_ctx.local_application_message.common_parameters.ep_key_type = (application_message -> common_parameters).ep_key_type; +#endif + // Message type. + sigfox_ep_api_ctx.local_application_message.type = (application_message -> type); + // Store callbacks (even if NULL). + sigfox_ep_api_ctx.uplink_cplt_cb = (application_message -> uplink_cplt_cb); + sigfox_ep_api_ctx.message_cplt_cb = (application_message -> message_cplt_cb); +#ifdef BIDIRECTIONAL + sigfox_ep_api_ctx.downlink_cplt_cb = (application_message -> downlink_cplt_cb); +#endif + // UL payload. +#ifdef UL_PAYLOAD_SIZE +#if (UL_PAYLOAD_SIZE > 0) + for (idx=0 ; idx ul_payload)[idx]; + } +#endif +#else /* UL_PAYLOAD_SIZE */ + if ((application_message -> type) == SIGFOX_APPLICATION_MESSAGE_TYPE_BYTE_ARRAY) { + for (idx=0 ; idx<(application_message -> ul_payload_size_bytes) ; idx++) { + sigfox_ep_api_ctx.local_ul_payload[idx] = (application_message -> ul_payload)[idx]; + } + sigfox_ep_api_ctx.local_application_message.ul_payload_size_bytes = (application_message -> ul_payload_size_bytes); + } + else { + sigfox_ep_api_ctx.local_application_message.ul_payload_size_bytes = 0; + } +#endif +#ifdef BIDIRECTIONAL + // Downlink parameters. + sigfox_ep_api_ctx.local_application_message.bidirectional_flag = (application_message -> bidirectional_flag); +#ifndef T_CONF_MS + sigfox_ep_api_ctx.local_application_message.t_conf_ms = (application_message -> t_conf_ms); +#endif +#endif + // Update pointers to local data. +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + sigfox_ep_api_ctx.common_parameters_ptr = &(sigfox_ep_api_ctx.local_application_message.common_parameters); +#endif + sigfox_ep_api_ctx.application_message_ptr = &(sigfox_ep_api_ctx.local_application_message); +#if !(defined UL_PAYLOAD_SIZE) || (UL_PAYLOAD_SIZE > 0) + (sigfox_ep_api_ctx.application_message_ptr) -> ul_payload = (sfx_u8*) sigfox_ep_api_ctx.local_ul_payload; +#endif +#else /* ASYNCHRONOUS */ + // In blocking mode, the message pointer will directly address the client data since it will be kept during processing. + sigfox_ep_api_ctx.application_message_ptr = application_message; +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + sigfox_ep_api_ctx.common_parameters_ptr = &(application_message -> common_parameters); +#endif +#endif /* ASYNCHRONOUS */ +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) +errors: +#endif + RETURN(); +} +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +/*******************************************************************/ +static SIGFOX_EP_API_status_t _store_control_message(SIGFOX_EP_API_control_message_t *control_message) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) + // Check parameters. + status = _check_control_message(control_message); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + status = _check_common_parameters(&(control_message -> common_parameters)); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#endif +#endif /* PARAMETERS_CHECK and ERROR_CODES */ +#ifdef ASYNCHRONOUS + // In asynchronous mode, all the data has to be stored locally since the client pointer could be removed, and the message pointer will address the local data. + // Common parameters. +#ifndef UL_BIT_RATE_BPS + sigfox_ep_api_ctx.local_control_message.common_parameters.ul_bit_rate = ((control_message -> common_parameters).ul_bit_rate); +#endif +#ifndef TX_POWER_DBM_EIRP + sigfox_ep_api_ctx.local_control_message.common_parameters.tx_power_dbm_eirp = ((control_message -> common_parameters).tx_power_dbm_eirp); +#endif +#ifndef SINGLE_FRAME + // Number of frames and inter frame delay. + sigfox_ep_api_ctx.local_control_message.common_parameters.number_of_frames = ((control_message -> common_parameters).number_of_frames); +#ifndef T_IFU_MS + sigfox_ep_api_ctx.local_control_message.common_parameters.t_ifu_ms = ((control_message -> common_parameters).t_ifu_ms); + sigfox_ep_api_ctx.interframe_ms = sigfox_ep_api_ctx.local_control_message.common_parameters.t_ifu_ms; +#else + sigfox_ep_api_ctx.interframe_ms = T_IFU_MS; +#endif +#endif /* SINGLE_FRAME */ +#ifdef PUBLIC_KEY_CAPABLE + sigfox_ep_api_ctx.local_application_message.common_parameters.ep_key_type = (control_message -> common_parameters).ep_key_type; +#endif + // Message type. + sigfox_ep_api_ctx.local_control_message.type = (control_message -> type); + // Store callbacks (even if NULL). + sigfox_ep_api_ctx.uplink_cplt_cb = (control_message -> uplink_cplt_cb); + sigfox_ep_api_ctx.message_cplt_cb = (control_message -> message_cplt_cb); + // Update pointer to local data. +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + sigfox_ep_api_ctx.common_parameters_ptr = &(sigfox_ep_api_ctx.local_control_message.common_parameters); +#endif + sigfox_ep_api_ctx.control_message_ptr = &(sigfox_ep_api_ctx.local_control_message); +#else /* ASYNCHRONOUS */ + // In blocking mode, the message pointer will directly address the client data since it will be kept during processing. + sigfox_ep_api_ctx.control_message_ptr = control_message; +#if !(defined SINGLE_FRAME) || !(defined UL_BIT_RATE_BPS) || !(defined TX_POWER_DBM_EIRP) || (defined PUBLIC_KEY_CAPABLE) + sigfox_ep_api_ctx.common_parameters_ptr = &(control_message -> common_parameters); +#endif +#endif /* ASYNCHRONOUS */ +#if (defined PARAMETERS_CHECK) && (defined ERROR_CODES) +errors: +#endif + RETURN(); +} +#endif + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _compute_next_ul_frequency(void) { + // Local variables. + sfx_u32 ul_frequency_hz = 0; + sfx_u16 random_value = 0; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + SIGFOX_EP_FREQUENCY_status_t frequency_status = SIGFOX_EP_FREQUENCY_SUCCESS; +#endif +#ifndef SINGLE_FRAME + SIGFOX_EP_FREQUENCY_uplink_signal_t frequency_parameters; +#endif + // Prepare frequency parameters. +#ifndef SINGLE_FRAME + frequency_parameters.ul_frame_rank = sigfox_ep_api_ctx.ul_frame_rank; +#ifdef BIDIRECTIONAL +#ifdef APPLICATION_MESSAGES + if (sigfox_ep_api_ctx.application_message_ptr == SFX_NULL) { + frequency_parameters.bidirectional_flag = 0; + } + else { + frequency_parameters.bidirectional_flag = (sigfox_ep_api_ctx.internal_flags.control_message != 0) ? 0 : ((sigfox_ep_api_ctx.application_message_ptr) -> bidirectional_flag); + } +#else + frequency_parameters.bidirectional_flag = 0; +#endif +#endif +#endif + // Compute frequency. +#ifndef SINGLE_FRAME +#ifdef ERROR_CODES + // Compute frequency. + frequency_status = SIGFOX_EP_FREQUENCY_compute_uplink(&frequency_parameters, &ul_frequency_hz); + SIGFOX_EP_FREQUENCY_check_status(SIGFOX_EP_API_ERROR_FREQUENCY); +#else + SIGFOX_EP_FREQUENCY_compute_uplink(&frequency_parameters, &ul_frequency_hz); +#endif +#else /* MULTIPLE_FRAMES */ +#ifdef ERROR_CODES + // Compute frequency. + frequency_status = SIGFOX_EP_FREQUENCY_compute_uplink(&ul_frequency_hz); + SIGFOX_EP_FREQUENCY_check_status(SIGFOX_EP_API_ERROR_FREQUENCY); +#else + SIGFOX_EP_FREQUENCY_compute_uplink(&ul_frequency_hz); +#endif +#endif /* MULTIPLE_FRAMES */ + // Update random value. +#ifdef ERROR_CODES + frequency_status = SIGFOX_EP_FREQUENCY_get_random_value(&random_value); + SIGFOX_EP_FREQUENCY_check_status(SIGFOX_EP_API_ERROR_FREQUENCY); +#else + SIGFOX_EP_FREQUENCY_get_random_value(&random_value); +#endif + sigfox_ep_api_ctx.random_value = random_value; +#ifdef CERTIFICATION + // Bypass frequency if needed. + if ((sigfox_ep_api_ctx.test_parameters.tx_frequency_hz) != 0) { + ul_frequency_hz = sigfox_ep_api_ctx.test_parameters.tx_frequency_hz; + } +#endif + // Update global variables. + sigfox_ep_api_ctx.ul_frequency_hz = ul_frequency_hz; +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +static sfx_bool _is_downlink_required(void) { + // Local variables. + sfx_bool downlink_required = SFX_FALSE; +#ifdef APPLICATION_MESSAGES + // Check message pointer. + if (sigfox_ep_api_ctx.application_message_ptr == SFX_NULL) goto errors; + // Check bidirectional flag. + if (((sigfox_ep_api_ctx.application_message_ptr -> bidirectional_flag) != 0) && (sigfox_ep_api_ctx.internal_flags.control_message == 0)) { + downlink_required = SFX_TRUE; + } +errors: +#endif + return downlink_required; +} +#endif + +/*******************************************************************/ +static sfx_bool _is_last_frame_of_uplink_sequence(void) { + // Local variables. + sfx_bool last_uplink_frame = SFX_FALSE; +#ifdef SINGLE_FRAME + last_uplink_frame = SFX_TRUE; +#else + if (sigfox_ep_api_ctx.ul_frame_rank >= (((sigfox_ep_api_ctx.common_parameters_ptr) -> number_of_frames) - 1)) { + last_uplink_frame = SFX_TRUE; + } +#endif + return last_uplink_frame; +} + +#if (defined REGULATORY) || (defined ASYNCHRONOUS) +/*******************************************************************/ +static sfx_bool _is_last_frame_of_message_sequence(void) { + // Local variables. + sfx_bool last_message_frame = SFX_FALSE; + // Check flags. +#ifdef BIDIRECTIONAL +#ifdef APPLICATION_MESSAGES + if ((sigfox_ep_api_ctx.application_message_ptr) == SFX_NULL) { + // Do not check bidirectional flag. + if ((sigfox_ep_api_ctx.internal_flags.ack_message != 0) || (_is_last_frame_of_uplink_sequence() == SFX_TRUE)) { + last_message_frame = SFX_TRUE; + } + } + else { + // Check bidirectional flag. + if ((sigfox_ep_api_ctx.internal_flags.ack_message != 0) || ((_is_last_frame_of_uplink_sequence() == SFX_TRUE) && (((sigfox_ep_api_ctx.application_message_ptr) -> bidirectional_flag) == 0))) { + last_message_frame = SFX_TRUE; + } + } +#endif +#else + last_message_frame = _is_last_frame_of_uplink_sequence(); +#endif + return last_message_frame; +} +#endif + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _radio_wake_up(void) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif + // Wake-up radio if in sleep. + if (sigfox_ep_api_ctx.internal_flags.radio_woken_up == 0) { +#ifdef ERROR_CODES + rf_status = RF_API_wake_up(); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_wake_up(); +#endif + // Update flag. + sigfox_ep_api_ctx.internal_flags.radio_woken_up = 1; + } +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _radio_sleep(void) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif + // Turn radio off if active. + if (sigfox_ep_api_ctx.internal_flags.radio_woken_up != 0) { +#ifdef ERROR_CODES + rf_status = RF_API_sleep(); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_sleep(); +#endif + // Update flag. + sigfox_ep_api_ctx.internal_flags.radio_woken_up = 0; + } +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} + +/*******************************************************************/ +static void _set_common_bitstream_parameters(SIGFOX_EP_BITSTREAM_common_t *bitsteam_common_parameter_ptr, sfx_u8 *ep_id) { + // EP ID and key. + bitsteam_common_parameter_ptr -> ep_id = ep_id; + // Message counter. + bitsteam_common_parameter_ptr -> message_counter = sigfox_ep_api_ctx.message_counter; +#ifndef MESSAGE_COUNTER_ROLLOVER + // Message counter rollover. + bitsteam_common_parameter_ptr -> message_counter_rollover = sigfox_ep_api_ctx.message_counter_rollover; +#endif +#ifndef SINGLE_FRAME + // Frame rank. + bitsteam_common_parameter_ptr -> ul_frame_rank = sigfox_ep_api_ctx.ul_frame_rank; +#endif +#ifdef PUBLIC_KEY_CAPABLE + bitsteam_common_parameter_ptr -> ep_key_type = (sigfox_ep_api_ctx.common_parameters_ptr) -> ep_key_type; +#endif +} + +#ifdef REGULATORY +/*******************************************************************/ +static void _set_tx_control_parameters(SIGFOX_TX_CONTROL_check_type check_type, SIGFOX_TX_CONTROL_parameters_t *tx_control_params) { + // Update TX control parameters. + tx_control_params -> type = check_type; + tx_control_params -> bitstream_length_bytes = SIGFOX_EP_BITSTREAM_SIZE_BYTES; // TODO dynamic computation. + tx_control_params -> last_message_frame = _is_last_frame_of_message_sequence(); +#ifndef UL_BIT_RATE_BPS + tx_control_params -> ul_bit_rate_bps = SIGFOX_UL_BIT_RATE_BPS_LIST[(sigfox_ep_api_ctx.common_parameters_ptr) -> ul_bit_rate]; +#endif +#ifndef SINGLE_FRAME + tx_control_params -> ul_frame_rank = sigfox_ep_api_ctx.ul_frame_rank; + tx_control_params -> number_of_frames = (sigfox_ep_api_ctx.common_parameters_ptr) -> number_of_frames; +#endif +#ifdef BIDIRECTIONAL + tx_control_params -> ack_message = sigfox_ep_api_ctx.internal_flags.ack_message; +#endif +#if !(defined SINGLE_FRAME) || (defined BIDIRECTIONAL) + tx_control_params -> interframe_ms = sigfox_ep_api_ctx.interframe_ms; // Tifu, Tifb or Tconf. +#endif +#ifdef CERTIFICATION +#ifdef SPECTRUM_ACCESS_FH + tx_control_params -> fh_timer_enable = sigfox_ep_api_ctx.test_parameters.fh_timer_enable; +#endif +#ifdef SPECTRUM_ACCESS_LBT + tx_control_params -> lbt_enable = sigfox_ep_api_ctx.test_parameters.lbt_enable; +#endif +#endif +#ifdef ASYNCHRONOUS + tx_control_params -> cplt_cb = (SIGFOX_TX_CONTROL_check_cplt_cb_t) ((check_type == SIGFOX_TX_CONTROL_TYPE_PRE_CHECK) ? &_SIGFOX_TX_CONTROL_pre_check_cplt_cb : &_SIGFOX_TX_CONTROL_post_check_cplt_cb); +#endif +} +#endif + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _send_frame(sfx_u8 *bitstream, sfx_u8 bitstream_size) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif + RF_API_radio_parameters_t radio_params; + RF_API_tx_data_t tx_data; + // Build radio parameters. + radio_params.rf_mode = RF_API_MODE_TX; + radio_params.modulation = RF_API_MODULATION_DBPSK; + radio_params.frequency_hz = sigfox_ep_api_ctx.ul_frequency_hz; +#ifdef UL_BIT_RATE_BPS + radio_params.bit_rate_bps = UL_BIT_RATE_BPS; +#else + radio_params.bit_rate_bps = SIGFOX_UL_BIT_RATE_BPS_LIST[(sigfox_ep_api_ctx.common_parameters_ptr) -> ul_bit_rate]; +#endif +#ifdef TX_POWER_DBM_EIRP + radio_params.tx_power_dbm_eirp = TX_POWER_DBM_EIRP; +#else + radio_params.tx_power_dbm_eirp = (sigfox_ep_api_ctx.common_parameters_ptr) -> tx_power_dbm_eirp; +#endif +#ifdef BIDIRECTIONAL + radio_params.deviation_hz = 0; +#endif + // Wake-up radio. +#ifdef ERROR_CODES + status = _radio_wake_up(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _radio_wake_up(); +#endif + // Start radio. +#ifdef ERROR_CODES + rf_status = RF_API_init(&radio_params); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_init(&radio_params); +#endif + // Send frame. + tx_data.bitstream = (sfx_u8*) bitstream; + tx_data.bitstream_size_bytes = bitstream_size; +#ifdef ASYNCHRONOUS + tx_data.cplt_cb = (RF_API_tx_cplt_cb_t) &_RF_API_tx_cplt_cb; +#endif +#ifdef ERROR_CODES + rf_status = RF_API_send(&tx_data); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_send(&tx_data); +#endif +#ifndef ASYNCHRONOUS + // Note: thanks to the RF_API_check_status the next lines are not executed in case of RF API error. + // Increment success count. + sigfox_ep_api_ctx.internal_flags.frame_success = 1; +#ifndef SINGLE_FRAME + sigfox_ep_api_ctx.frame_success_count++; +#endif +#endif /* BLOCKING */ +#ifdef ERROR_CODES +errors: +#endif +#ifndef ASYNCHRONOUS + sigfox_ep_api_ctx.irq_flags.rf_tx_cplt = 1; // Set flag manually in blocking mode. +#endif + RETURN(); +} + +#ifdef APPLICATION_MESSAGES +/*******************************************************************/ +static SIGFOX_EP_API_status_t _send_application_frame(void) { + // Local variables. + sfx_u8 idx = 0; + sfx_u8 ep_id[SIGFOX_EP_ID_SIZE_BYTES]; + SIGFOX_EP_BITSTREAM_application_frame_t bitstream_parameters; + sfx_u8 bitstream[SIGFOX_EP_BITSTREAM_SIZE_BYTES]; + sfx_u8 bitstream_size; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + SIGFOX_EP_BITSTREAM_status_t bitstream_status = SIGFOX_EP_BITSTREAM_SUCCESS; +#endif + // Create local EP-ID. + for (idx=0 ; idx type); +#if !(defined UL_PAYLOAD_SIZE) || (UL_PAYLOAD_SIZE > 0) + bitstream_parameters.ul_payload = ((sigfox_ep_api_ctx.application_message_ptr) -> ul_payload); +#endif +#ifndef UL_PAYLOAD_SIZE + bitstream_parameters.ul_payload_size_bytes = ((sigfox_ep_api_ctx.application_message_ptr) -> ul_payload_size_bytes); +#endif +#ifdef BIDIRECTIONAL + bitstream_parameters.bidirectional_flag = ((sigfox_ep_api_ctx.application_message_ptr) -> bidirectional_flag); +#endif + // Build frame. +#ifdef ERROR_CODES + bitstream_status = SIGFOX_EP_BITSTREAM_build_application_frame(&bitstream_parameters, bitstream, &bitstream_size); + SIGFOX_EP_BITSTREAM_check_status(SIGFOX_EP_API_ERROR_BITSTREAM); +#else + SIGFOX_EP_BITSTREAM_build_application_frame(&bitstream_parameters, bitstream, &bitstream_size); +#endif + // Send frame. +#ifdef ERROR_CODES + status = _send_frame(bitstream, bitstream_size); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _send_frame(bitstream, bitstream_size); +#endif +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} +#endif + +#if (defined CONTROL_KEEP_ALIVE_MESSAGE) || (defined BIDIRECTIONAL) +/*******************************************************************/ +static SIGFOX_EP_API_status_t _read_mcu_voltage_temperature(void) { + // Local variables. + sfx_s16 temperature_tenth_degrees = 0; + sfx_u16 voltage_tx_mv = 0; + sfx_u16 voltage_idle_mv = 0; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif +#ifdef SINGLE_FRAME + // Read MCU data. +#ifdef ERROR_CODES + mcu_status = MCU_API_get_voltage_temperature(&voltage_idle_mv, &voltage_tx_mv, &temperature_tenth_degrees); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_get_voltage_temperature(&voltage_idle_mv, &voltage_tx_mv, &temperature_tenth_degrees); +#endif + // Update global variables. + sigfox_ep_api_ctx.voltage_idle_mv = voltage_idle_mv; + sigfox_ep_api_ctx.voltage_tx_mv = voltage_tx_mv; + sigfox_ep_api_ctx.temperature_tenth_degrees = temperature_tenth_degrees; +#else /* SINGLE_FRAME */ + if (sigfox_ep_api_ctx.ul_frame_rank == SIGFOX_UL_FRAME_RANK_1) { + // Read MCU data. +#ifdef ERROR_CODES + mcu_status = MCU_API_get_voltage_temperature(&voltage_idle_mv, &voltage_tx_mv, &temperature_tenth_degrees); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_get_voltage_temperature(&voltage_idle_mv, &voltage_tx_mv, &temperature_tenth_degrees); +#endif + // Update global variables. + sigfox_ep_api_ctx.voltage_idle_mv = voltage_idle_mv; + sigfox_ep_api_ctx.voltage_tx_mv = voltage_tx_mv; + sigfox_ep_api_ctx.temperature_tenth_degrees = temperature_tenth_degrees; + } +#endif /* SINGLE_FRAME */ +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +/*******************************************************************/ +static SIGFOX_EP_API_status_t _send_control_keep_alive_frame(void) { + // Local variables. + sfx_u8 idx = 0; + sfx_u8 ep_id[SIGFOX_EP_ID_SIZE_BYTES]; + SIGFOX_EP_BITSTREAM_control_frame_t bitstream_parameters; + sfx_u8 bitstream[SIGFOX_EP_BITSTREAM_SIZE_BYTES]; + sfx_u8 bitstream_size; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + SIGFOX_EP_BITSTREAM_status_t bitstream_status = SIGFOX_EP_BITSTREAM_SUCCESS; +#endif + // Create local EP-ID. + for (idx=0 ; idx ep_key_type; +#endif + // Check DL frame. +#ifdef ERROR_CODES + bitstream_status = SIGFOX_EP_BITSTREAM_decode_downlink_frame(&bitstream_parameters, &dl_status, (sfx_u8*) dl_payload); + SIGFOX_EP_BITSTREAM_check_status(SIGFOX_EP_API_ERROR_BITSTREAM); +#else + SIGFOX_EP_BITSTREAM_decode_downlink_frame(&bitstream_parameters, &dl_status, (sfx_u8*) dl_payload); +#endif + // Update data in global context. + if (dl_status == SFX_TRUE) { + for (idx=0 ; idx 0))) || (defined BIDIRECTIONAL) +/*******************************************************************/ +static SIGFOX_EP_API_status_t _start_timer1(sfx_u16 duration_ms) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif + MCU_API_timer_t mcu_timer; + // Start timer. + mcu_timer.instance = MCU_API_TIMER_1; + mcu_timer.duration_ms = duration_ms; +#ifdef ASYNCHRONOUS + mcu_timer.cplt_cb = (MCU_API_timer_cplt_cb_t) &_MCU_API_timer1_cplt_cb; +#endif +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_start(&mcu_timer); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_start(&mcu_timer); +#endif +#ifdef ERROR_CODES +errors: + return status; +#endif + RETURN(); +} +#endif + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +static SIGFOX_EP_API_status_t _start_timer2(sfx_u16 duration_ms) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif + MCU_API_timer_t mcu_timer; + // Start timer. + mcu_timer.instance = MCU_API_TIMER_2; + mcu_timer.duration_ms = duration_ms; +#ifdef ASYNCHRONOUS + mcu_timer.cplt_cb = (MCU_API_timer_cplt_cb_t) &_MCU_API_timer2_cplt_cb; +#endif + // Start timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_start(&mcu_timer); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_start(&mcu_timer); +#endif +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} +#endif + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _end_transmission(void); + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _start_transmission(void) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#ifdef REGULATORY + SIGFOX_TX_CONTROL_status_t tx_control_status = SIGFOX_TX_CONTROL_SUCCESS; +#endif +#endif +#ifdef REGULATORY + SIGFOX_TX_CONTROL_parameters_t tx_control_params; + SIGFOX_TX_CONTROL_result_t tx_control_pre_check_result = SIGFOX_TX_CONTROL_RESULT_FORBIDDEN; +#endif + // Reset TX success flag. + sigfox_ep_api_ctx.internal_flags.frame_success = 0; + // Compute frequency. + // This must be done here since the frame 1 frequency has to be known for eventual bidirectional procedure, even if the frame itself is not sent (due to error or TX control). +#ifdef ERROR_CODES + status = _compute_next_ul_frequency(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _compute_next_ul_frequency(); +#endif +#ifdef REGULATORY + // Check if radio is required for pre-check. + if (SIGFOX_TX_CONTROL_is_radio_required(SIGFOX_TX_CONTROL_TYPE_PRE_CHECK) == SFX_TRUE) { +#ifdef ERROR_CODES + status = _radio_wake_up(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _radio_wake_up(); +#endif + } + _set_tx_control_parameters(SIGFOX_TX_CONTROL_TYPE_PRE_CHECK, &tx_control_params); + // Start TX control. +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_check(&tx_control_params); + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_check(&tx_control_params); +#endif + sigfox_ep_api_ctx.internal_flags.tx_control_pre_check_running = 1; +#ifndef ASYNCHRONOUS + sigfox_ep_api_ctx.irq_flags.tx_control_pre_check_cplt = 1; // Set flag manually in blocking mode. +#endif + // Read result. +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_get_result(&tx_control_pre_check_result); + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_get_result(&tx_control_pre_check_result); +#endif + // Update state according to result. + switch (tx_control_pre_check_result) { + case SIGFOX_TX_CONTROL_RESULT_ALLOWED: + // Send frame. +#ifdef ERROR_CODES + status = sigfox_ep_api_ctx.sending_function_ptr(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + sigfox_ep_api_ctx.sending_function_ptr(); +#endif + // Update state. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_UL_MODULATION_PENDING; + break; + case SIGFOX_TX_CONTROL_RESULT_FORBIDDEN: + // Set error flags. + sigfox_ep_api_ctx.message_status.error = 1; + sigfox_ep_api_ctx.internal_flags.tx_forbidden = 1; +#ifdef ERROR_STACK + // Add error to stack. + SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_EP_LIBRARY, SIGFOX_EP_API_ERROR_TX_FORBIDDEN); +#endif + // Try next frame. +#ifdef ERROR_CODES + status = _end_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _end_transmission(); +#endif + break; + case SIGFOX_TX_CONTROL_RESULT_PENDING: + // Wait for completion. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_REGULATORY; + break; + default: + EXIT_ERROR(SIGFOX_EP_API_ERROR_TX_CONTROL); + break; + } +#else /* REGULATORY */ + // Send frame. +#ifdef ERROR_CODES + status = sigfox_ep_api_ctx.sending_function_ptr(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + sigfox_ep_api_ctx.sending_function_ptr(); +#endif + // Update state. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_UL_MODULATION_PENDING; +#endif /* REGULATORY */ +#if (defined ERROR_CODES) || (defined REGULATORY) +errors: +#endif + RETURN(); +} + +/*******************************************************************/ +static void _update_message_status(void) { +#ifdef SINGLE_FRAME +#ifdef BIDIRECTIONAL + // Update message status. + if (sigfox_ep_api_ctx.internal_flags.ack_message != 0) { + sigfox_ep_api_ctx.message_status.ack_frame = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + } + else { + sigfox_ep_api_ctx.message_status.app_frame_1 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + } +#else /* BIDIRECTIONAL */ + // Update message status. + sigfox_ep_api_ctx.message_status.app_frame_1 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; +#endif /* BIDIRECTIONAL */ +#else /* SINGLE_FRAME */ +#ifdef BIDIRECTIONAL + // Update message status. + if (sigfox_ep_api_ctx.internal_flags.ack_message != 0) { + sigfox_ep_api_ctx.message_status.ack_frame = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + } + else { + switch (sigfox_ep_api_ctx.ul_frame_rank) { + case SIGFOX_UL_FRAME_RANK_1: + sigfox_ep_api_ctx.message_status.app_frame_1 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + break; + case SIGFOX_UL_FRAME_RANK_2: + sigfox_ep_api_ctx.message_status.app_frame_2 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + break; + case SIGFOX_UL_FRAME_RANK_3: + sigfox_ep_api_ctx.message_status.app_frame_3 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + break; + default: + break; + } + } +#else /* BIDIRECTIONAL */ + switch (sigfox_ep_api_ctx.ul_frame_rank) { + case SIGFOX_UL_FRAME_RANK_1: + sigfox_ep_api_ctx.message_status.app_frame_1 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + break; + case SIGFOX_UL_FRAME_RANK_2: + sigfox_ep_api_ctx.message_status.app_frame_2 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + break; + case SIGFOX_UL_FRAME_RANK_3: + sigfox_ep_api_ctx.message_status.app_frame_3 = (sigfox_ep_api_ctx.internal_flags.frame_success != 0) ? 1 : 0; + break; + default: + break; + } +#endif /* BIDIRECTIONAL */ +#endif /* SINGLE_FRAME */ +} + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _write_nvm(void) { + // Local variable. + sfx_u8 nvm_data[SIGFOX_NVM_DATA_SIZE_BYTES]; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif + // Write message counter and random value in NVM when at least one frame has been successfully transmitted. +#ifdef SINGLE_FRAME + if (sigfox_ep_api_ctx.internal_flags.frame_success != 0) { +#else + if (sigfox_ep_api_ctx.frame_success_count > 0) { +#endif + // Build local NVM array. + nvm_data[SIGFOX_NVM_DATA_INDEX_MESSAGE_COUNTER_MSB] = (sfx_u8) ((sigfox_ep_api_ctx.message_counter >> 8) & 0x00FF); + nvm_data[SIGFOX_NVM_DATA_INDEX_MESSAGE_COUNTER_LSB] = (sfx_u8) ((sigfox_ep_api_ctx.message_counter >> 0) & 0x00FF); + nvm_data[SIGFOX_NVM_DATA_INDEX_RANDOM_VALUE_MSB] = (sfx_u8) ((sigfox_ep_api_ctx.random_value >> 8) & 0x00FF); + nvm_data[SIGFOX_NVM_DATA_INDEX_RANDOM_VALUE_LSB] = (sfx_u8) ((sigfox_ep_api_ctx.random_value >> 0) & 0x00FF); + // Write NVM. +#ifdef ERROR_CODES + mcu_status = MCU_API_set_nvm(nvm_data, SIGFOX_NVM_DATA_SIZE_BYTES); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_set_nvm(nvm_data, SIGFOX_NVM_DATA_SIZE_BYTES); +#endif + // Update flags. + sigfox_ep_api_ctx.internal_flags.nvm_write_pending = 0; + } +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _compute_state_after_transmission(void) { + // Local variables. + SIGFOX_EP_API_state_t state = SIGFOX_EP_API_STATE_READY; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#if (defined BIDIRECTIONAL) && !(defined SINGLE_FRAME) + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif +#endif +#ifdef SINGLE_FRAME +#ifdef BIDIRECTIONAL + // Compute next state. + if (_is_downlink_required() == SFX_TRUE) { + // Abort downlink sequence if the frame has not been sent due to error or TX control. + if (sigfox_ep_api_ctx.internal_flags.frame_success == 0) { + // Force bidirectional to 0 in order to call the message completion callback. + if (sigfox_ep_api_ctx.application_message_ptr != SFX_NULL) { + (sigfox_ep_api_ctx.application_message_ptr) -> bidirectional_flag = 0; + } + // Force state to ready. + state = SIGFOX_EP_API_STATE_READY; + } + else { + // Start DL_T_W timer. +#ifdef ERROR_CODES + status = _start_timer2(((sigfox_ep_api_ctx.rc_ptr) -> spectrum_access) -> dl_t_w_ms); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_timer2(((sigfox_ep_api_ctx.rc_ptr) -> spectrum_access) -> dl_t_w_ms); +#endif + state = SIGFOX_EP_API_STATE_DL_TIMER; + } + } + else { + state = SIGFOX_EP_API_STATE_READY; + } +#else /* BIDIRECTIONAL */ + // Compute next state. + state = SIGFOX_EP_API_STATE_READY; +#endif /* BIDIRECTIONAL */ +#else /* SINGLE_FRAME */ +#ifdef BIDIRECTIONAL + // Check bidirectional flag. + if ((sigfox_ep_api_ctx.ul_frame_rank == SIGFOX_UL_FRAME_RANK_1) && (_is_downlink_required() == SFX_TRUE)) { + // Start DL_T_W timer. +#ifdef ERROR_CODES + status = _start_timer2(((sigfox_ep_api_ctx.rc_ptr) -> spectrum_access) -> dl_t_w_ms); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_timer2(((sigfox_ep_api_ctx.rc_ptr) -> spectrum_access) -> dl_t_w_ms); +#endif + } + // Compute next state. + if (_is_last_frame_of_uplink_sequence() == SFX_TRUE) { + // Check bidirectional condition. + if (_is_downlink_required() == SFX_TRUE) { + // Abort downlink sequence if none frame has been sent due to error or TX control. + if (sigfox_ep_api_ctx.frame_success_count == 0) { + // Stop DL_T_W timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_2); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_2); +#endif + // Force bidirectional to 0 in order to call the message completion callback. + if (sigfox_ep_api_ctx.application_message_ptr != SFX_NULL) { + (sigfox_ep_api_ctx.application_message_ptr) -> bidirectional_flag = 0; + } + // Force state to ready. + state = SIGFOX_EP_API_STATE_READY; + } + else { + state = SIGFOX_EP_API_STATE_DL_TIMER; + } + } + else { + state = SIGFOX_EP_API_STATE_READY; + } + } + else { + // Compute next state. + if (_is_downlink_required() == SFX_TRUE) { + sigfox_ep_api_ctx.interframe_ms = SIGFOX_T_IFB_MS; + state = SIGFOX_EP_API_STATE_UL_INTER_FRAME_TIMER; + } + else { + // Compute inter-frame duration. +#ifdef T_IFU_MS + sigfox_ep_api_ctx.interframe_ms = T_IFU_MS; +#else + sigfox_ep_api_ctx.interframe_ms = ((sigfox_ep_api_ctx.common_parameters_ptr) -> t_ifu_ms); +#endif + } + // Start inter-frame timer if required. + if ((sigfox_ep_api_ctx.interframe_ms) != 0) { +#ifdef ERROR_CODES + status = _start_timer1(sigfox_ep_api_ctx.interframe_ms); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_timer1(sigfox_ep_api_ctx.interframe_ms); +#endif + // Update state. + state = SIGFOX_EP_API_STATE_UL_INTER_FRAME_TIMER; + } + else { + // Inter-frame delay set to 0, directly start next frame. +#ifdef ERROR_CODES + status = _start_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_transmission(); +#endif + } + } +#else /* BIDIRECTIONAL */ + // Compute next state. + if (_is_last_frame_of_uplink_sequence() == SFX_TRUE) { + state = SIGFOX_EP_API_STATE_READY; + } + else { + // Compute inter-frame duration. +#ifdef T_IFU_MS + sigfox_ep_api_ctx.interframe_ms = T_IFU_MS; +#if (T_IFU_MS > 0) + // Start inter-frame timer. +#ifdef ERROR_CODES + status = _start_timer1(sigfox_ep_api_ctx.interframe_ms); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_timer1(sigfox_ep_api_ctx.interframe_ms); +#endif + // Update state. + state = SIGFOX_EP_API_STATE_UL_INTER_FRAME_TIMER; +#else /* T_IFU_MS > 0 */ + // Inter-frame delay set to 0, directly start next frame. +#ifdef ERROR_CODES + status = _start_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_transmission(); +#endif +#endif /* T_IFU_MS > 0 */ +#else /* T_IFU_MS */ + sigfox_ep_api_ctx.interframe_ms = ((sigfox_ep_api_ctx.common_parameters_ptr) -> t_ifu_ms); + // Start inter-frame timer if required. + if ((sigfox_ep_api_ctx.interframe_ms) != 0) { +#ifdef ERROR_CODES + status = _start_timer1(sigfox_ep_api_ctx.interframe_ms); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_timer1(sigfox_ep_api_ctx.interframe_ms); +#endif + // Update state. + state = SIGFOX_EP_API_STATE_UL_INTER_FRAME_TIMER; + } + else { + // Inter-frame delay set to 0, directly start next frame. +#ifdef ERROR_CODES + status = _start_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_transmission(); +#endif + } +#endif /* T_IFU_MS */ + } +#endif /* BIDIRECTIONAL */ +#endif /* SINGLE_FRAME */ +#ifdef ASYNCHRONOUS + // Manage API callbacks. + if ((_is_last_frame_of_uplink_sequence() == SFX_TRUE) && (sigfox_ep_api_ctx.internal_flags.ack_message == 0)) { + // Call uplink completion callback (except for ACK message). + _UPLINK_CPLT_CALLBACK(); + } + if (_is_last_frame_of_message_sequence() == SFX_TRUE) { + // Call message completion callback. + _MESSAGE_CPLT_CALLBACK(); + } +#endif /* ASYNCHRONOUS */ +#ifndef SINGLE_FRAME + // Increment frame rank. + sigfox_ep_api_ctx.ul_frame_rank++; +#endif + // Update global state. + sigfox_ep_api_ctx.state = state; +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _end_transmission(void) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + RF_API_status_t rf_status = RF_API_SUCCESS; +#ifdef REGULATORY + SIGFOX_TX_CONTROL_status_t tx_control_status = SIGFOX_TX_CONTROL_SUCCESS; +#endif +#endif +#ifdef REGULATORY + SIGFOX_TX_CONTROL_parameters_t tx_control_params; + SIGFOX_TX_CONTROL_result_t tx_control_post_check_result = SIGFOX_TX_CONTROL_RESULT_FORBIDDEN; +#endif + // Update message status. + _update_message_status(); + // Stop RF. + // Note: if TX control returned forbidden result, the RF_API_de_init function was already called by the TX control itself. + if (sigfox_ep_api_ctx.internal_flags.frame_success != 0) { +#ifdef ERROR_CODES + rf_status = RF_API_de_init(); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_de_init(); +#endif + } + // Manage radio state and NVM. + if (_is_last_frame_of_uplink_sequence() == SFX_TRUE) { +#ifdef REGULATORY + if (SIGFOX_TX_CONTROL_is_radio_required(SIGFOX_TX_CONTROL_TYPE_POST_CHECK) == SFX_FALSE) { +#ifdef ERROR_CODES + status = _radio_sleep(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _radio_sleep(); +#endif + } +#else /* REGULATORY */ +#ifdef ERROR_CODES + status = _radio_sleep(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _radio_sleep(); +#endif +#endif /* REGULATORY */ + // Write NVM. +#ifdef ERROR_CODES + status = _write_nvm(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _write_nvm(); +#endif + } +#ifdef REGULATORY + // Set parameters. + _set_tx_control_parameters(SIGFOX_TX_CONTROL_TYPE_POST_CHECK, &tx_control_params); +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_check(&tx_control_params); + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_check(&tx_control_params); +#endif + sigfox_ep_api_ctx.internal_flags.tx_control_post_check_running = 1; +#ifndef ASYNCHRONOUS + sigfox_ep_api_ctx.irq_flags.tx_control_post_check_cplt = 1; // Set flag manually in blocking mode. +#endif + // Read result. +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_get_result(&tx_control_post_check_result); + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_get_result(&tx_control_post_check_result); +#endif + // Update state according to result. + switch (tx_control_post_check_result) { + case SIGFOX_TX_CONTROL_RESULT_FORBIDDEN: + sigfox_ep_api_ctx.message_status.error = 1; + sigfox_ep_api_ctx.internal_flags.tx_forbidden = 1; +#ifdef ERROR_STACK + // Add error to stack. + SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_EP_LIBRARY, SIGFOX_EP_API_ERROR_TX_FORBIDDEN); +#endif + // Note: no break since "forbidden" and "allowed" cases are treated the same way (except for error flag). + case SIGFOX_TX_CONTROL_RESULT_ALLOWED: + // Compute next state. +#ifdef ERROR_CODES + status = _compute_state_after_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _compute_state_after_transmission(); +#endif + break; + case SIGFOX_TX_CONTROL_RESULT_PENDING: + // Wait for completion. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_REGULATORY; + break; + default: + EXIT_ERROR(SIGFOX_EP_API_ERROR_TX_CONTROL); + break; + } +#else /* REGULATORY */ + // Compute next state. +#ifdef ERROR_CODES + status = _compute_state_after_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _compute_state_after_transmission(); +#endif +#endif /* REGULATORY */ +#if (defined ERROR_CODES) || (defined REGULATORY) +errors: +#endif + RETURN(); +} + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +static SIGFOX_EP_API_status_t _start_reception(RF_API_rx_data_t *rx_data_ptr) { + // Local variables. + RF_API_radio_parameters_t radio_params; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + RF_API_status_t rf_status = RF_API_SUCCESS; + SIGFOX_EP_FREQUENCY_status_t frequency_status = SIGFOX_EP_FREQUENCY_SUCCESS; +#endif + // Set radio configuration for downlink reception. + radio_params.rf_mode = RF_API_MODE_RX; + radio_params.modulation = RF_API_MODULATION_GFSK; + radio_params.bit_rate_bps = SIGFOX_DL_BIT_RATE_BPS; + radio_params.deviation_hz = SIGFOX_DL_GFSK_DEVIATION_HZ; + // Set uplink frequency. +#ifdef ERROR_CODES + frequency_status = SIGFOX_EP_FREQUENCY_compute_downlink(&radio_params.frequency_hz); + SIGFOX_EP_FREQUENCY_check_status(SIGFOX_EP_API_ERROR_FREQUENCY); +#else + SIGFOX_EP_FREQUENCY_compute_downlink(&radio_params.frequency_hz); +#endif +#ifdef ERROR_CODES + rf_status = RF_API_init(&radio_params); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_init(&radio_params); +#endif + // Start DL reception. +#ifdef ERROR_CODES + rf_status = RF_API_receive(rx_data_ptr); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_receive(rx_data_ptr); +#endif +#ifdef ERROR_CODES +errors: +#endif + RETURN(); +} +#endif + +/*******************************************************************/ +static void _message_prepare(void) { + // Increment message counter. + // Note: operation is only performed in RAM first, it will be write in NVM when at least 1 frame has been successfully transmitted. + // Note: the nvm_write_pending flag is checked to prevent multiple increments if the previous value was not written in NVM (due to TX control forbidden or error). + if (sigfox_ep_api_ctx.internal_flags.nvm_write_pending == 0) { +#ifdef MESSAGE_COUNTER_ROLLOVER + sigfox_ep_api_ctx.message_counter = (sigfox_ep_api_ctx.message_counter + 1) % MESSAGE_COUNTER_ROLLOVER; +#else + sigfox_ep_api_ctx.message_counter = (sigfox_ep_api_ctx.message_counter + 1) % (sigfox_ep_api_ctx.message_counter_rollover); +#endif + // Update flag. + sigfox_ep_api_ctx.internal_flags.nvm_write_pending = 1; + } + sigfox_ep_api_ctx.internal_flags.tx_forbidden = 0; +#ifndef SINGLE_FRAME + // Reset frame rank. + sigfox_ep_api_ctx.ul_frame_rank = SIGFOX_UL_FRAME_RANK_1; + sigfox_ep_api_ctx.frame_success_count = 0; +#endif +} + +/*******************************************************************/ +static SIGFOX_EP_API_status_t _internal_process(void) { + // Local variables. +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#if (defined ASYNCHRONOUS) || (defined BIDIRECTIONAL) + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif +#if (defined ASYNCHRONOUS) || (defined BIDIRECTIONAL) || (!(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0))) + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif +#ifdef REGULATORY + SIGFOX_TX_CONTROL_status_t tx_control_status = SIGFOX_TX_CONTROL_SUCCESS; +#endif +#endif /* ERROR_CODES */ +#ifdef BIDIRECTIONAL + RF_API_rx_data_t rx_data; +#endif +#ifdef REGULATORY + SIGFOX_TX_CONTROL_result_t tx_control_result = SIGFOX_TX_CONTROL_RESULT_FORBIDDEN; +#endif + // Prepare RX data structure. +#ifdef BIDIRECTIONAL + rx_data.dl_phy_content_size = SIGFOX_DL_PHY_CONTENT_SIZE_BYTES; +#ifdef ASYNCHRONOUS + rx_data.data_received_cb = (RF_API_rx_data_received_cb_t) &_RF_API_rx_data_received_cb; +#else + rx_data.data_received = SFX_FALSE; +#endif +#endif + // Check library is opened. + _CHECK_LIBRARY_STATE(== SIGFOX_EP_API_STATE_CLOSED); +#ifdef ASYNCHRONOUS + // Check error flag in case the process was called after a low level error callback. + if (sigfox_ep_api_ctx.irq_flags.low_level_error != 0) { + // Clear flag. + sigfox_ep_api_ctx.irq_flags.low_level_error = 0; + // Exit. + EXIT_ERROR(sigfox_ep_api_ctx.status_from_callback); + } +#endif + // Check low level process flags. +#ifdef ASYNCHRONOUS + if (sigfox_ep_api_ctx.irq_flags.mcu_process != 0) { +#ifdef ERROR_CODES + mcu_status = MCU_API_process(); + sigfox_ep_api_ctx.irq_flags.mcu_process = 0; + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_process(); + sigfox_ep_api_ctx.irq_flags.mcu_process = 0; +#endif + } + if (sigfox_ep_api_ctx.irq_flags.rf_process != 0) { +#ifdef ERROR_CODES + rf_status = RF_API_process(); + sigfox_ep_api_ctx.irq_flags.rf_process = 0; + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_process(); + sigfox_ep_api_ctx.irq_flags.rf_process = 0; +#endif + } +#ifdef REGULATORY + if (sigfox_ep_api_ctx.irq_flags.tx_control_process != 0) { +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_process(); + sigfox_ep_api_ctx.irq_flags.tx_control_process = 0; + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_process(); + sigfox_ep_api_ctx.irq_flags.tx_control_process = 0; +#endif + } +#endif +#endif + // Perform internal state machine. + switch (sigfox_ep_api_ctx.state) { + case SIGFOX_EP_API_STATE_READY: + // Check pending requests. + if (sigfox_ep_api_ctx.internal_flags.send_message_request != 0) { +#ifdef ERROR_CODES + status = _start_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_transmission(); +#endif + // Clear request. + sigfox_ep_api_ctx.internal_flags.send_message_request = 0; + } + break; +#ifdef REGULATORY + case SIGFOX_EP_API_STATE_REGULATORY: + // Check completion flags. + if ((sigfox_ep_api_ctx.irq_flags.tx_control_post_check_cplt != 0) || (sigfox_ep_api_ctx.irq_flags.tx_control_pre_check_cplt != 0)) { + // Check pre-check flags. + if (sigfox_ep_api_ctx.irq_flags.tx_control_pre_check_cplt != 0) { + // Clear flags. + sigfox_ep_api_ctx.irq_flags.tx_control_pre_check_cplt = 0; + sigfox_ep_api_ctx.internal_flags.tx_control_pre_check_running = 0; + // Read result. +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_get_result(&tx_control_result); + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_get_result(&tx_control_result); +#endif + // Check TX control status. + if (tx_control_result == SIGFOX_TX_CONTROL_RESULT_ALLOWED) { + // Send frame. +#ifdef ERROR_CODES + status = sigfox_ep_api_ctx.sending_function_ptr(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + sigfox_ep_api_ctx.sending_function_ptr(); +#endif + // Update state. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_UL_MODULATION_PENDING; + } + else { + // Set error flags. + sigfox_ep_api_ctx.message_status.error = 1; + sigfox_ep_api_ctx.internal_flags.tx_forbidden = 1; +#ifdef ERROR_STACK + // Add error to stack. + SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_EP_LIBRARY, SIGFOX_EP_API_ERROR_TX_FORBIDDEN); +#endif + // Compute next state. +#ifdef ERROR_CODES + status = _end_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _end_transmission(); +#endif + } + } + // Check post-check flag. + if (sigfox_ep_api_ctx.irq_flags.tx_control_post_check_cplt != 0) { + // Clear flags. + sigfox_ep_api_ctx.irq_flags.tx_control_post_check_cplt = 0; + sigfox_ep_api_ctx.internal_flags.tx_control_post_check_running = 0; + // Read result. +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_get_result(&tx_control_result); + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_get_result(&tx_control_result); +#endif + // Set error flags if needed. + if (tx_control_result == SIGFOX_TX_CONTROL_RESULT_FORBIDDEN) { + sigfox_ep_api_ctx.message_status.error = 1; + sigfox_ep_api_ctx.internal_flags.tx_forbidden = 1; +#ifdef ERROR_STACK + // Add error to stack. + SIGFOX_ERROR_stack(SIGFOX_ERROR_SOURCE_EP_LIBRARY, SIGFOX_EP_API_ERROR_TX_FORBIDDEN); +#endif + } + // Switch to next state whatever the result. +#ifdef ERROR_CODES + status = _compute_state_after_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _compute_state_after_transmission(); +#endif + } + } + break; +#endif + case SIGFOX_EP_API_STATE_UL_MODULATION_PENDING: + // Check end of transmission flag.. + if (sigfox_ep_api_ctx.irq_flags.rf_tx_cplt != 0) { + // Clear flag. + sigfox_ep_api_ctx.irq_flags.rf_tx_cplt = 0; +#ifdef ASYNCHRONOUS + // Increment success count. + sigfox_ep_api_ctx.internal_flags.frame_success = 1; +#ifndef SINGLE_FRAME + sigfox_ep_api_ctx.frame_success_count++; +#endif +#endif + // End transmission. +#ifdef ERROR_CODES + status = _end_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _end_transmission(); +#endif + } + break; +#if !(defined SINGLE_FRAME) && (!(defined T_IFU_MS) || (T_IFU_MS > 0) || (defined BIDIRECTIONAL)) + case SIGFOX_EP_API_STATE_UL_INTER_FRAME_TIMER: +#ifndef ASYNCHRONOUS + // Wait for timer completion. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_wait_cplt(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_wait_cplt(MCU_API_TIMER_1); +#endif + sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt = 1; // Set flag manually in blocking mode. +#endif + // Check timer completion flag. + if (sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt != 0) { + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_1); +#endif + sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt = 0; + // Start next frame. +#ifdef ERROR_CODES + status = _start_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_transmission(); +#endif + } + break; +#endif +#ifdef BIDIRECTIONAL + case SIGFOX_EP_API_STATE_DL_TIMER: +#ifndef ASYNCHRONOUS + // Wait for timer completion. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_wait_cplt(MCU_API_TIMER_2); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_wait_cplt(MCU_API_TIMER_2); +#endif + sigfox_ep_api_ctx.irq_flags.mcu_timer2_cplt = 1; // Set flag manually in blocking mode. +#endif /* BLOCKING */ + // Check timer completion flag. + if (sigfox_ep_api_ctx.irq_flags.mcu_timer2_cplt != 0) { + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_2); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_2); +#endif + sigfox_ep_api_ctx.irq_flags.mcu_timer2_cplt = 0; + // Start DL_T_RX timer. +#ifdef ERROR_CODES + status = _start_timer1(((sigfox_ep_api_ctx.rc_ptr) -> spectrum_access) -> dl_t_rx_ms); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_timer1(((sigfox_ep_api_ctx.rc_ptr) -> spectrum_access) -> dl_t_rx_ms); +#endif + // Wake-up radio. +#ifdef ERROR_CODES + status = _radio_wake_up(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _radio_wake_up(); +#endif +#ifdef ASYNCHRONOUS + // Start DL reception. +#ifdef ERROR_CODES + status = _start_reception(&rx_data); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_reception(&rx_data); +#endif +#endif /* ASYNCHRONOUS */ + // Update state. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_DL_LISTENING; + } + break; + case SIGFOX_EP_API_STATE_DL_LISTENING: +#ifndef ASYNCHRONOUS + // Wait for incoming DL frame or timeout. +#ifdef ERROR_CODES + status = _start_reception(&rx_data); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_reception(&rx_data); +#endif + // If the function exits with dl_frame_received flag set to 0, it means that the DL window timer has elapsed. + sigfox_ep_api_ctx.irq_flags.rf_rx_data_received = rx_data.data_received; + sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt = !(rx_data.data_received); +#endif /* BLOCKING */ + if (sigfox_ep_api_ctx.irq_flags.rf_rx_data_received != 0) { + // Clear flag. + sigfox_ep_api_ctx.irq_flags.rf_rx_data_received = 0; + // Decode downlink frame. +#ifdef ERROR_CODES + status = _check_dl_frame(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _check_dl_frame(); +#endif + // Restart reception if the frame authentication failed. + if ((sigfox_ep_api_ctx.dl_status) == SFX_FALSE) { +#ifdef ERROR_CODES + rf_status = RF_API_de_init(); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_de_init(); +#endif +#ifdef ASYNCHRONOUS +#ifdef ERROR_CODES + status = _start_reception(&rx_data); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_reception(&rx_data); +#endif +#endif /* ASYNCHRONOUS */ + } + } + if (((sigfox_ep_api_ctx.dl_status) == SFX_TRUE) || (sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt != 0)) { + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_1); +#endif + sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt = 0; + // Stop radio. +#ifdef ERROR_CODES + rf_status = RF_API_de_init(); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_de_init(); +#endif + // RX sequence done. +#ifdef ERROR_CODES + status = _radio_sleep(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _radio_sleep(); +#endif + // Update state. + if ((sigfox_ep_api_ctx.dl_status) == SFX_TRUE) { + // Update message status. + sigfox_ep_api_ctx.message_status.downlink_frame = 1; +#ifdef ASYNCHRONOUS + _DOWNLINK_CPLT_CALLBACK(); +#endif + // Start DL confirmation timer. +#ifdef T_CONF_MS + sigfox_ep_api_ctx.interframe_ms = T_CONF_MS; +#else + sigfox_ep_api_ctx.interframe_ms = (sigfox_ep_api_ctx.application_message_ptr) -> t_conf_ms; +#endif +#ifdef ERROR_CODES + status = _start_timer1(sigfox_ep_api_ctx.interframe_ms); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_timer1(sigfox_ep_api_ctx.interframe_ms); +#endif + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_DL_CONFIRMATION_TIMER; + } + else { + // Dowlink timeout: set error flag. + sigfox_ep_api_ctx.message_status.error = 1; +#ifdef ASYNCHRONOUS + _MESSAGE_CPLT_CALLBACK(); +#endif + // Force state to ready. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_READY; + } + } + break; + case SIGFOX_EP_API_STATE_DL_CONFIRMATION_TIMER: +#ifndef ASYNCHRONOUS + // Wait for timer completion. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_wait_cplt(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_wait_cplt(MCU_API_TIMER_1); +#endif + sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt = 1; // Set flag manually in blocking mode. +#endif + // Check timer completion flag. + if (sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt != 0) { + // Stop timer. +#ifdef ERROR_CODES + mcu_status = MCU_API_timer_stop(MCU_API_TIMER_1); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_timer_stop(MCU_API_TIMER_1); +#endif + sigfox_ep_api_ctx.irq_flags.mcu_timer1_cplt = 0; + // Configure DL confirmation message. + _message_prepare(); + sigfox_ep_api_ctx.internal_flags.control_message = 1; + sigfox_ep_api_ctx.internal_flags.ack_message = 1; +#ifndef SINGLE_FRAME + (sigfox_ep_api_ctx.common_parameters_ptr) -> number_of_frames = 1; +#endif + // Update send function pointer. + sigfox_ep_api_ctx.sending_function_ptr = &_send_dl_confirmation_frame; + // Start confirmation frame. +#ifdef ERROR_CODES + status = _start_transmission(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _start_transmission(); +#endif + } + break; +#endif + default: + // Unknown state. + EXIT_ERROR(SIGFOX_EP_API_ERROR_STATE); + } + // Return here if no error occurred. + RETURN(); +error_state: +errors: +#ifdef ERROR_CODES + // Notify error to low level drivers. + MCU_API_error(); + RF_API_error(); +#endif + // Do not set the error flag and call the message completion callback on the first process call. + if (sigfox_ep_api_ctx.internal_flags.send_message_request == 0) { + // Set error flag and call completion callback. + sigfox_ep_api_ctx.message_status.error = 1; +#ifdef ASYNCHRONOUS + _MESSAGE_CPLT_CALLBACK(); +#endif + } + // Force state to ready after error. + sigfox_ep_api_ctx.state = SIGFOX_EP_API_STATE_READY; + RETURN(); +} + +#ifdef APPLICATION_MESSAGES +/*******************************************************************/ +SIGFOX_EP_API_status_t _send_application_message(SIGFOX_EP_API_application_message_t *application_message) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif + // Check library state. + _CHECK_LIBRARY_STATE(!= SIGFOX_EP_API_STATE_READY); + // Reset message status. + sigfox_ep_api_ctx.message_status.all = 0; + // Reset IRQ flags. + sigfox_ep_api_ctx.irq_flags.all = 0; + // Store application message parameters locally. +#ifdef ERROR_CODES + status = _store_application_message(application_message); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _store_application_message(application_message); +#endif + // Reset context. + _message_prepare(); + // Set internal flags. + sigfox_ep_api_ctx.internal_flags.send_message_request = 1; + sigfox_ep_api_ctx.internal_flags.control_message = 0; + sigfox_ep_api_ctx.internal_flags.ack_message = 0; + // Set sending function pointer. + sigfox_ep_api_ctx.sending_function_ptr = &_send_application_frame; + // Trigger TX. +#ifdef ERROR_CODES + status = _internal_process(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _internal_process(); +#endif +#ifdef ASYNCHRONOUS + if (sigfox_ep_api_ctx.internal_flags.synchronous != 0) { +#endif + // Block until library goes back to READY state. + while (sigfox_ep_api_ctx.state != SIGFOX_EP_API_STATE_READY) { +#ifdef ERROR_CODES + status = _internal_process(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _internal_process(); +#endif + } +#ifdef ASYNCHRONOUS + } +#endif +#if !(defined ASYNCHRONOUS) && (defined REGULATORY) && (defined ERROR_CODES) + // Force status to error if any frame has not been sent due to TX control. + // Note: other errors are caught in the errors label. + if (sigfox_ep_api_ctx.internal_flags.tx_forbidden != 0) { + status = SIGFOX_EP_API_ERROR_TX_FORBIDDEN; + } +#endif /* REGULATORY and BLOCKING */ + // Return here if no error occurred. + sigfox_ep_api_ctx.internal_flags.send_message_request = 0; + RETURN(); +#ifdef ERROR_CODES +errors: + // Set error flag except for state error (in order to keep the message status if a message sequence is ongoing). + sigfox_ep_api_ctx.message_status.error = 1; +#endif +error_state: + // Clear request. + sigfox_ep_api_ctx.internal_flags.send_message_request = 0; + RETURN(); +} +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +/*******************************************************************/ +SIGFOX_EP_API_status_t _send_control_message(SIGFOX_EP_API_control_message_t *control_message) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif + // Check library state. + _CHECK_LIBRARY_STATE(!= SIGFOX_EP_API_STATE_READY); + // Reset message status. + sigfox_ep_api_ctx.message_status.all = 0; + // Reset IRQ flags. + sigfox_ep_api_ctx.irq_flags.all = 0; + // Store control message parameters locally. +#ifdef ERROR_CODES + status = _store_control_message(control_message); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _store_control_message(control_message); +#endif + // Reset context. + _message_prepare(); + /// Set internal flags. + sigfox_ep_api_ctx.internal_flags.send_message_request = 1; + sigfox_ep_api_ctx.internal_flags.control_message = 1; + sigfox_ep_api_ctx.internal_flags.ack_message = 0; + // Set sending function pointer. + switch (control_message -> type) { +#ifdef CONTROL_KEEP_ALIVE_MESSAGE + case SIGFOX_CONTROL_MESSAGE_TYPE_KEEP_ALIVE: + sigfox_ep_api_ctx.sending_function_ptr = &_send_control_keep_alive_frame; + break; +#endif + default: + // Downlink confirmation is not allowed. + EXIT_ERROR(SIGFOX_EP_API_ERROR_MESSAGE_TYPE); + } + // Trigger TX. +#ifdef ERROR_CODES + status = _internal_process(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _internal_process(); +#endif +#ifdef ASYNCHRONOUS + if (sigfox_ep_api_ctx.internal_flags.synchronous != 0) { +#endif + // Block until library goes back to READY state. + while (sigfox_ep_api_ctx.state != SIGFOX_EP_API_STATE_READY) { +#ifdef ERROR_CODES + status = _internal_process(); + CHECK_STATUS(SIGFOX_EP_API_SUCCESS); +#else + _internal_process(); +#endif + } +#ifdef ASYNCHRONOUS + } +#endif +#if !(defined ASYNCHRONOUS) && (defined REGULATORY) && (defined ERROR_CODES) + // Force status to error if any frame has not been sent due to TX control. + // Note: other errors are caught in the errors label. + if (sigfox_ep_api_ctx.internal_flags.tx_forbidden != 0) { + status = SIGFOX_EP_API_ERROR_TX_FORBIDDEN; + } +#endif /* REGULATORY and BLOCKING */ + // Return here if no error occurred. + sigfox_ep_api_ctx.internal_flags.send_message_request = 0; + RETURN(); +errors: + // Set error flag except for state error (in order to keep the message status if a message sequence is ongoing). + sigfox_ep_api_ctx.message_status.error = 1; +error_state: + // Clear request. + sigfox_ep_api_ctx.internal_flags.send_message_request = 0; + RETURN(); +} +#endif + +/*** SIGFOX EP API functions ***/ + +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_open(SIGFOX_EP_API_config_t *config) { + // Local variables. + sfx_u8 idx = 0; + sfx_u8 ep_id[SIGFOX_EP_ID_SIZE_BYTES]; + sfx_u8 nvm_data[SIGFOX_NVM_DATA_SIZE_BYTES]; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; + SIGFOX_EP_FREQUENCY_status_t frequency_status = SIGFOX_EP_FREQUENCY_SUCCESS; +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif +#ifdef REGULATORY + SIGFOX_TX_CONTROL_status_t tx_control_status = SIGFOX_TX_CONTROL_SUCCESS; +#endif +#endif /* ERROR_CODES */ +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) + MCU_API_config_t mcu_config; + RF_API_config_t rf_config; +#endif +#ifdef REGULATORY + SIGFOX_TX_CONTROL_config_t tx_control_config; +#endif +#if (defined MESSAGE_COUNTER_ROLLOVER) && (defined PARAMETERS_CHECK) + sfx_bool message_counter_rollover_valid = SFX_FALSE; +#endif +#ifdef ERROR_STACK + // Init error stack. + SIGFOX_ERROR_init(); + sigfox_ep_api_ctx.internal_flags.error_stack_initialized = 1; +#endif + // Check state. + _CHECK_LIBRARY_STATE(!= SIGFOX_EP_API_STATE_CLOSED); +#ifdef PARAMETERS_CHECK + // Check parameter. + if (config == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } +#endif + // Check (even if PARAMETERS_CHECK flag is disabled) and store RC. + if ((config -> rc) == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_RC); + } + sigfox_ep_api_ctx.rc_ptr = (config -> rc); +#ifdef MESSAGE_COUNTER_ROLLOVER +#ifdef PARAMETERS_CHECK + // Check message counter macro value. + for (idx=0 ; idx message_counter_rollover) >= SIGFOX_MESSAGE_COUNTER_ROLLOVER_LAST) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_MESSAGE_COUNTER_ROLLOVER); + } +#endif /* PARAMETERS_CHECK */ + // Store message counter rollover. + sigfox_ep_api_ctx.message_counter_rollover = SIGFOX_MESSAGE_COUNTER_ROLLOVER_LIST[config -> message_counter_rollover]; +#endif /* MESSAGE_COUNTER_ROLLOVER */ + // Init MCU API. +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) + mcu_config.rc = sigfox_ep_api_ctx.rc_ptr; +#ifdef ASYNCHRONOUS + mcu_config.process_cb = (MCU_API_process_cb_t) &_MCU_API_process_cb; + mcu_config.error_cb = (MCU_API_error_cb_t) &_MCU_API_error_cb; +#endif +#ifdef ERROR_CODES + mcu_status = MCU_API_open(&mcu_config); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_open(&mcu_config); +#endif +#endif + // Init RF API. +#if (defined ASYNCHRONOUS) || (defined LOW_LEVEL_OPEN_CLOSE) + rf_config.rc = sigfox_ep_api_ctx.rc_ptr; +#ifdef ASYNCHRONOUS + rf_config.process_cb = (RF_API_process_cb_t) &_RF_API_process_cb; + rf_config.error_cb = &_RF_API_error_cb; +#endif +#ifdef ERROR_CODES + rf_status = RF_API_open(&rf_config); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_open(&rf_config); +#endif +#endif +#ifdef REGULATORY + // Init TX CONTROL driver. + tx_control_config.rc = sigfox_ep_api_ctx.rc_ptr; +#ifdef ASYNCHRONOUS + tx_control_config.process_cb = &_SIGFOX_TX_CONTROL_process_cb; +#endif +#ifdef ERROR_CODES + tx_control_status = SIGFOX_TX_CONTROL_open(&tx_control_config); + SIGFOX_TX_CONTROL_check_status(SIGFOX_EP_API_ERROR_TX_CONTROL); +#else + SIGFOX_TX_CONTROL_open(&tx_control_config); +#endif +#endif /* REGULATORY */ +#ifdef ASYNCHRONOUS + // Store process callback (even if NULL). + sigfox_ep_api_ctx.process_cb = (config -> process_cb); + // Update library behavior. + sigfox_ep_api_ctx.internal_flags.synchronous = (sigfox_ep_api_ctx.process_cb == SFX_NULL) ? 1 : 0; +#endif + // Read last message counter and last random value value in NVM. +#ifdef ERROR_CODES + mcu_status = MCU_API_get_nvm(nvm_data, SIGFOX_NVM_DATA_SIZE_BYTES); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_get_nvm(nvm_data, SIGFOX_NVM_DATA_SIZE_BYTES); +#endif + sigfox_ep_api_ctx.message_counter = 0; + sigfox_ep_api_ctx.message_counter |= ((((sfx_u16) (nvm_data[SIGFOX_NVM_DATA_INDEX_MESSAGE_COUNTER_MSB])) << 8) & 0xFF00); + sigfox_ep_api_ctx.message_counter |= ((((sfx_u16) (nvm_data[SIGFOX_NVM_DATA_INDEX_MESSAGE_COUNTER_LSB])) << 0) & 0x00FF); + sigfox_ep_api_ctx.random_value = 0; + sigfox_ep_api_ctx.random_value |= ((((sfx_u16) (nvm_data[SIGFOX_NVM_DATA_INDEX_RANDOM_VALUE_MSB])) << 8) & 0xFF00); + sigfox_ep_api_ctx.random_value |= ((((sfx_u16) (nvm_data[SIGFOX_NVM_DATA_INDEX_RANDOM_VALUE_LSB])) << 0) & 0x00FF); + // Read device ID. +#ifdef ERROR_CODES + mcu_status = MCU_API_get_ep_id(ep_id, SIGFOX_EP_ID_SIZE_BYTES); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_get_ep_id(ep_id, SIGFOX_EP_ID_SIZE_BYTES); +#endif + for (idx=0 ; idx tx_frequency_hz); +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + sigfox_ep_api_ctx.test_parameters.fh_timer_enable = (test_parameters -> fh_timer_enable); +#endif +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) + sigfox_ep_api_ctx.test_parameters.lbt_enable = (test_parameters -> lbt_enable); +#endif + // Send message. +#ifdef ERROR_CODES + status = _send_application_message(application_message); +#else + _send_application_message(application_message); +#endif +errors: + RETURN(); +} +#endif + +#ifdef CONTROL_KEEP_ALIVE_MESSAGE +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_send_control_message(SIGFOX_EP_API_control_message_t *control_message) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif +#ifdef CERTIFICATION + // Disable all test parameters. + sigfox_ep_api_ctx.test_parameters.tx_frequency_hz = 0; +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + sigfox_ep_api_ctx.test_parameters.fh_timer_enable = SFX_TRUE; +#endif +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) + sigfox_ep_api_ctx.test_parameters.lbt_enable = SFX_TRUE; +#endif +#endif + // Send message. +#ifdef ERROR_CODES + status = _send_control_message(control_message); +#else + _send_control_message(control_message); +#endif + RETURN(); +} +#endif + +#if (defined CERTIFICATION) && (defined CONTROL_KEEP_ALIVE_MESSAGE) +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_TEST_send_control_message(SIGFOX_EP_API_control_message_t *control_message, SIGFOX_EP_API_TEST_parameters_t *test_parameters) { + // Local variables. + sfx_u8 idx = 0; +#ifdef ERROR_CODES + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + // Check parameters. + if (test_parameters == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } +#endif + // Check EP-ID. + for (idx=0 ; idx tx_frequency_hz); +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_FH) + sigfox_ep_api_ctx.test_parameters.fh_timer_enable = (test_parameters -> fh_timer_enable); +#endif +#if (defined REGULATORY) && (defined SPECTRUM_ACCESS_LBT) + sigfox_ep_api_ctx.test_parameters.lbt_enable = (test_parameters -> lbt_enable); +#endif + // Send message. +#ifdef ERROR_CODES + status = _send_control_message(control_message); +#else + _send_control_message(control_message); +#endif +errors: + RETURN(); +} +#endif + +#ifdef BIDIRECTIONAL +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_dl_payload(sfx_u8 *dl_payload, sfx_u8 dl_payload_size, sfx_s16 *dl_rssi_dbm) { + // Local variables. + sfx_u8 idx = 0; +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif + // Check library is opened. + _CHECK_LIBRARY_STATE(== SIGFOX_EP_API_STATE_CLOSED); +#ifdef PARAMETERS_CHECK + if ((dl_payload == SFX_NULL) || (dl_rssi_dbm == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } + if (dl_payload_size > SIGFOX_DL_PAYLOAD_SIZE_BYTES) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_DL_PAYLOAD_SIZE); + } +#endif + // Check downlink status. + if (sigfox_ep_api_ctx.message_status.downlink_frame == 0) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_DL_PAYLOAD_UNAVAILABLE); + } + // Copy local bytes into given buffer. + for (idx=0 ; idx SIGFOX_EP_ID_SIZE_BYTES) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_EP_ID); + } +#endif + // Check library is opened. + _CHECK_LIBRARY_STATE(== SIGFOX_EP_API_STATE_CLOSED); + // Read ID directly from MCU driver. +#ifdef ERROR_CODES + mcu_status = MCU_API_get_ep_id(ep_id, ep_id_size_bytes); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_get_ep_id(ep_id, ep_id_size_bytes); +#endif +error_state: +#if (defined PARAMETERS_CHECK) || (defined ERROR_CODES) +errors: +#endif + RETURN(); +} +#endif + +#ifdef VERBOSE +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_initial_pac(sfx_u8 *initial_pac, sfx_u8 initial_pac_size_bytes) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + if (initial_pac == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } + if (initial_pac_size_bytes > SIGFOX_EP_PAC_SIZE_BYTES) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_EP_PAC); + } +#endif + // Check library is opened. + _CHECK_LIBRARY_STATE(== SIGFOX_EP_API_STATE_CLOSED); + // Read PAC directly from MCU driver. +#ifdef ERROR_CODES + mcu_status = MCU_API_get_initial_pac(initial_pac, initial_pac_size_bytes); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_get_initial_pac(initial_pac, initial_pac_size_bytes); +#endif +error_state: +#if (defined PARAMETERS_CHECK) || (defined ERROR_CODES) +errors: +#endif + RETURN(); +} +#endif + +#ifdef VERBOSE +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_version(SIGFOX_version_t version_type, sfx_u8 **version, sfx_u8 *version_size_char) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; + MCU_API_status_t mcu_status = MCU_API_SUCCESS; + RF_API_status_t rf_status = RF_API_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + if ((version == SFX_NULL) || (version_size_char == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } +#endif + // Check library is opened. + _CHECK_LIBRARY_STATE(== SIGFOX_EP_API_STATE_CLOSED); + // Check version type. + switch (version_type) { + case SIGFOX_VERSION_EP_LIBRARY: + (*version) = (sfx_u8*) SIGFOX_EP_API_VERSION; + (*version_size_char) = (sfx_u8) sizeof(SIGFOX_EP_API_VERSION); + break; + case SIGFOX_VERSION_MCU_DRIVER: +#ifdef ERROR_CODES + mcu_status = MCU_API_get_version(version, version_size_char); + MCU_API_check_status(SIGFOX_EP_API_ERROR_MCU); +#else + MCU_API_get_version(version, version_size_char); +#endif + break; + case SIGFOX_VERSION_RF_DRIVER: +#ifdef ERROR_CODES + rf_status = RF_API_get_version(version, version_size_char); + RF_API_check_status(SIGFOX_EP_API_ERROR_RF); +#else + RF_API_get_version(version, version_size_char); +#endif + break; + default: + EXIT_ERROR(SIGFOX_EP_API_ERROR_VERSION); + break; + } +error_state: +errors: + RETURN(); +} +#endif + +#ifdef VERBOSE +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_get_flags(sfx_u8 **ep_flags, sfx_u8 *ep_flags_size_char) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + if ((ep_flags == SFX_NULL) || (ep_flags_size_char == SFX_NULL)) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } +#endif + (*ep_flags) = (sfx_u8*) SIGFOX_EP_API_FLAGS; + (*ep_flags_size_char) = (sfx_u8) sizeof(SIGFOX_EP_API_FLAGS); +#ifdef PARAMETERS_CHECK +errors: +#endif + RETURN(); +} +#endif + +#ifdef ERROR_STACK +/*******************************************************************/ +SIGFOX_EP_API_status_t SIGFOX_EP_API_unstack_error(SIGFOX_ERROR_t *error_ptr) { +#ifdef ERROR_CODES + // Local variables. + SIGFOX_EP_API_status_t status = SIGFOX_EP_API_SUCCESS; +#endif +#ifdef PARAMETERS_CHECK + if (error_ptr == SFX_NULL) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_NULL_PARAMETER); + } +#endif + // Check error stack has been initialized. + if (sigfox_ep_api_ctx.internal_flags.error_stack_initialized == 0) { + EXIT_ERROR(SIGFOX_EP_API_ERROR_STATE); + } + // Directly call error stack driver. + SIGFOX_ERROR_unstack(error_ptr); +errors: + RETURN(); +} +#endif diff --git a/src/sigfox_error.c b/src/sigfox_error.c new file mode 100644 index 0000000..b165919 --- /dev/null +++ b/src/sigfox_error.c @@ -0,0 +1,106 @@ +/*!***************************************************************** + * \file sigfox_error.c + * \brief Sigfox error driver. + ******************************************************************* + * \copyright + * + * Copyright (c) 2022, UnaBiz SAS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1 Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3 Neither the name of UnaBiz SAS nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************/ + +#include "sigfox_error.h" + +#ifdef USE_SIGFOX_EP_FLAGS_H +#include "sigfox_ep_flags.h" +#endif +#include "sigfox_types.h" + +#ifdef ERROR_STACK +/*** SIGFOX EP API local structures ***/ + +/*******************************************************************/ +typedef struct { + SIGFOX_ERROR_t error_stack[ERROR_STACK]; + sfx_u32 error_stack_idx; +} SIGFOX_ERROR_context_t; +#endif + +#ifdef ERROR_STACK +/*** SIGFOX EP API local global variables ***/ + +static SIGFOX_ERROR_context_t sigfox_error_ctx; +#endif + +/*** SIGFOX ERROR functions ***/ + +#ifdef ERROR_STACK +/*******************************************************************/ +void SIGFOX_ERROR_init(void) { + // Reset all errors. + for (sigfox_error_ctx.error_stack_idx=0 ; sigfox_error_ctx.error_stack_idx= ERROR_STACK) { + sigfox_error_ctx.error_stack_idx = 0; + } +} +#endif + +#ifdef ERROR_STACK +/*******************************************************************/ +void SIGFOX_ERROR_unstack(SIGFOX_ERROR_t *error_ptr) { + // Set index to last error. + if (sigfox_error_ctx.error_stack_idx > 0) { + sigfox_error_ctx.error_stack_idx--; + } + else { + sigfox_error_ctx.error_stack_idx = (ERROR_STACK - 1); + } + // Read last error. + (error_ptr -> source) = sigfox_error_ctx.error_stack[sigfox_error_ctx.error_stack_idx].source; + (error_ptr -> code) = sigfox_error_ctx.error_stack[sigfox_error_ctx.error_stack_idx].code; + // Clear error. + sigfox_error_ctx.error_stack[sigfox_error_ctx.error_stack_idx].source = SIGFOX_ERROR_SOURCE_NONE; + sigfox_error_ctx.error_stack[sigfox_error_ctx.error_stack_idx].code = 0; +} +#endif