From 325f855b8b2e0d1c6329af1987ab5cdbed0f2674 Mon Sep 17 00:00:00 2001 From: Diana-Doe Date: Sun, 18 Jul 2021 13:09:47 +0300 Subject: [PATCH 1/4] Add BLE --- .../MedButton/.vscode/c_cpp_properties.json | 77 +- firmware/MedButton/.vscode/launch.json | 34 +- firmware/MedButton/.vscode/settings.json | 2 +- firmware/MedButton/FreeRTOSConfig.h | 4 +- firmware/MedButton/Makefile | 5 +- .../MedButton/MedButton_First.code-workspace | 9 +- .../MedButton/OnethinxCore/LoRaWAN_keys.h | 6 +- firmware/MedButton/ble_task.c | 1160 +++++++++++++++++ firmware/MedButton/ble_task.h | 99 ++ firmware/MedButton/data_struct.h | 12 + firmware/MedButton/deps/bless.mtb | 1 + firmware/MedButton/main.c | 414 +++++- firmware/MedButton/openocd.tcl | 1 - 13 files changed, 1704 insertions(+), 120 deletions(-) create mode 100644 firmware/MedButton/ble_task.c create mode 100644 firmware/MedButton/ble_task.h create mode 100644 firmware/MedButton/data_struct.h create mode 100644 firmware/MedButton/deps/bless.mtb diff --git a/firmware/MedButton/.vscode/c_cpp_properties.json b/firmware/MedButton/.vscode/c_cpp_properties.json index 74b9f97..3a749e1 100644 --- a/firmware/MedButton/.vscode/c_cpp_properties.json +++ b/firmware/MedButton/.vscode/c_cpp_properties.json @@ -15,53 +15,70 @@ "-g", "-Wall" ], - "mtbGccDefaultIncludePath": [ + "mtbDefaultIncludePath": [ ".", "./OnethinxCore", "../mtb_shared/TARGET_CY8CKIT-062-BLE/latest-v2.X", - "../mtb_shared/capsense/latest-v2.X", - "../mtb_shared/core-lib/latest-v1.X", - "../mtb_shared/core-lib/latest-v1.X/include", - "../mtb_shared/freertos/latest-v10.X", - "../mtb_shared/freertos/latest-v10.X/Source", - "../mtb_shared/freertos/latest-v10.X/Source/include", - "../mtb_shared/freertos/latest-v10.X/Source/portable", - "../mtb_shared/freertos/latest-v10.X/Source/portable/TOOLCHAIN_GCC_ARM", - "../mtb_shared/freertos/latest-v10.X/Source/portable/TOOLCHAIN_GCC_ARM/CM4F", - "../mtb_shared/mtb-hal-cat1/latest-v1.X", - "../mtb_shared/mtb-hal-cat1/latest-v1.X/COMPONENT_PSOC6HAL", - "../mtb_shared/mtb-hal-cat1/latest-v1.X/COMPONENT_PSOC6HAL/include", - "../mtb_shared/mtb-hal-cat1/latest-v1.X/COMPONENT_PSOC6HAL/include/pin_packages", - "../mtb_shared/mtb-hal-cat1/latest-v1.X/COMPONENT_PSOC6HAL/include/triggers", - "../mtb_shared/mtb-hal-cat1/latest-v1.X/include", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/cmsis", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/cmsis/include", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/devices", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/devices/COMPONENT_CAT1A", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/devices/COMPONENT_CAT1A/include", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/devices/COMPONENT_CAT1A/include/ip", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/drivers", - "../mtb_shared/mtb-pdl-cat1/latest-v2.X/drivers/include", - "../mtb_shared/retarget-io/latest-v1.X", + "../mtb_shared/abstraction-rtos/release-v1.4.0", + "../mtb_shared/abstraction-rtos/release-v1.4.0/include", + "../mtb_shared/abstraction-rtos/release-v1.4.0/include/COMPONENT_FREERTOS", + "../mtb_shared/abstraction-rtos/release-v1.4.0/include/Template", + "../mtb_shared/bless/release-v3.50.0", + "../mtb_shared/bless/release-v3.50.0/common", + "../mtb_shared/capsense/release-v2.10.0", + "../mtb_shared/clib-support/release-v1.1.0", + "../mtb_shared/clib-support/release-v1.1.0/TOOLCHAIN_GCC_ARM", + "../mtb_shared/core-lib/release-v1.2.0", + "../mtb_shared/core-lib/release-v1.2.0/include", + "../mtb_shared/freertos/release-v10.3.1", + "../mtb_shared/freertos/release-v10.3.1/Source", + "../mtb_shared/freertos/release-v10.3.1/Source/include", + "../mtb_shared/freertos/release-v10.3.1/Source/portable", + "../mtb_shared/freertos/release-v10.3.1/Source/portable/COMPONENT_CM4", + "../mtb_shared/freertos/release-v10.3.1/Source/portable/COMPONENT_CM4/TOOLCHAIN_GCC_ARM", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/COMPONENT_PSOC6HAL", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/COMPONENT_PSOC6HAL/COMPONENT_CAT1A", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/COMPONENT_PSOC6HAL/COMPONENT_CAT1A/include", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/COMPONENT_PSOC6HAL/COMPONENT_CAT1A/include/pin_packages", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/COMPONENT_PSOC6HAL/COMPONENT_CAT1A/include/triggers", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/COMPONENT_PSOC6HAL/include", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/COMPONENT_PSOC6HAL/source", + "../mtb_shared/mtb-hal-cat1/release-v1.6.0/include", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/cmsis", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/cmsis/include", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/devices", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/devices/COMPONENT_CAT1A", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/devices/COMPONENT_CAT1A/include", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/devices/COMPONENT_CAT1A/include/ip", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/drivers", + "../mtb_shared/mtb-pdl-cat1/release-v2.2.0/drivers/include", + "../mtb_shared/retarget-io/release-v1.2.0", "${config:modustoolbox.toolsPath}/gcc/lib/gcc/arm-none-eabi/9.3.1/include", "${config:modustoolbox.toolsPath}/gcc/lib/gcc/arm-none-eabi/9.3.1/include-fixed", "${config:modustoolbox.toolsPath}/gcc/arm-none-eabi/include", "${config:modustoolbox.toolsPath}/gcc/arm-none-eabi/include/c++/9.3.1", - "${config:modustoolbox.toolsPath}/gcc/arm-none-eabi/include/c++/9.3.1/arm-none-eabi/thumb/v7e-m/fpv4-sp/hard", + "${config:modustoolbox.toolsPath}/gcc/arm-none-eabi/include/c++/9.3.1/arm-none-eabi/thumb/v7e-m/fpv4-sp/softfp", "${config:modustoolbox.toolsPath}/gcc/arm-none-eabi/include/c++/9.3.1/backward" ], - "mtbGccDefaultDefines": [ + "mtbDefaultDefines": [ "CY_USING_HAL", "CY_APPNAME_MedButton_First", "CY8C6347BZI_BLD53", "CY_TARGET_DEVICE=CY8C6347BZI_BLD53", "TARGET_CY8CKIT_062_BLE", "CY_TARGET_BOARD=CY8CKIT_062_BLE", + "COMPONENT_BLESS_CONTROLLER", + "COMPONENT_BLESS_HOST", + "COMPONENT_CAT1", "COMPONENT_CAT1A", "COMPONENT_CM0P_SLEEP", "COMPONENT_CM4", + "COMPONENT_CY8CKIT_062_BLE", + "COMPONENT_FREERTOS", "COMPONENT_PSOC6HAL", + "COMPONENT_RTOS_AWARE", "COMPONENT_SOFTFP", "DEBUG", "__GNUC__" @@ -79,14 +96,14 @@ "cStandard": "c99", "cppStandard": "c++11", "includePath": [ - "${mtbGccDefaultIncludePath}" + "${mtbDefaultIncludePath}" ], "browse": { "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" }, "defines": [ - "${mtbGccDefaultDefines}" + "${mtbDefaultDefines}" ] } ] diff --git a/firmware/MedButton/.vscode/launch.json b/firmware/MedButton/.vscode/launch.json index 690cedb..17d673a 100644 --- a/firmware/MedButton/.vscode/launch.json +++ b/firmware/MedButton/.vscode/launch.json @@ -37,11 +37,6 @@ "${workspaceRoot}", "${config:modustoolbox.toolsPath}/openocd/scripts/" ], - "openOCDPreConfigLaunchCommands": [ - "set PROGRAMMER kitprog3", - "set ENABLE_ACQUIRE 0", - "set ENABLE_CM0 0", - ], "configFiles": [ "openocd.tcl" ], @@ -49,22 +44,22 @@ "set mem inaccessible-by-default off", "-enable-pretty-printing", "set remotetimeout 15", - "monitor reset init", // Comment this next line out if you don't want to reload program "monitor program {./build/CY8CKIT-062-BLE/Debug/MedButton_First.hex}", "monitor reset run", "monitor sleep 200", "monitor psoc6 reset_halt sysresetreq" ], - "numberOfProcessors": 1, + "numberOfProcessors": 2, "targetProcessor": 1, // Set to 0 for the CM0+, set to 1 for the CM4 - "postStartSessionCommands": [ // Needed if runToMain is false - // Following two commands are needed to get gdb and openocd and HW all in sync. - // Or, execution context (PC, stack, registers, etc.) look like they are from before reset. - // The stepi, is a pretend instruction that does not actually do a stepi, but MUST be done - // Its a documented workaround in openocd. Do a 'monitor help' to see more info - // - // An alternative command to use is "continue" instead of the following two + "postStartSessionCommands": [ + // Needed if runToMain is false + // Following two commands are needed to get gdb and openocd and HW all in sync. + // Or, execution context (PC, stack, registers, etc.) look like they are from before reset. + // The stepi, is a pretend instruction that does not actually do a stepi, but MUST be done + // Its a documented workaround in openocd. Do a 'monitor help' to see more info + // + // An alternative command to use is "continue" instead of the following two "monitor gdb_sync", "stepi" ], @@ -108,8 +103,7 @@ ], "overrideAttachCommands": [ "set mem inaccessible-by-default off", - "-enable-pretty-printing", - "monitor halt" + "-enable-pretty-printing" ], "numberOfProcessors": 2, "targetProcessor": 1, // Set to 0 for the CM0+, set to 1 for the CM4 @@ -231,9 +225,9 @@ "servertype": "jlink", "device": "CY8C6xx7_CM0p_sect256KB_tm", "overrideLaunchCommands": [ - "monitor reset 2", // Reset via the reset pin - "monitor flash erase", "monitor reset 0", // Reset both core and the peripherals + "monitor flash erase", + "monitor reset 2", // Reset via the reset pin "quit" ], "showDevDebugOutput": false // When set to true, displays output of GDB. @@ -249,9 +243,9 @@ "servertype": "jlink", "device": "CY8C6xx7_CM0p_sect256KB_tm", "overrideLaunchCommands": [ - "monitor reset 2", // Reset via the reset pin - "-target-download", "monitor reset 0", // Reset both core and the peripherals + "-target-download", + "monitor reset 2", // Reset via the reset pin "monitor go", "quit" ], diff --git a/firmware/MedButton/.vscode/settings.json b/firmware/MedButton/.vscode/settings.json index fff1062..488df3a 100644 --- a/firmware/MedButton/.vscode/settings.json +++ b/firmware/MedButton/.vscode/settings.json @@ -10,7 +10,7 @@ //mtb// macOS : $HOME/Library/Application Support/Code/User/settings.json //mtb// Linux : $HOME/.config/Code/User/settings.json //mtb// - "modustoolbox.toolsPath": "C:/Users/maksy/ModusToolbox/tools_2.2", + "modustoolbox.toolsPath": "C:/Users/Deer/ModusToolbox/tools_2.3", "cortex-debug.armToolchainPath": "${config:modustoolbox.toolsPath}/gcc/bin", "cortex-debug.openocdPath": "${config:modustoolbox.toolsPath}/openocd/bin/openocd" } diff --git a/firmware/MedButton/FreeRTOSConfig.h b/firmware/MedButton/FreeRTOSConfig.h index c605aca..d060c78 100644 --- a/firmware/MedButton/FreeRTOSConfig.h +++ b/firmware/MedButton/FreeRTOSConfig.h @@ -71,7 +71,7 @@ #define configUSE_QUEUE_SETS 0 #define configUSE_TIME_SLICING 0 #define configENABLE_BACKWARD_COMPATIBILITY 0 -#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 3 /* Memory allocation related definitions. */ #define configSUPPORT_STATIC_ALLOCATION 1 @@ -188,7 +188,7 @@ standard names - or at least those used in the unmodified vector table. */ #define HEAP_ALLOCATION_TYPE5 (5) /* heap_5.c*/ #define NO_HEAP_ALLOCATION (0) -#define configHEAP_ALLOCATION_SCHEME (HEAP_ALLOCATION_TYPE3) +#define configHEAP_ALLOCATION_SCHEME (HEAP_ALLOCATION_TYPE4) /* Check if the ModusToolbox Device Configurator Power personality parameter * "System Idle Power Mode" is set to either "CPU Sleep" or "System Deep Sleep". diff --git a/firmware/MedButton/Makefile b/firmware/MedButton/Makefile index a60566e..82cdd11 100644 --- a/firmware/MedButton/Makefile +++ b/firmware/MedButton/Makefile @@ -79,10 +79,11 @@ VERBOSE= # ... then code in directories named COMPONENT_foo and COMPONENT_bar will be # added to the build # -COMPONENTS=FREERTOS RTOS_AWARE +COMPONENTS+=FREERTOS RTOS_AWARE BLESS_CONTROLLER BLESS_HOST # Like COMPONENTS, but disable optional code that was enabled by default. -DISABLE_COMPONENTS=BSP_DESIGN_MODUS +DISABLE_COMPONENTS= +#BSP_DESIGN_MODUS # By default the build system automatically looks in the Makefile's directory # tree for source code and builds it. The SOURCES variable can be used to diff --git a/firmware/MedButton/MedButton_First.code-workspace b/firmware/MedButton/MedButton_First.code-workspace index a19fa6c..e5afb9d 100644 --- a/firmware/MedButton/MedButton_First.code-workspace +++ b/firmware/MedButton/MedButton_First.code-workspace @@ -17,13 +17,8 @@ //mtb// macOS : $HOME/Library/Application Support/Code/User/settings.json //mtb// Linux : $HOME/.config/Code/User/settings.json //mtb// - "modustoolbox.toolsPath": "C:/Users/maksy/ModusToolbox/tools_2.2", + "modustoolbox.toolsPath": "C:/Users/Deer/ModusToolbox/tools_2.3", "cortex-debug.armToolchainPath": "${config:modustoolbox.toolsPath}/gcc/bin", - "cortex-debug.openocdPath": "${config:modustoolbox.toolsPath}/openocd/bin/openocd", - "workbench.colorCustomizations": { - "activityBar.background": "#482133", - "titleBar.activeBackground": "#642E47", - "titleBar.activeForeground": "#FCF9FA" - } + "cortex-debug.openocdPath": "${config:modustoolbox.toolsPath}/openocd/bin/openocd" } } \ No newline at end of file diff --git a/firmware/MedButton/OnethinxCore/LoRaWAN_keys.h b/firmware/MedButton/OnethinxCore/LoRaWAN_keys.h index 7169427..fcc2976 100644 --- a/firmware/MedButton/OnethinxCore/LoRaWAN_keys.h +++ b/firmware/MedButton/OnethinxCore/LoRaWAN_keys.h @@ -23,9 +23,9 @@ LoRaWAN_keys_t TTN_OTAAkeys = { .KeyType = OTAA_10x_key, .PublicNetwork = true, - .OTAA_10x.DevEui = {{ 0x00, 0xE3, 0x52, 0x07, 0xC8, 0x0A, 0x56, 0x90 }}, - .OTAA_10x.AppEui = {{ 0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x02, 0xB6, 0x3A }}, - .OTAA_10x.AppKey = {{ 0x8A, 0x9C, 0x9F, 0xAE, 0x12, 0xFE, 0x58, 0x1D, 0xFB, 0xA8, 0x41, 0x0E, 0xD9, 0x2B, 0xCC, 0x0C }} + .OTAA_10x.DevEui = {{ 0x00, 0x3A, 0xB1, 0x24, 0x7C, 0x87, 0x1A, 0x60 }}, + .OTAA_10x.AppEui = {{ 0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x03, 0x99, 0x3B }}, + .OTAA_10x.AppKey = {{ 0x3E, 0xB6, 0x2B, 0x9E, 0x19, 0x49, 0x7F, 0xFB, 0xAB, 0x85, 0xEF, 0x54, 0x10, 0xC9, 0x45, 0x85 }} }; #endif /* LORAWAN_KEYS_H */ diff --git a/firmware/MedButton/ble_task.c b/firmware/MedButton/ble_task.c new file mode 100644 index 0000000..4c1c6db --- /dev/null +++ b/firmware/MedButton/ble_task.c @@ -0,0 +1,1160 @@ +/****************************************************************************** +* File Name: ble_task.c +* +* Description: This file contains the task that initializes BLE and +* handles different BLE events. +* +* Related Document: README.md +* +******************************************************************************* +* Copyright (2020), Cypress Semiconductor Corporation. All rights reserved. +******************************************************************************* +* This software, including source code, documentation and related materials +* ("Software"), is owned by Cypress Semiconductor Corporation or one of its +* subsidiaries ("Cypress") and is protected by and subject to worldwide patent +* protection (United States and foreign), United States copyright laws and +* international treaty provisions. Therefore, you may use this Software only +* as provided in the license agreement accompanying the software package from +* which you obtained this Software ("EULA"). +* +* If no EULA applies, Cypress hereby grants you a personal, non-exclusive, +* non-transferable license to copy, modify, and compile the Software source +* code solely for use in connection with Cypress's integrated circuit products. +* Any reproduction, modification, translation, compilation, or representation +* of this Software except as specified above is prohibited without the express +* written permission of Cypress. +* +* Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress +* reserves the right to make changes to the Software without notice. Cypress +* does not assume any liability arising out of the application or use of the +* Software or any product or circuit described in the Software. Cypress does +* not authorize its products for use in any products where a malfunction or +* failure of the Cypress product may reasonably be expected to result in +* significant property damage, injury or death ("High Risk Product"). By +* including Cypress's product in a High Risk Product, the manufacturer of such +* system or application assumes all risk of such use and in doing so agrees to +* indemnify Cypress against all liability. +*******************************************************************************/ + +/****************************************************************************** + * Include header files + *****************************************************************************/ +#include "ble_task.h" +#include "cybsp.h" +#include "cyhal.h" +#include "cycfg_ble.h" +#include "string.h" +#ifdef EINK_DISPLAY_SHIELD_PRESENT +#include "display_task.h" +#endif + +/****************************************************************************** + * Macros + *****************************************************************************/ +/* GATT parameters */ +#define NOTIFY_CCCD_UUID {0x02, 0x29} +#define NOTIFY_CCCD_SIZE (02u) +#define WRITEME_CHAR_UUID {0xC7u, 0x58u, 0xCFu, 0x70u, 0xB3u, 0xAFu,\ + 0xE4u,0xADu, 0x65u, 0x44u, 0xA3u, 0x85u, 0x26u,\ + 0x7Bu,0x70u, 0xD4u} +#define WRITEME_CHAR_UUID_SIZE (16u) +#define BLE_INTERRUPT_PRIORITY (1u) +#define ENABLE (1u) +#define DISABLE (0u) +#define CONN_INTERVAL_MULTIPLIER (1.25f) +#define TARGET_NAME_LENGTH (5u) +#define AD_TYPE_COMPLETE_LOCAL_NAME (9u) +/******************************************************************************* +* Global Variables +*******************************************************************************/ +/* Variables to hold GATT notification bytes count and GATT write bytes count */ +static uint32_t gatt_write_tx_bytes; +static uint32_t notif_rx_bytes; + +/* Variable to store latest connection interval for the BLE connection */ +static float conn_interval; + +/* CCCD value to enable/disable notification */ +static uint8_t CCCD_VALUE[NOTIFY_CCCD_SIZE] = {ENABLE,DISABLE}; + +/* Flags */ +/* Indication to start/stop GATT write */ +bool gatt_write_flag = false; +/* To ignore button press before the device is connected with peer */ +bool button_flag = false; +/* To scan only once, upon first button press by user */ +bool scan_flag = false; +/* To indicate if BLE stack is busy or free */ +static bool stack_free = true; +/* Flags used to indicate when to stop discovery procedure */ +static bool notify_cccd_uuid_found; +static bool writeme_char_uuid_found; +/* To indicate display task that the device has disconnected */ +bool device_disconnect_flag = false; + +/* Variable to store discovered attribute handles of the custom service */ +static cy_ble_gatt_db_attr_handle_t gatt_notify_cccd_attrHandle, gatt_write_val_attrHandle; + +/* Constant to store server name and use during scanning */ +static const char target_name[] = "Amazfit Bip Watch"; + +/* Variable to store user LED status */ +// static led_status_t led_status = {CYBSP_LED_STATE_OFF, CYBSP_LED_STATE_OFF}; + +/* Structure used for GATT writes */ +static cy_stc_ble_gattc_write_cmd_req_t write_param_characteristic; + +/* Variable to store TX and RX throughput values */ +throughput_val_t client_throughput = {0u, 0u}; + +/* Connection handle to identify the connected peer device */ +static cy_stc_ble_conn_handle_t conn_handle; + +/* Variable to keep track of the BLE API result */ +static cy_en_ble_api_result_t ble_api_result; + +/* Variable to hold the address of the peer device */ +static cy_stc_ble_gap_bd_addr_t peer_addr; + +/* Variable to store MTU size for active BLE connection */ +static uint16_t att_mtu_size = CY_BLE_GATT_MTU; + +#ifdef EINK_DISPLAY_SHIELD_PRESENT +/* Variable used to refresh the E-ink display every 5 seconds */ +static uint8_t count_five_sec; +#endif + +/******************************************************************************* +* Function Prototypes +*******************************************************************************/ +static void ble_init(void); +static void ble_stack_event_handler(uint32_t event, void *eventParam); +static void ble_controller_interrupt_handler(void); +static void bless_interrupt_handler(void); +static void ble_initialize_gatt_write(cy_ble_gatt_db_attr_handle_t attribute_handle); +static cy_en_ble_api_result_t ble_enable_notification(cy_ble_gatt_db_attr_handle_t attribute_handle); +static cy_en_ble_api_result_t ble_disable_notification(cy_ble_gatt_db_attr_handle_t attribute_handle); +static uint8* adv_parser(uint16_t AD_type, cy_stc_ble_gapc_adv_report_param_t *scan_report, uint8 *adv_type_length); + +/******************************************************************************* +* Function Name: void task_BLE(void *pvParameters) +******************************************************************************** +* Summary: FreeRTOS task which handles the BLE activity of the application +* +* Parameters: +* void *pvParameters : Task parameter defined during task creation (unused) +* +* Return: +* None +* +*******************************************************************************/ +void task_BLE(void *pvParameters) +{ + /* Variable to store return value from FreeRTOS APIs */ + BaseType_t rtos_api_result; + + /* Variable to store BLE command received from queue */ + ble_command_type_t ble_cmd = BLE_PROCESS_EVENTS; + + /* Remove compiler warning for unused variable */ + (void)pvParameters; + + /* Initialize BLE and process any stack events */ + ble_init(); + + /* Repeatedly running part of the task */ + for(;;) + { + /* Block until a BLE command has been received over bleCmdQ */ + rtos_api_result = xQueueReceive(ble_cmdQ, &ble_cmd, portMAX_DELAY); + + /* Command has been received from bleCmdQ */ + if(rtos_api_result == pdTRUE) + { + /* Process the BLE command */ + switch(ble_cmd) + { + /* Process BLE stack events */ + case BLE_PROCESS_EVENTS: + { + Cy_BLE_ProcessEvents(); + break; + } + + /* Enable BLE notifications on GATT server device */ + case BLE_ENABLE_NOTIFICATION: + { + /* Stop the 1 second timer */ + xTimerStop(timer_handle,(TickType_t)0); + + /* Send user LED2 status to the LED queue */ + // led_status.data_led = CYBSP_LED_STATE_OFF; + // xQueueSend(led_cmdQ, &led_status, (TickType_t)0); + + /* Enable the notifications */ + if(ble_enable_notification(gatt_notify_cccd_attrHandle) == CY_BLE_SUCCESS) + { + tprintf("Notifications Enabled\r\n"); + /* Start 1 second timer to calculate throughput */ + xTimerStart(timer_handle, (TickType_t)0); +#ifdef EINK_DISPLAY_SHIELD_PRESENT + /* Notify the display task */ + xTaskNotifyGive(display_task_handle); +#endif + } + else + { + tprintf("Failed to enable notifications\r\n"); + } + break; + } + + /* Disable BLE notifications on GATT server device */ + case BLE_DISABLE_NOTIFICATION: + { + /* Stop the 1 second timer */ + xTimerStop(timer_handle,(TickType_t)0); + + /* Disable the notifications */ + if(ble_disable_notification(gatt_notify_cccd_attrHandle) == CY_BLE_SUCCESS) + { + tprintf("Notifications Disabled\r\n"); + /* Start 1 second timer to calculate throughput */ + xTimerStart(timer_handle, (TickType_t)0); +#ifdef EINK_DISPLAY_SHIELD_PRESENT + /* Notify the display task */ + xTaskNotifyGive(display_task_handle); +#endif + } + else + { + tprintf("Failed to disable notifications\r\n"); + } + + /* Initialize the structure for GATT write */ + ble_initialize_gatt_write(gatt_write_val_attrHandle); + break; + } + + /* Invalid BLE command */ + default: + { + iprintf("Invalid BLE command!"); + break; + } + } + /* If notifications are disabled and GATT write is enabled */ + if(gatt_write_flag) + { + /* Check the BLE stack status */ + if(stack_free) + { + /* Write without response into server GATT Write characteristic */ + ble_api_result = Cy_BLE_GATTC_WriteWithoutResponse(&write_param_characteristic); + if(ble_api_result == CY_BLE_SUCCESS) + { + /* Increment bytes count only on successful operation */ + gatt_write_tx_bytes += write_param_characteristic.handleValPair.value.len; + + /* Switch ON LED2 to show data TX */ + // led_status.data_led = CYBSP_LED_STATE_ON; + // xQueueSend(led_cmdQ, &led_status, (TickType_t)0); + } + else + { + /* GATT write failed. Switch OFF LED2 to show there is no data TX */ + // led_status.data_led = CYBSP_LED_STATE_OFF; + // xQueueSend(led_cmdQ, &led_status, (TickType_t)0); + } + } + else + { + /* Stack is busy. Switch OFF LED2 to show data TX stopped */ + // led_status.data_led = CYBSP_LED_STATE_OFF; + // xQueueSend(led_cmdQ, &led_status, (TickType_t)0); + } + } + } + } +} + +/******************************************************************************* +* Function Name: static void ble_init(void) +******************************************************************************** +* Summary: +* This function initializes the BLE Host and Controller, configures BLE +* interrupt, and registers Application Host callbacks. +* +* Parameters: +* None +* +* Return: +* None +* +*******************************************************************************/ +static void ble_init(void) +{ + cy_en_ble_api_result_t ble_api_result = CY_BLE_SUCCESS; + + /* BLESS interrupt configuration structure */ + const cy_stc_sysint_t bless_isr_config = + { + /* The BLESS interrupt */ + .intrSrc = bless_interrupt_IRQn, + + /* The interrupt priority number */ + .intrPriority = BLE_INTERRUPT_PRIORITY + }; + + /* Store the pointer to blessIsrCfg in the BLE configuration structure */ + cy_ble_config.hw->blessIsrConfig = &bless_isr_config; + + /* Hook interrupt service routines for BLESS */ + (void) Cy_SysInt_Init(&bless_isr_config, bless_interrupt_handler); + + /* Register the generic callback functions */ + Cy_BLE_RegisterEventCallback(ble_stack_event_handler); + + /* Register the application Host callback */ + Cy_BLE_RegisterAppHostCallback(ble_controller_interrupt_handler); + + /* Initialize the BLE */ + ble_api_result = Cy_BLE_Init(&cy_ble_config); + if(ble_api_result != CY_BLE_SUCCESS) + { + /* BLE stack initialization failed, check configuration, notify error + * and halt CPU in debug mode + */ + // eprintf("Cy_BLE_Init API, errorcode = 0x%X ", ble_api_result); + vTaskSuspend(NULL); + } + + /* Enable BLE */ + ble_api_result = Cy_BLE_Enable(); + if(ble_api_result != CY_BLE_SUCCESS) + { + /* BLE stack initialization failed, check configuration, notify error + * and halt CPU in debug mode + */ + eprintf("Cy_BLE_Enable API, errorcode = 0x%X ", ble_api_result); + vTaskSuspend(NULL); + } + /* Process BLE events after enabling BLE */ + Cy_BLE_ProcessEvents(); +} + +/******************************************************************************* +* Function Name: static void bless_interrupt_handler(void) +******************************************************************************** +* Summary: +* Wrapper function for BLESS interrupt +* +* Parameters: +* None +* +* Return: +* None +*******************************************************************************/ +static void bless_interrupt_handler(void) +{ + /* Process interrupt events generated by the BLE sub-system */ + Cy_BLE_BlessIsrHandler(); +} + + +/******************************************************************************* +* Function Name: static void ble_controller_interrupt_handler(void) +******************************************************************************** +* Summary: +* Call back event function to handle interrupts from BLE Controller +* +* Parameters: +* None +* +* Return: +* None +*******************************************************************************/ +static void ble_controller_interrupt_handler(void) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + /* Send command to process BLE events */ + ble_command_type_t bleCommand = BLE_PROCESS_EVENTS; + xQueueSendFromISR(ble_cmdQ, &bleCommand, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +/************************************************************************************ +* Function Name: static void ble_stack_event_handler(uint32_t event, void *eventParam) +************************************************************************************* +* +* Summary: Call back event function to handle various events from the BLE stack. +* +* Parameters: +* uint32_t event : event from BLE stack +* void eventParam : Pointer to the value of event specific parameters +* +* Return: +* None +************************************************************************************/ +static void ble_stack_event_handler(uint32_t event, void *eventParam) +{ + /* Take an action based on the current event */ + switch(event) + { + /*********************************************************************** + * General Events * + ***********************************************************************/ + /* This event is received when the BLE stack is Started */ + case CY_BLE_EVT_STACK_ON: + { + iprintf("BLE Stack Event: CY_BLE_EVT_STACK_ON"); + tprintf("Press button SW2 on your kit to start Scanning ...\r\n"); + + /* Wait till button press to start scanning */ + // ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + /* Start scanning for other BLE devices */ + ble_api_result = Cy_BLE_GAPC_StartScan(CY_BLE_SCANNING_FAST, CY_BLE_CENTRAL_CONFIGURATION_0_INDEX); + if( ble_api_result == CY_BLE_SUCCESS) + { + tprintf("Scanning.....\r\n"); + iprintf("BLE Start Scan API successfull"); + } + else + { + eprintf("BLE Start Scan API, errorcode = 0x%X", ble_api_result); + } + break; + } + + /* This event indicates BLE stack status. This event is used to handle + * data throttling which may occur due to continuous GATT write being + * sent */ + case CY_BLE_EVT_STACK_BUSY_STATUS: + { + /* Variable to store status of the stack */ + cy_stc_ble_l2cap_state_info_t stack_status; + stack_status = *( cy_stc_ble_l2cap_state_info_t*)eventParam; + + if(stack_status.flowState == CY_BLE_STACK_STATE_BUSY) + { + /* If stack is busy, stop GATT write */ + stack_free = false; + } + else + { + /* If stack is free, start GATT write */ + stack_free = true; + } + break; + } + + /* This event is received when there is a timeout */ + case CY_BLE_EVT_TIMEOUT: + { + iprintf("BLE Stack Event: CY_BLE_EVT_TIMEOUT"); + break; + } + + /*********************************************************************** + * Gap Events * + ***********************************************************************/ + /* This event indicates that the central device has started or stopped + * scanning */ + case CY_BLE_EVT_GAPC_SCAN_START_STOP: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GAPC_SCAN_START_STOP"); + break; + } + + /* This event is triggered every time a device is discovered */ + case CY_BLE_EVT_GAPC_SCAN_PROGRESS_RESULT: + { + cy_stc_ble_gapc_adv_report_param_t *scan_report; + scan_report = (cy_stc_ble_gapc_adv_report_param_t*)eventParam; + + /* Pointer to store return value from advertisement parser */ + char* peer_name = NULL; + uint8 name_length = 0u; + bool target_found = true; + + /* Process only for Advertisement packets, not on scan response + * packets */ + if(scan_report->eventType == CY_BLE_GAPC_SCAN_RSP) + { + /* Process the adv packets and get peer name if present in the + * packet */ + peer_name = (char*) adv_parser(AD_TYPE_COMPLETE_LOCAL_NAME, scan_report, &name_length); + if(peer_name == NULL) + { + target_found =false; + break; + } + /* Compare peer name with "TPUT" */ + else + { + peer_name[name_length]= '\0'; + tprintf("Current device is: %c \r\n", peer_name); + target_found = ((strcmp(peer_name, target_name)) ? false : true); + } + + /* If target is found stop scanning and initiate connection */ + if (target_found) + { + Cy_BLE_GAPC_StopScan(); + + /* Get address and address type of the peer device to + * initiate connection */ + for(uint8 i = 0u; i < CY_BLE_BD_ADDR_SIZE; i++) + { + peer_addr.bdAddr[i] = scan_report->peerBdAddr[i]; + } + + tprintf("Found Peer Device with address:"); + /* Print the peer bd address on UART terminal */ + for(uint8 i = (CY_BLE_BD_ADDR_SIZE); i > 0u; i--) + { + tprintf(" %X", peer_addr.bdAddr[i - 1u]); + } + + /* Get the peer address type */ + peer_addr.type = scan_report->peerAddrType; + tprintf("\r\nScan Completed\r\n"); + + /* Initiate connection with discovered peer device */ + ble_api_result = Cy_BLE_GAPC_ConnectDevice(&peer_addr, CY_BLE_CENTRAL_CONFIGURATION_0_INDEX); + if(ble_api_result == CY_BLE_SUCCESS) + { + iprintf(" SUCCESS : Connection Initiation "); + } + else + { + eprintf(" Connection Initiation API, errorcode = 0x%X", ble_api_result); + } + } + } + break; + } + + /* This event is generated at the GAP Peripheral end after connection + * is completed with peer Central device */ + case CY_BLE_EVT_GAP_DEVICE_CONNECTED: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GAP_DEVICE_CONNECTED"); + /* Variable to store connection parameters after GAP connection */ + cy_stc_ble_gap_connected_param_t* conn_param; + conn_param = (cy_stc_ble_gap_connected_param_t*)eventParam; + + /* Variable to store values to update PHY to 2M */ + cy_stc_ble_set_phy_info_t phy_pram; + phy_pram.allPhyMask = CY_BLE_PHY_NO_PREF_MASK_NONE; + phy_pram.bdHandle = conn_handle.bdHandle; + phy_pram.rxPhyMask = CY_BLE_PHY_MASK_LE_1M; + phy_pram.txPhyMask = CY_BLE_PHY_MASK_LE_1M; + + /* Reset the connection status flag upon reconnection */ + device_disconnect_flag = false; + + /* Reset the notify flag and stack_free flag after disconnection */ + gatt_write_flag = false; + stack_free = true; + + /* Store the connection interval value */ + conn_interval = (conn_param->connIntv) * CONN_INTERVAL_MULTIPLIER; + tprintf("Connection Interval is: %f ms \r\n", conn_interval); + + /* Send user LED1 status to the LED queue */ + // led_status.conn_led = CYBSP_LED_STATE_ON; + // xQueueSend(led_cmdQ, &led_status, (TickType_t)0); + + /* Function call to set PHY to 2M */ + ble_api_result = Cy_BLE_SetPhy(&phy_pram); + if(ble_api_result == CY_BLE_SUCCESS) + { + iprintf("Set PHY to 2M API successfull \r\n"); + tprintf("Request sent to switch PHY to 2M \r\n"); + } + else + { + eprintf("Set PHY API, errorcode = 0x%X", ble_api_result); + } + break; + } + + /* This event is generated when the device is disconnected from remote + * device or fails to establish connection */ + case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GAP_DEVICE_DISCONNECTED"); + + /* Stop the timer after device disconnection */ + xTimerStop(timer_handle,(TickType_t)0); + + /* Set the device disconnect flag */ + device_disconnect_flag = true; + + /* Reset the button flag to avoid any action due to button press + * after disconnection */ + button_flag = false; + + /* Change the mode flag value to GATT notifications */ + mode_flag = GATT_NOTIF_STOC; + + /* Reset the flags after disconnection */ + notify_cccd_uuid_found = false; + writeme_char_uuid_found =false; + gatt_write_flag = false; + + /* Send user LED1 and LED2 status to the LED queue */ + // led_status.conn_led = CYBSP_LED_STATE_OFF; + // led_status.data_led = CYBSP_LED_STATE_OFF; + // xQueueSend(led_cmdQ, &led_status, (TickType_t)0); + +#ifdef EINK_DISPLAY_SHIELD_PRESENT + /* Notify display task immediately to update E-ink display with + * appropriate message */ + count_five_sec = 0u; + xTaskNotifyGive(display_task_handle); +#endif + tprintf("Device Disconnected!!!\r\n"); + tprintf("Press button SW2 on your kit to start Scanning...\r\n"); + + /* Start scanning again after button press */ + scan_flag = false; + /* Wait till button press to start scanning */ + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + ble_api_result = Cy_BLE_GAPC_StartScan(CY_BLE_SCANNING_FAST, CY_BLE_CENTRAL_CONFIGURATION_0_INDEX); + if(ble_api_result == CY_BLE_SUCCESS) + { + tprintf("Scanning.....\r\n"); + iprintf("BLE Start Scan API successfull"); + } + else + { + eprintf("BLE Start Scan API, errorcode = 0x%X", ble_api_result); + } + break; + } + + /* This event is generated when PHY is updated during an active + * connection */ + case CY_BLE_EVT_PHY_UPDATE_COMPLETE: + { + iprintf("BLE Stack Event: CY_BLE_EVT_PHY_UPDATE_COMPLETE"); + + cy_stc_ble_events_param_generic_t *generic_param; + cy_stc_ble_phy_param_t *currentPHY; + generic_param = (cy_stc_ble_events_param_generic_t*)eventParam; + + /* Local variable to set MTU for the active connection */ + cy_stc_ble_gatt_xchg_mtu_param_t mtuParam = {conn_handle, CY_BLE_GATT_MTU}; + + /* GenericParam has to be cast to cy_stc_ble_phy_param_t to get + * TX and RX PHY */ + currentPHY = (cy_stc_ble_phy_param_t*)(generic_param->eventParams); + + /* Print the RX PHY selected on UART terminal */ + switch(currentPHY->rxPhyMask) + { + case CY_BLE_PHY_MASK_LE_1M: + tprintf("Selected Rx PHY: 1M\r\n"); + break; + + case CY_BLE_PHY_MASK_LE_2M: + tprintf("Selected Rx PHY: 2M\r\n"); + break; + + case CY_BLE_PHY_MASK_LE_CODED: + tprintf("Selected Rx PHY: LE Coded\r\n"); + break; + } + + /* Print the TX PHY selected on UART terminal */ + switch(currentPHY->txPhyMask) + { + case CY_BLE_PHY_MASK_LE_1M: + tprintf("Selected Tx PHY: 1M\r\n"); + break; + + case CY_BLE_PHY_MASK_LE_2M: + tprintf("Selected Tx PHY: 2M\r\n"); + break; + + case CY_BLE_PHY_MASK_LE_CODED: + tprintf("Selected Tx PHY: LE Coded\r\n"); + break; + } + + /* Initiate MTU exchange request */ + ble_api_result = Cy_BLE_GATTC_ExchangeMtuReq(&mtuParam); + if(ble_api_result == CY_BLE_SUCCESS) + { + iprintf("GATT Exchange MTU Request successfull"); + } + else + { + eprintf("GATT Exchange MTU Request API, errorcode = 0x%X", ble_api_result); + } + break; + } + + /* This event is generated when connection parameter update is + * requested. If the request is accepted by the Central, this event is + * generated on both the devices. If request is rejected, this event is + * not generated */ + case CY_BLE_EVT_GAP_CONNECTION_UPDATE_COMPLETE: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GAP_CONNECTION_UPDATE_COMPLETE"); + cy_stc_ble_gap_conn_param_updated_in_controller_t new_conn_params; + new_conn_params = *((cy_stc_ble_gap_conn_param_updated_in_controller_t*)eventParam); + + /* Store the new connection interval */ + conn_interval = (new_conn_params.connIntv) * CONN_INTERVAL_MULTIPLIER; + + tprintf("Updated Connection interval : %f ms\r\n", conn_interval); + break; + } + + /*********************************************************************** + * GATT Events * + ***********************************************************************/ + /* This event is generated at the GAP Peripheral end after connection + * is completed with peer Central device + */ + case CY_BLE_EVT_GATT_CONNECT_IND: + { + conn_handle = *((cy_stc_ble_conn_handle_t*)eventParam); + iprintf("BLE Stack Event: CY_BLE_EVT_GATT_CONNECT_IND"); + tprintf("GATT connected\r\n"); + /* Start 1 second timer to calculate throughput */ + xTimerStart(timer_handle, (TickType_t)0); + break; + } + + /* This event indicates that the GATT is disconnected.*/ + case CY_BLE_EVT_GATT_DISCONNECT_IND: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GATT_DISCONNECT_IND"); + break; + } + + /* This event is generated when response is received from GATT Server + * for MTU exchange request */ + case CY_BLE_EVT_GATTC_XCHNG_MTU_RSP: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_XCHNG_MTU_RSP"); + cy_stc_ble_gatt_xchg_mtu_param_t *mtu_xchg_resp = + (cy_stc_ble_gatt_xchg_mtu_param_t*)eventParam; + + cy_stc_ble_gattc_find_info_req_t find_req_param; + find_req_param.connHandle = conn_handle; + /* 0x0001 - 0xFFFF is the range of attribute handles that can be present in a GATT database*/ + find_req_param.range.startHandle = 0x01; + find_req_param.range.endHandle = 0xFFFF; + + tprintf("Negotiated MTU size: %d\r\n", mtu_xchg_resp->mtu); + if(mtu_xchg_resp->mtu > CY_BLE_GATT_MTU) + { + att_mtu_size = CY_BLE_GATT_MTU; + } + else + { + att_mtu_size = mtu_xchg_resp->mtu; + } + + cy_stc_ble_gattc_read_by_type_req_t param; + param.range.startHandle=0x0063; + param.range.endHandle=0x0064; + param.connHandle=conn_handle; + param.uuidFormat=CY_BLE_GATT_16_BIT_UUID_FORMAT; + param.uuid.uuid16=0x2A37; + ble_api_result= Cy_BLE_GATTC_DiscoverCharacteristicByUuid(¶m); + if(ble_api_result == CY_BLE_SUCCESS) + { + tprintf("ReadCharacteristic value success \r\n"); + } + else + { + tprintf("ReadCharacteristicValue, errorcode = 0x%X \r\n", ble_api_result); + } + break; + } + + /* This event indicates that the 'Find Information Response' is received + * from GATT Server device */ + case CY_BLE_EVT_GATTC_FIND_INFO_RSP: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_FIND_INFO_RSP"); + + cy_stc_ble_gattc_find_info_rsp_param_t find_info_param; + + find_info_param = *((cy_stc_ble_gattc_find_info_rsp_param_t*)eventParam); + int index = 0u; + uint8_t notify_cccd_uuid[NOTIFY_CCCD_SIZE] = NOTIFY_CCCD_UUID; + uint8_t writeme_char_uuid[WRITEME_CHAR_UUID_SIZE] = WRITEME_CHAR_UUID; + + /* Search for 'WriteMe' characteristic UUID and get the attribute + * handle */ + if(!writeme_char_uuid_found) + { + for (index = 0u; index < WRITEME_CHAR_UUID_SIZE; index++) + { + writeme_char_uuid_found =(find_info_param.handleValueList.list[index + 2] + != writeme_char_uuid[index]) ? false : true; + } + if(writeme_char_uuid_found) + { + gatt_write_val_attrHandle= find_info_param.handleValueList.list[0] + | (find_info_param.handleValueList.list[1] << 8 ); + iprintf("Att Handle of Custom characteristic: 0x%X", + gatt_write_val_attrHandle); + } + } + + /* Search for the CCCD UUID (0x2902) to identify the the CCCD + * attribute and get the handle */ + if(!notify_cccd_uuid_found) + { + for(index = 0u; index < NOTIFY_CCCD_SIZE; index++) + { + notify_cccd_uuid_found = (find_info_param.handleValueList.list[index + 2] != + notify_cccd_uuid[index]) ? false : true; + } + if(notify_cccd_uuid_found) + { + gatt_notify_cccd_attrHandle= find_info_param.handleValueList.list[0] + | (find_info_param.handleValueList.list[1] << 8 ); + iprintf("Att Handle of Custom characteristic: 0x%X",gatt_notify_cccd_attrHandle); + } + } + + + /* If both the UUIDs are found, stop discovering characteristics */ + if((notify_cccd_uuid_found == true) && (writeme_char_uuid_found == true)) + { + cy_stc_ble_gattc_stop_cmd_param_t stop_cmd; + stop_cmd.connHandle = conn_handle; + Cy_BLE_GATTC_StopCmd(&stop_cmd); + } + break; + } + + case CY_BLE_EVT_GATTC_READ_RSP: + { + cy_stc_ble_gattc_read_rsp_param_t read_info_param; + read_info_param = *((cy_stc_ble_gattc_read_rsp_param_t*)eventParam); + tprintf("Characteristick value is = 0x%X \r\n", read_info_param.value.val); + break; + } + + case CY_BLE_EVT_GATTC_READ_BY_TYPE_RSP: + { + cy_stc_ble_gattc_read_by_type_rsp_param_t read_info_param; + read_info_param = *((cy_stc_ble_gattc_read_by_type_rsp_param_t*)eventParam); + tprintf("Characteristick value is = 0x%X \r\n", read_info_param.attrData.attrValue); + break; + } + + /* This event indicates that the GATT long procedure has ended and the + * BLE Stack will not send any further requests to the peer */ + case CY_BLE_EVT_GATTC_LONG_PROCEDURE_END: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_LONG_PROCEDURE_END"); + break; + } + + /* This event is received by the GATT Client when the GATT Server cannot + * perform the requested operation and sends out an error response */ + case CY_BLE_EVT_GATTC_ERROR_RSP: + { + cy_stc_ble_gatt_err_param_t error_rsp; + error_rsp = *((cy_stc_ble_gatt_err_param_t*)eventParam); + iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_ERROR_RSP"); + /* To suppress compiler warning */ + (void)error_rsp; + break; + } + + /* This event indicates that a GATT group procedure has stopped or + * completed. This event occurs only if the application has called the + * Cy_BLE_GATTC_StopCmd() function. */ + case CY_BLE_EVT_GATTC_STOP_CMD_COMPLETE: + { + iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_STOP_CMD_COMPLETE"); + tprintf("Attributes discovery complete\r\n"); + + /* Enable notification for the custom throughput service */ + ble_enable_notification(gatt_notify_cccd_attrHandle); +#ifdef EINK_DISPLAY_SHIELD_PRESENT + /* Notify the display task */ + xTaskNotifyGive(display_task_handle); +#endif + break; + } + + /* This event indicates that the 'Write Response' is received from GATT + * Server device. */ + case CY_BLE_EVT_GATTC_WRITE_RSP: + { + button_flag = true; + iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_WRITE_RSP"); + break; + } + + /* This event indicates that Notification data is received from GATT + * Server device. */ + case CY_BLE_EVT_GATTC_HANDLE_VALUE_NTF: + { + cy_stc_ble_gattc_handle_value_ntf_param_t *notif_parameter; + notif_parameter = (cy_stc_ble_gattc_handle_value_ntf_param_t*)eventParam; + + /* Increment the notification byte count */ + notif_rx_bytes += notif_parameter->handleValPair.value.len; + break; + } + + /*********************************************************************** + * L2CAP Events * + ***********************************************************************/ + /* This event indicates that the connection parameter update request + * is receivedfrom the remote device. */ + case CY_BLE_EVT_L2CAP_CONN_PARAM_UPDATE_REQ: + { + cy_stc_ble_gap_conn_update_param_info_t* conn_param; + conn_param = (cy_stc_ble_gap_conn_update_param_info_t*)eventParam; + + cy_stc_ble_l2cap_conn_update_param_rsp_info_t conn_param_update_rsp + = {true, conn_handle.bdHandle}; + + /* Check minimum and maximum connection interval requested by + * peripheral device */ + tprintf("Connection Interval Update request received\r\n"); + if((conn_param->connIntvMin >= 54u) && (conn_param->connIntvMax <= 60u)) + { + conn_param_update_rsp.result = false; + tprintf("Connection interval update request accepted\r\n"); + Cy_BLE_L2CAP_LeConnectionParamUpdateResponse(&conn_param_update_rsp); + } + else + { + conn_param_update_rsp.result = true; + tprintf("Connection interval update request rejected\r\n"); + Cy_BLE_L2CAP_LeConnectionParamUpdateResponse(&conn_param_update_rsp); + } + break; + } + /*********************************************************************** + * Other Events * + ***********************************************************************/ + default: + { + iprintf("Other BLE event: 0x%X", (uint32_t)event); + break; + } + } +} + +/*********************************************************************************************************** +* Function Name: cy_en_ble_api_result_t ble_enable_notification(cy_ble_gatt_db_attr_handle_t attribute_handle) +************************************************************************************************************ +* +* Summary: This function writes to the CCCD of custom characteristic +* 'GATT Notify' and enables notifications. +* +* Parameters: +* cy_ble_gatt_db_attr_handle_t attribute_handle : peer CCCD attribute details +* +* Return: +* cy_en_ble_api_result_t : error codes received as API result +* +***********************************************************************************************************/ +static cy_en_ble_api_result_t ble_enable_notification(cy_ble_gatt_db_attr_handle_t attribute_handle) +{ + cy_stc_ble_gattc_write_req_t write_param_notification; + + write_param_notification.connHandle = conn_handle; + write_param_notification.handleValPair.attrHandle = attribute_handle; + write_param_notification.handleValPair.value.val = CCCD_VALUE; + write_param_notification.handleValPair.value.len = NOTIFY_CCCD_SIZE; + + /* Clear TX/RX data byte count */ + gatt_write_tx_bytes = 0u; + notif_rx_bytes = 0u; + + /* Enable notification */ + ble_api_result = Cy_BLE_GATTC_WriteCharacteristicValue(&write_param_notification); + + return(ble_api_result); +} + +/************************************************************************************************************ +* Function Name: cy_en_ble_api_result_t ble_disable_notification(cy_ble_gatt_db_attr_handle_t attribute_handle) +************************************************************************************************************* +* +* Summary: This function writes to the CCCD of custom characteristic +* 'GATT Notify' and disables notifications. +* +* Parameters: +* cy_ble_gatt_db_attr_handle_t attribute_handle : peer CCCD attribute details +* +* Return: +* cy_en_ble_api_result_t : error codes received as API result +* +************************************************************************************************************/ +static cy_en_ble_api_result_t ble_disable_notification(cy_ble_gatt_db_attr_handle_t attribute_handle) +{ + cy_stc_ble_gattc_write_req_t write_param_notification; + + write_param_notification.connHandle = conn_handle; + write_param_notification.handleValPair.attrHandle = attribute_handle; + write_param_notification.handleValPair.value.val = &CCCD_VALUE[1]; + write_param_notification.handleValPair.value.len = NOTIFY_CCCD_SIZE; + + /* Clear TX/RX data byte count */ + gatt_write_tx_bytes = 0u; + notif_rx_bytes = 0u; + + /* Disable Notification */ + ble_api_result = Cy_BLE_GATTC_WriteCharacteristicValue(&write_param_notification); + + return(ble_api_result); +} + +/****************************************************************************************** +* Function Name: void ble_initialize_gatt_write(cy_ble_gatt_db_attr_handle_t attribute_handle) +******************************************************************************************* +* +* Summary: This function stores the attribute handle and custom data for GATT +* write. +* +* Parameters: +* cy_ble_gatt_db_attr_handle_t attribute_handle : peer attribute details +* +* Return: +* None +* +******************************************************************************************/ +static void ble_initialize_gatt_write(cy_ble_gatt_db_attr_handle_t attribute_handle) +{ + /* Variable to hold custom data to write into server GATT database. + * Size of the array is derived from MTU exchanged. + * Packet Length = (ATT_MTU - ATT_OPCODE(1 byte) - ATT_HANDLE(2 bytes)) */ + uint8_t custom_data_length = (att_mtu_size -3u); + uint8_t custom_write_data[CY_BLE_GATT_MTU] = {0}; + + cy_stc_ble_gatt_value_t writeValue = {custom_write_data, custom_data_length, custom_data_length}; + + write_param_characteristic.connHandle = conn_handle; + write_param_characteristic.handleValPair.attrHandle = attribute_handle; + write_param_characteristic.handleValPair.value = writeValue; +} + +/*************************************************************************************************************************** +* Function Name: uint8* adv_parser(uint16_t AD_type,cy_stc_ble_gapc_adv_report_param_t* scan_report, uint8* adv_type_length) +**************************************************************************************************************************** +* +* Summary: This function searches adv packets for the given type. +* +* Parameters: +* uint16_t AD_type : the type of value to be discovered +* cy_stc_ble_gapc_adv_report_param_t* scan_report : advertisement report +* parameter +* uint8* adv_type_length : length of discovered value +* +* Return: +* uint8* : Pointer to the value discovered in ADV packet +* +***************************************************************************************************************************/ +static uint8* adv_parser(uint16_t AD_type, cy_stc_ble_gapc_adv_report_param_t* + scan_report, uint8* adv_type_length) +{ + uint8 length =0u; + uint8* pName = NULL; + + for(uint8 i=0u; idataLen; i+=length+1) + { + length = scan_report->data[i]; + if(scan_report->data[i+1] == AD_type) + { + pName = & scan_report->data[i+2]; + *adv_type_length = length-1; + return pName; + } + } + return ((uint8*)NULL); +} + +/******************************************************************************* +* Function Name: void rtos_timer_cb(TimerHandle_t timer_handle) +******************************************************************************** +* Summary: This is a freeRTOS Software timer callback function. It calculates +* the throughput and displays the value over UART terminal. +* +* Parameters: +* TimerHandle_t timer_handle: parameter defined during timer creation (unused) +* +* Return: +* None +*******************************************************************************/ +void rtos_timer_cb(TimerHandle_t timer_handle) +{ + /* Avoid warning for unused parameter */ + (void)timer_handle; + + client_throughput.rx = 0u; + client_throughput.tx = 0u; + + /* Calculate TX and RX throughput + * + * throughput(kbps) = (number of bytes sent/received in 1 second) * 8(bits) + * ----------------------------------------------------- + * 1 second * 2^10 (kilo) + */ + + if(notif_rx_bytes != 0u) + { + /* Number of bytes */ + client_throughput.rx = (notif_rx_bytes) >> 7u; + notif_rx_bytes = 0u; + tprintf("GATT NOTIFICATION: Client Throughput Rx = %lu kbps\r\n", client_throughput.rx); + } + + if(gatt_write_tx_bytes != 0u) + { + client_throughput.tx = (gatt_write_tx_bytes) >> 7u; + gatt_write_tx_bytes = 0u; + tprintf("GATT WRITE: Client Throughput Tx = %lu kbps\r\n", client_throughput.tx); + + } + +#ifdef EINK_DISPLAY_SHIELD_PRESENT + count_five_sec++; + if(count_five_sec == 5u) + { + /* Notify the display task */ + xTaskNotifyGive(display_task_handle); + count_five_sec = 0u; + } +#endif +} + +/******************************************************************************* +* Function Name: throughput_val_t* get_throughput(void) +******************************************************************************** +* Summary: This function returns the pointer to the tx and rx throughput values. +* +* Parameters: +* None +* +* Return: +* throughput_val_t* gatt_throughput: BLE gatt throughput calculated in ble task. +*******************************************************************************/ +throughput_val_t* get_throughput(void) +{ + return(&client_throughput); +} +/* [] END OF FILE */ diff --git a/firmware/MedButton/ble_task.h b/firmware/MedButton/ble_task.h new file mode 100644 index 0000000..f088c2f --- /dev/null +++ b/firmware/MedButton/ble_task.h @@ -0,0 +1,99 @@ +/****************************************************************************** +* File Name: ble_task.h +* +* Description: This file is public interface of ble_task.c source file +* +* Related Document: Readme.md +* +******************************************************************************* +* Copyright (2020), Cypress Semiconductor Corporation. All rights reserved. +******************************************************************************* +* This software, including source code, documentation and related materials +* ("Software"), is owned by Cypress Semiconductor Corporation or one of its +* subsidiaries ("Cypress") and is protected by and subject to worldwide patent +* protection (United States and foreign), United States copyright laws and +* international treaty provisions. Therefore, you may use this Software only +* as provided in the license agreement accompanying the software package from +* which you obtained this Software ("EULA"). +* +* If no EULA applies, Cypress hereby grants you a personal, non-exclusive, +* non-transferable license to copy, modify, and compile the Software source +* code solely for use in connection with Cypress's integrated circuit products. +* Any reproduction, modification, translation, compilation, or representation +* of this Software except as specified above is prohibited without the express +* written permission of Cypress. +* +* Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress +* reserves the right to make changes to the Software without notice. Cypress +* does not assume any liability arising out of the application or use of the +* Software or any product or circuit described in the Software. Cypress does +* not authorize its products for use in any products where a malfunction or +* failure of the Cypress product may reasonably be expected to result in +* significant property damage, injury or death ("High Risk Product"). By +* including Cypress's product in a High Risk Product, the manufacturer of such +* system or application assumes all risk of such use and in doing so agrees to +* indemnify Cypress against all liability. +******************************************************************************/ + +/****************************************************************************** + * Include guard + *****************************************************************************/ +#ifndef RTOS_FILES_BLE_TASK_H_ +#define RTOS_FILES_BLE_TASK_H_ + +/****************************************************************************** + * Include header files + *****************************************************************************/ +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include "timers.h" + +/****************************************************************************** + * Structures and Enumerations + *****************************************************************************/ +/* Enumeration for BLE command types */ +typedef enum +{ + BLE_PROCESS_EVENTS, + BLE_ENABLE_NOTIFICATION, + BLE_DISABLE_NOTIFICATION +}ble_command_type_t; + +/* Enumeration for data transfer modes */ +typedef enum +{ + GATT_NOTIF_STOC =0, + GATT_WRITE_CTOS, + GATT_WRITE_AND_NOTIF +}tx_rx_mode; + +/* Structure to hold TX and RX throughput value */ +typedef struct +{ + uint32_t tx; + uint32_t rx; +}throughput_val_t; + +/****************************************************************************** + * External global references + *****************************************************************************/ +extern QueueHandle_t ble_cmdQ; +extern TaskHandle_t ble_task_handle; +extern TimerHandle_t timer_handle; +extern tx_rx_mode mode_flag; +extern bool gatt_write_flag; +extern bool button_flag; +extern bool scan_flag; +extern bool device_disconnect_flag; + +/****************************************************************************** + * Function prototypes + *****************************************************************************/ +void task_BLE(void *pvParameters); +void rtos_timer_cb(TimerHandle_t); +throughput_val_t* get_throughput(void); +#endif /* RTOS_FILES_BLE_TASK_H_ */ +/* [] END OF FILE */ diff --git a/firmware/MedButton/data_struct.h b/firmware/MedButton/data_struct.h new file mode 100644 index 0000000..e30bfb3 --- /dev/null +++ b/firmware/MedButton/data_struct.h @@ -0,0 +1,12 @@ +#ifndef C_DATA_STRUCT_H +#define C_DATA_STRUCT_H + +struct message_struct { + float latitude; + float longitude; + char resultTime[11]; +}; + +typedef struct message_struct message_struct; + +#endif //C_DATA_STRUCT_H \ No newline at end of file diff --git a/firmware/MedButton/deps/bless.mtb b/firmware/MedButton/deps/bless.mtb new file mode 100644 index 0000000..f3b26c8 --- /dev/null +++ b/firmware/MedButton/deps/bless.mtb @@ -0,0 +1 @@ +https://github.com/cypresssemiconductorco/bless#release-v3.50.0#$$ASSET_REPO$$/bless/release-v3.50.0 diff --git a/firmware/MedButton/main.c b/firmware/MedButton/main.c index b82beea..a7d2e25 100644 --- a/firmware/MedButton/main.c +++ b/firmware/MedButton/main.c @@ -1,11 +1,21 @@ #include "cyhal.h" #include "cybsp.h" -#include "FreeRTOS.h" -#include "task.h" #include "cy_retarget_io.h" #include "OnethinxCore01.h" #include "LoRaWAN_keys.h" +#include "cy_pdl.h" +#include +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include "semphr.h" +#include "data_struct.h" +message_struct message_str; + +SemaphoreHandle_t semaphore_gprs; +SemaphoreHandle_t semaphore_lora; + coreStatus_t coreStatus; coreInfo_t coreInfo; @@ -14,6 +24,10 @@ uint8_t TXbuffer[64]; #define LED_BLUE (P12_4) #define LED_RED (P12_5) +#define GPRS_SETUP (P9_2) + +#define TASK (256u) +#define TASK_GPRS (512u) coreConfiguration_t coreConfig = { .Join.KeysPtr = &TTN_OTAAkeys, @@ -28,6 +42,51 @@ coreConfiguration_t coreConfig = { .TX.FPort = 1, }; +void task_gps(void* param); +void task_gprs(void* param); +void lora_send(void* param); +static void gpio_interrupt_handler(void *handler_arg, cyhal_gpio_event_t event); + +float latitude, longitude; +char resultTime[11]; +char resultMessage[100]; +uint8_t * castedMessage; + +const cyhal_uart_cfg_t uart_config = +{ + .data_bits = 8, + .stop_bits = 1, + .parity = CYHAL_UART_PARITY_NONE, + .rx_buffer = NULL, + .rx_buffer_size = 0, +}; + +cyhal_uart_t gprs_uart; +uint8_t rx_buf[64]; + +const cyhal_uart_cfg_t uart_config_gprs = +{ + .data_bits = 8, + .stop_bits = 1, + .parity = CYHAL_UART_PARITY_NONE, + .rx_buffer = rx_buf, + .rx_buffer_size = 64, +}; + +char disable_nmea[10][30] = { + "$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n", + "$PUBX,40,ZDA,0,0,0,0,0,0*44\r\n", + "PUBX,40,VTG,0,0,0,0,0,0*5E\r\n", + "PUBX,40,GSV,0,0,0,0,0,0*59\r\n", + "$PUBX,40,GSA,0,0,0,0,0,0*4E\r\n", + "$PUBX,40,RMC,0,0,0,0,0,0*47\r\n", + "$PUBX,40,GNS,0,0,0,0,0,0*41\r\n", + "$PUBX,40,GRS,0,0,0,0,0,0*5D\r\n", + "$PUBX,40,GST,0,0,0,0,0,0*5B\r\n", + "$PUBX,40,TXT,0,0,0,0,0,0*43\r\n", +}; + + int main(void) { cy_rslt_t result; @@ -44,13 +103,21 @@ int main(void) /* Enable global interrupts */ __enable_irq(); - /* Initialize retarget-io to use the debug UART port */ - result = cy_retarget_io_init(P10_1, P10_0, CY_RETARGET_IO_BAUDRATE); + + /* Initialize the user button */ + result = cyhal_gpio_init(P0_4, CYHAL_GPIO_DIR_INPUT, CYHAL_GPIO_DRIVE_NONE, CYBSP_BTN_OFF); + if (result != CY_RSLT_SUCCESS) + { + // printf("Failed to init button!\n"); + } + + /* Configure GPIO interrupt */ + cyhal_gpio_register_callback(P0_4, gpio_interrupt_handler, NULL); + cyhal_gpio_enable_event(P0_4, CYHAL_GPIO_IRQ_RISE, CYHAL_ISR_PRIORITY_DEFAULT, true); CY_ASSERT(CY_RSLT_SUCCESS == cyhal_gpio_init(LED_BLUE, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, false)); CY_ASSERT(CY_RSLT_SUCCESS == cyhal_gpio_init(LED_RED, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, false)); - printf("Test printf\n"); /* retarget-io init failed. Stop program execution */ if (result != CY_RSLT_SUCCESS) @@ -58,59 +125,298 @@ int main(void) CY_ASSERT(0); } + + semaphore_lora = xSemaphoreCreateBinary(); + semaphore_gprs = xSemaphoreCreateBinary(); + + xTaskCreate(lora_send, "LoRa", TASK , &message_str, 2, NULL); + xTaskCreate(task_gprs, "GPRS Task", TASK_GPRS , &message_str, 2, NULL); + xTaskCreate(task_gps, "GPS Task", TASK, &message_str, 1, NULL); + + vTaskStartScheduler(); + + CY_ASSERT(0); + +} + + +static void gpio_interrupt_handler(void *handler_arg, cyhal_gpio_irq_event_t event) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE;; + xSemaphoreGiveFromISR(semaphore_gprs, xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +float NMEAtoDecimalDegrees(const char *degree, char quadrant) +{ + // nmea format: "ddmm.mmmm" or "dddmm.mmmm" to decimal degrees + // D+M/60 + float result = 0; + if (strlen(degree) > 5) + { + char integerPart[3 + 1]; + int counter = (degree[4] == '.' ? 2 : 3); + memcpy(integerPart, degree, counter); + integerPart[counter] = 0; + degree += counter; + result = atoi(integerPart) + atof(degree) / 60.; + if (quadrant == 'W' || quadrant == 'S') + result = -result; + } + return result; +} + +char *UTCtoKyivTime(const char *utcTime, message_struct *message_data) +{ + // 172814.0 - hhmmss.ss + int i, digit, number = 0; + char c; + for (i = 0; i < 2; i++) + { + c = utcTime[i]; + if (c >= '0' && c <= '9') //to confirm it's a digit + { + digit = c - '0'; + number = number * 10 + digit; + } + } + number = (number + 2) % 24; + sprintf(message_data->resultTime, "%d:%c%c:%c%c", number, utcTime[2], utcTime[3], utcTime[4], utcTime[5]); + return 0; +} +uint8_t rx_buf[64]; +size_t rx_length = 64; + +void wait_uart_free(cyhal_uart_t *uart_obj) +{ + while (cyhal_uart_is_rx_active(uart_obj)) { cyhal_system_delay_ms(1); } + while (cyhal_uart_is_tx_active(uart_obj)) { cyhal_system_delay_ms(1); } +} + +void uart_send_cmd_and_wait(char *cmd, size_t cmd_len, cyhal_uart_t *uart_obj) +{ + cy_rslt_t result; + wait_uart_free(uart_obj); + if (cmd_len > 1) + { + result = cyhal_uart_write_async(uart_obj, cmd, cmd_len); + } + else + { + result = cyhal_uart_putc(uart_obj, cmd[0]); + } + + if (CY_RSLT_SUCCESS != result) + { + while(1) + { + cyhal_gpio_toggle(LED_RED); + cyhal_system_delay_ms(100); + } + } + + wait_uart_free(uart_obj); + bool data_received = false; + while(cyhal_uart_readable(uart_obj) != 0) + { + data_received = true; + uint8_t received_char; + if (CY_RSLT_SUCCESS == cyhal_uart_getc(uart_obj, &received_char, 1)) + { + //printf("%c", received_char); + } + } + if (data_received) + { + //printf("\n"); + } + cyhal_system_delay_ms(3000); +} + +static void gprs_setup(void) { + cy_rslt_t rslt; + rslt = cyhal_gpio_init(GPRS_SETUP, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, false); + + if (rslt != CY_RSLT_SUCCESS) { + //printf("Not powered\r\n"); + } + + cyhal_gpio_write(GPRS_SETUP, false); + cyhal_system_delay_ms(1000); + cyhal_gpio_write(GPRS_SETUP, true); + cyhal_system_delay_ms(2000); + cyhal_gpio_write(GPRS_SETUP, false); + cyhal_system_delay_ms(3000); +} + +void task_gprs(void* param) { + /* uart GPRS */ + message_struct *message_data = (message_struct*) param; + + cy_rslt_t result; + + cyhal_uart_t gprs_uart; + + result = cyhal_uart_init(&gprs_uart, P9_1, P9_0, NULL, &uart_config_gprs); + + if (result == CY_RSLT_SUCCESS) + { + cyhal_uart_set_baud(&gprs_uart, 115200, NULL); + } + + gprs_setup(); + + char message[100]; + while (1) { + if (xSemaphoreTake(semaphore_gprs, 1000)) { + sprintf(message, "%s-%f,%f", message_data->resultTime, message_data->latitude, message_data->longitude); + char at_cmd[] = "AT\r"; + uart_send_cmd_and_wait(at_cmd, sizeof(at_cmd), &gprs_uart); + char sms_config_cmd[] = "AT+CMGF=1\r"; + uart_send_cmd_and_wait(sms_config_cmd, sizeof(sms_config_cmd), &gprs_uart); + char sms_prepare_cmd[] = "AT+CMGS=\"+380958957865\"\r"; + uart_send_cmd_and_wait(sms_prepare_cmd, sizeof(sms_prepare_cmd), &gprs_uart); + cyhal_uart_clear(&gprs_uart); + uart_send_cmd_and_wait(message, sizeof(message), &gprs_uart); + char sms_stop_cmd[] = { (char)26 }; + uart_send_cmd_and_wait(sms_stop_cmd, 1, &gprs_uart); + } + } +} + + +void lora_send(void* param) { + message_struct * message_data = (message_struct*) param; + coreStatus = LoRaWAN_Init(&coreConfig); - /* Check Onethinx Core info */ - LoRaWAN_GetInfo(&coreInfo); + // /* Check Onethinx Core info */ + LoRaWAN_GetInfo(&coreInfo); /* send join using parameters in coreConfig, blocks until either success or MAXtries */ - coreStatus = LoRaWAN_Join(true); - - /* check for successful join */ - if (!coreStatus.mac.isJoined){ - while(1) { - cyhal_gpio_toggle(LED_BLUE); - CyDelay(100); - } - } else { - printf("Joined network successfully!\n"); - cyhal_gpio_write(LED_BLUE, false); - /*delay before first message will be sent */ - CyDelay(1000); - } - - /* main loop */ - for(;;) - { - cyhal_gpio_write(LED_BLUE, true); - - /* compose a message to send */ - uint8_t j=0; - TXbuffer[j++] = 0x48; /* H */ - TXbuffer[j++] = 0x45; /* E */ - TXbuffer[j++] = 0x4c; /* L */ - TXbuffer[j++] = 0x4c; /* L */ - TXbuffer[j++] = 0x4f; /* O */ - TXbuffer[j++] = 0x20; /* */ - TXbuffer[j++] = 0x57; /* W */ - TXbuffer[j++] = 0x4f; /* O */ - TXbuffer[j++] = 0x52; /* R */ - TXbuffer[j++] = 0x4c; /* L */ - TXbuffer[j++] = 0x44; /* D */ - coreStatus = LoRaWAN_Send((uint8_t *) TXbuffer, j, true); - CyDelay(1000); - if( coreStatus.system.errorStatus == system_BusyError ){ - for(int i=0; i<10; i++){ - cyhal_gpio_toggle(LED_BLUE);; - CyDelay(100); - } + coreStatus = LoRaWAN_Join(true); + + // /* check for successful join */ + if (!coreStatus.mac.isJoined){ + // while(1) { + cyhal_gpio_toggle(LED_BLUE); + cyhal_system_delay_ms(100); + // } + } else { + cyhal_gpio_write(LED_BLUE, true); + /*delay before first message will be sent */ + cyhal_system_delay_ms(1000); + } + + char message[50]; + while(1) { + if(xSemaphoreTake(semaphore_lora, 1000)) { + sprintf(message, "%s-%f,%f", message_data->resultTime, message_data->latitude, message_data->longitude); + coreStatus = LoRaWAN_Send((uint8_t *) message, 64, true); + cyhal_system_delay_ms(1000); // vTaskDelay + if( coreStatus.system.errorStatus == system_BusyError ){ + for(int i=0; i<10; i++){ + cyhal_gpio_toggle(LED_BLUE);; + cyhal_system_delay_ms(100); + } + } } - else + } +} + +uint8_t is_float(const char *check) { + int len; + float ignore; + int ret = sscanf(check, "%f %n", &ignore, &len); + return (ret==1 && !check[len]); +} + +void task_gps(void* param) { + /* uart GPS */ + message_struct *message_data = (message_struct*) param; + message_data->longitude = 0.0; + message_data->latitude = 0.0; + + cy_rslt_t result; + + cyhal_uart_t gps_uart; + + result = cyhal_uart_init(&gps_uart, P10_1, P10_0, NULL, &uart_config); + + if (result == CY_RSLT_SUCCESS) + { + result = cyhal_uart_set_baud(&gps_uart, 9600, NULL); + } + + for (size_t i = 0; i < 10; i++) { + uart_send_cmd_and_wait(disable_nmea[i], 30, &gps_uart); + } + + /* GPS TASK */ + uint8_t c = 0; + int k, index; + char nmea[20], time[20]; + char lon[20], lat[20]; + + //read and parse raw NMEA sentences + + for (;;) + { + if (cyhal_uart_getc(&gps_uart, &c, 0) == CY_RSLT_SUCCESS) { - printf("Sent a message!\n"); - } - cyhal_gpio_write(LED_BLUE, false); + if (c) + { + if (c == '$') + { + for (k = 0; k < 5; k++) + { + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c)) + { + cyhal_uart_getc(&gps_uart, &c, 0); + } + nmea[k] = c; // G + P + G + G + A + } - /* wait before sending next message */ - CyDelay( 10000 ); - } + if (strstr(nmea, "GPGGA")) + { + index = 0; + cyhal_uart_getc(&gps_uart, &c, 0); + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c == ',')) + { + time[index] = c; + ++index; + cyhal_uart_getc(&gps_uart, &c, 0); + } + index = 0; + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c == ',')) + { + lat[index] = c; + ++index; + cyhal_uart_getc(&gps_uart, &c, 0); + } + cyhal_uart_getc(&gps_uart, &c, 0); + index = 0; + cyhal_uart_getc(&gps_uart, &c, 0); + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c == ',')) + { + lon[index] = c; + ++index; + cyhal_uart_getc(&gps_uart, &c, 0); + } + cyhal_uart_getc(&gps_uart, &c, 0); + + /// check if new longitude is float if not - don't change old data + if (is_float(lon)) { + message_data->longitude = NMEAtoDecimalDegrees(lon, c); + message_data->latitude = NMEAtoDecimalDegrees(lat, c); + UTCtoKyivTime(time, message_data); + } + } + } + } + } + } } \ No newline at end of file diff --git a/firmware/MedButton/openocd.tcl b/firmware/MedButton/openocd.tcl index b7f906c..ea8c9c0 100644 --- a/firmware/MedButton/openocd.tcl +++ b/firmware/MedButton/openocd.tcl @@ -1,5 +1,4 @@ source [find interface/kitprog3.cfg] source [find target/psoc6.cfg] -kitprog3 power_config on 3300 ${TARGET}.cm4 configure -rtos auto -rtos-wipe-on-reset-halt 1 psoc6 sflash_restrictions 1 From d3cf99be4bb4a9c169bfcf93f1d9d40f279672d6 Mon Sep 17 00:00:00 2001 From: Diana-Doe Date: Sat, 24 Jul 2021 12:20:14 +0300 Subject: [PATCH 2/4] Working gps and gprs --- firmware/MedButton/.vscode/launch.json | 23 +- .../MedButton/MedButton_First.code-workspace | 5 +- firmware/MedButton/ble_task.c | 137 +++--- firmware/MedButton/data_struct.h | 7 + firmware/MedButton/gprs_task.c | 116 +++++ firmware/MedButton/gprs_task.h | 13 + firmware/MedButton/gps_task.c | 165 +++++++ firmware/MedButton/gps_task.h | 13 + firmware/MedButton/lora_task.c | 69 +++ firmware/MedButton/lora_task.h | 6 + firmware/MedButton/main.c | 402 ++++-------------- firmware/MedButton/openocd.tcl | 3 +- 12 files changed, 553 insertions(+), 406 deletions(-) create mode 100644 firmware/MedButton/gprs_task.c create mode 100644 firmware/MedButton/gprs_task.h create mode 100644 firmware/MedButton/gps_task.c create mode 100644 firmware/MedButton/gps_task.h create mode 100644 firmware/MedButton/lora_task.c create mode 100644 firmware/MedButton/lora_task.h diff --git a/firmware/MedButton/.vscode/launch.json b/firmware/MedButton/.vscode/launch.json index 17d673a..71db460 100644 --- a/firmware/MedButton/.vscode/launch.json +++ b/firmware/MedButton/.vscode/launch.json @@ -37,6 +37,11 @@ "${workspaceRoot}", "${config:modustoolbox.toolsPath}/openocd/scripts/" ], + "openOCDPreConfigLaunchCommands": [ + "set PROGRAMMER kitprog3", + "set ENABLE_ACQUIRE 0", + "set ENABLE_CM0 0" + ], "configFiles": [ "openocd.tcl" ], @@ -50,16 +55,16 @@ "monitor sleep 200", "monitor psoc6 reset_halt sysresetreq" ], - "numberOfProcessors": 2, + "numberOfProcessors": 1, "targetProcessor": 1, // Set to 0 for the CM0+, set to 1 for the CM4 - "postStartSessionCommands": [ - // Needed if runToMain is false - // Following two commands are needed to get gdb and openocd and HW all in sync. - // Or, execution context (PC, stack, registers, etc.) look like they are from before reset. - // The stepi, is a pretend instruction that does not actually do a stepi, but MUST be done - // Its a documented workaround in openocd. Do a 'monitor help' to see more info - // - // An alternative command to use is "continue" instead of the following two + "postStartSessionCommands": [ + // Needed if runToMain is false + // Following two commands are needed to get gdb and openocd and HW all in sync. + // Or, execution context (PC, stack, registers, etc.) look like they are from before reset. + // The stepi, is a pretend instruction that does not actually do a stepi, but MUST be done + // Its a documented workaround in openocd. Do a 'monitor help' to see more info + // + // An alternative command to use is "continue" instead of the following two "monitor gdb_sync", "stepi" ], diff --git a/firmware/MedButton/MedButton_First.code-workspace b/firmware/MedButton/MedButton_First.code-workspace index e5afb9d..d4e3dfc 100644 --- a/firmware/MedButton/MedButton_First.code-workspace +++ b/firmware/MedButton/MedButton_First.code-workspace @@ -19,6 +19,9 @@ //mtb// "modustoolbox.toolsPath": "C:/Users/Deer/ModusToolbox/tools_2.3", "cortex-debug.armToolchainPath": "${config:modustoolbox.toolsPath}/gcc/bin", - "cortex-debug.openocdPath": "${config:modustoolbox.toolsPath}/openocd/bin/openocd" + "cortex-debug.openocdPath": "${config:modustoolbox.toolsPath}/openocd/bin/openocd", + "files.associations": { + "onethinxcore01.h": "c" + } } } \ No newline at end of file diff --git a/firmware/MedButton/ble_task.c b/firmware/MedButton/ble_task.c index 4c1c6db..f417497 100644 --- a/firmware/MedButton/ble_task.c +++ b/firmware/MedButton/ble_task.c @@ -46,6 +46,7 @@ #include "cyhal.h" #include "cycfg_ble.h" #include "string.h" +#include "data_struct.h" #ifdef EINK_DISPLAY_SHIELD_PRESENT #include "display_task.h" #endif @@ -130,7 +131,7 @@ static uint8_t count_five_sec; * Function Prototypes *******************************************************************************/ static void ble_init(void); -static void ble_stack_event_handler(uint32_t event, void *eventParam); +static void ble_stack_event_handler(uint32_t event, void *eventParam, message_struct *message_data); static void ble_controller_interrupt_handler(void); static void bless_interrupt_handler(void); static void ble_initialize_gatt_write(cy_ble_gatt_db_attr_handle_t attribute_handle); @@ -159,7 +160,7 @@ void task_BLE(void *pvParameters) ble_command_type_t ble_cmd = BLE_PROCESS_EVENTS; /* Remove compiler warning for unused variable */ - (void)pvParameters; + message_struct *message_data = (message_struct*) pvParameters; /* Initialize BLE and process any stack events */ ble_init(); @@ -196,7 +197,7 @@ void task_BLE(void *pvParameters) /* Enable the notifications */ if(ble_enable_notification(gatt_notify_cccd_attrHandle) == CY_BLE_SUCCESS) { - tprintf("Notifications Enabled\r\n"); + // tprintf("Notifications Enabled\r\n"); /* Start 1 second timer to calculate throughput */ xTimerStart(timer_handle, (TickType_t)0); #ifdef EINK_DISPLAY_SHIELD_PRESENT @@ -206,7 +207,7 @@ void task_BLE(void *pvParameters) } else { - tprintf("Failed to enable notifications\r\n"); + // tprintf("Failed to enable notifications\r\n"); } break; } @@ -220,7 +221,7 @@ void task_BLE(void *pvParameters) /* Disable the notifications */ if(ble_disable_notification(gatt_notify_cccd_attrHandle) == CY_BLE_SUCCESS) { - tprintf("Notifications Disabled\r\n"); + // tprintf("Notifications Disabled\r\n"); /* Start 1 second timer to calculate throughput */ xTimerStart(timer_handle, (TickType_t)0); #ifdef EINK_DISPLAY_SHIELD_PRESENT @@ -230,7 +231,7 @@ void task_BLE(void *pvParameters) } else { - tprintf("Failed to disable notifications\r\n"); + //tprintf("Failed to disable notifications\r\n"); } /* Initialize the structure for GATT write */ @@ -241,7 +242,7 @@ void task_BLE(void *pvParameters) /* Invalid BLE command */ default: { - iprintf("Invalid BLE command!"); + // //iprintf("Invalid BLE command!"); break; } } @@ -338,7 +339,7 @@ static void ble_init(void) /* BLE stack initialization failed, check configuration, notify error * and halt CPU in debug mode */ - eprintf("Cy_BLE_Enable API, errorcode = 0x%X ", ble_api_result); + //eprintf("Cy_BLE_Enable API, errorcode = 0x%X ", ble_api_result); vTaskSuspend(NULL); } /* Process BLE events after enabling BLE */ @@ -399,7 +400,7 @@ static void ble_controller_interrupt_handler(void) * Return: * None ************************************************************************************/ -static void ble_stack_event_handler(uint32_t event, void *eventParam) +static void ble_stack_event_handler(uint32_t event, void *eventParam, message_struct *message_data) { /* Take an action based on the current event */ switch(event) @@ -410,8 +411,8 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) /* This event is received when the BLE stack is Started */ case CY_BLE_EVT_STACK_ON: { - iprintf("BLE Stack Event: CY_BLE_EVT_STACK_ON"); - tprintf("Press button SW2 on your kit to start Scanning ...\r\n"); + //iprintf("BLE Stack Event: CY_BLE_EVT_STACK_ON"); + //tprintf("Press button SW2 on your kit to start Scanning ...\r\n"); /* Wait till button press to start scanning */ // ulTaskNotifyTake(pdTRUE, portMAX_DELAY); @@ -420,12 +421,12 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) ble_api_result = Cy_BLE_GAPC_StartScan(CY_BLE_SCANNING_FAST, CY_BLE_CENTRAL_CONFIGURATION_0_INDEX); if( ble_api_result == CY_BLE_SUCCESS) { - tprintf("Scanning.....\r\n"); - iprintf("BLE Start Scan API successfull"); + //tprintf("Scanning.....\r\n"); + //iprintf("BLE Start Scan API successfull"); } else { - eprintf("BLE Start Scan API, errorcode = 0x%X", ble_api_result); + // eprintf("BLE Start Scan API, errorcode = 0x%X", ble_api_result); } break; } @@ -455,7 +456,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) /* This event is received when there is a timeout */ case CY_BLE_EVT_TIMEOUT: { - iprintf("BLE Stack Event: CY_BLE_EVT_TIMEOUT"); + //iprintf("BLE Stack Event: CY_BLE_EVT_TIMEOUT"); break; } @@ -466,7 +467,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * scanning */ case CY_BLE_EVT_GAPC_SCAN_START_STOP: { - iprintf("BLE Stack Event: CY_BLE_EVT_GAPC_SCAN_START_STOP"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GAPC_SCAN_START_STOP"); break; } @@ -497,7 +498,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) else { peer_name[name_length]= '\0'; - tprintf("Current device is: %c \r\n", peer_name); + //tprintf("Current device is: %c \r\n", peer_name); target_found = ((strcmp(peer_name, target_name)) ? false : true); } @@ -513,26 +514,26 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) peer_addr.bdAddr[i] = scan_report->peerBdAddr[i]; } - tprintf("Found Peer Device with address:"); + //tprintf("Found Peer Device with address:"); /* Print the peer bd address on UART terminal */ for(uint8 i = (CY_BLE_BD_ADDR_SIZE); i > 0u; i--) { - tprintf(" %X", peer_addr.bdAddr[i - 1u]); + //tprintf(" %X", peer_addr.bdAddr[i - 1u]); } /* Get the peer address type */ peer_addr.type = scan_report->peerAddrType; - tprintf("\r\nScan Completed\r\n"); + //tprintf("\r\nScan Completed\r\n"); /* Initiate connection with discovered peer device */ ble_api_result = Cy_BLE_GAPC_ConnectDevice(&peer_addr, CY_BLE_CENTRAL_CONFIGURATION_0_INDEX); if(ble_api_result == CY_BLE_SUCCESS) { - iprintf(" SUCCESS : Connection Initiation "); + //iprintf(" SUCCESS : Connection Initiation "); } else { - eprintf(" Connection Initiation API, errorcode = 0x%X", ble_api_result); + //eprintf(" Connection Initiation API, errorcode = 0x%X", ble_api_result); } } } @@ -543,7 +544,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * is completed with peer Central device */ case CY_BLE_EVT_GAP_DEVICE_CONNECTED: { - iprintf("BLE Stack Event: CY_BLE_EVT_GAP_DEVICE_CONNECTED"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GAP_DEVICE_CONNECTED"); /* Variable to store connection parameters after GAP connection */ cy_stc_ble_gap_connected_param_t* conn_param; conn_param = (cy_stc_ble_gap_connected_param_t*)eventParam; @@ -564,7 +565,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) /* Store the connection interval value */ conn_interval = (conn_param->connIntv) * CONN_INTERVAL_MULTIPLIER; - tprintf("Connection Interval is: %f ms \r\n", conn_interval); + //tprintf("Connection Interval is: %f ms \r\n", conn_interval); /* Send user LED1 status to the LED queue */ // led_status.conn_led = CYBSP_LED_STATE_ON; @@ -574,12 +575,12 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) ble_api_result = Cy_BLE_SetPhy(&phy_pram); if(ble_api_result == CY_BLE_SUCCESS) { - iprintf("Set PHY to 2M API successfull \r\n"); - tprintf("Request sent to switch PHY to 2M \r\n"); + //iprintf("Set PHY to 2M API successfull \r\n"); + //tprintf("Request sent to switch PHY to 2M \r\n"); } else { - eprintf("Set PHY API, errorcode = 0x%X", ble_api_result); + //eprintf("Set PHY API, errorcode = 0x%X", ble_api_result); } break; } @@ -588,7 +589,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * device or fails to establish connection */ case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED: { - iprintf("BLE Stack Event: CY_BLE_EVT_GAP_DEVICE_DISCONNECTED"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GAP_DEVICE_DISCONNECTED"); /* Stop the timer after device disconnection */ xTimerStop(timer_handle,(TickType_t)0); @@ -619,8 +620,8 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) count_five_sec = 0u; xTaskNotifyGive(display_task_handle); #endif - tprintf("Device Disconnected!!!\r\n"); - tprintf("Press button SW2 on your kit to start Scanning...\r\n"); + //tprintf("Device Disconnected!!!\r\n"); + //tprintf("Press button SW2 on your kit to start Scanning...\r\n"); /* Start scanning again after button press */ scan_flag = false; @@ -630,12 +631,12 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) ble_api_result = Cy_BLE_GAPC_StartScan(CY_BLE_SCANNING_FAST, CY_BLE_CENTRAL_CONFIGURATION_0_INDEX); if(ble_api_result == CY_BLE_SUCCESS) { - tprintf("Scanning.....\r\n"); - iprintf("BLE Start Scan API successfull"); + //tprintf("Scanning.....\r\n"); + //iprintf("BLE Start Scan API successfull"); } else { - eprintf("BLE Start Scan API, errorcode = 0x%X", ble_api_result); + //eprintf("BLE Start Scan API, errorcode = 0x%X", ble_api_result); } break; } @@ -644,7 +645,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * connection */ case CY_BLE_EVT_PHY_UPDATE_COMPLETE: { - iprintf("BLE Stack Event: CY_BLE_EVT_PHY_UPDATE_COMPLETE"); + //iprintf("BLE Stack Event: CY_BLE_EVT_PHY_UPDATE_COMPLETE"); cy_stc_ble_events_param_generic_t *generic_param; cy_stc_ble_phy_param_t *currentPHY; @@ -661,15 +662,15 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) switch(currentPHY->rxPhyMask) { case CY_BLE_PHY_MASK_LE_1M: - tprintf("Selected Rx PHY: 1M\r\n"); + //tprintf("Selected Rx PHY: 1M\r\n"); break; case CY_BLE_PHY_MASK_LE_2M: - tprintf("Selected Rx PHY: 2M\r\n"); + //tprintf("Selected Rx PHY: 2M\r\n"); break; case CY_BLE_PHY_MASK_LE_CODED: - tprintf("Selected Rx PHY: LE Coded\r\n"); + //tprintf("Selected Rx PHY: LE Coded\r\n"); break; } @@ -677,15 +678,15 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) switch(currentPHY->txPhyMask) { case CY_BLE_PHY_MASK_LE_1M: - tprintf("Selected Tx PHY: 1M\r\n"); + //tprintf("Selected Tx PHY: 1M\r\n"); break; case CY_BLE_PHY_MASK_LE_2M: - tprintf("Selected Tx PHY: 2M\r\n"); + //tprintf("Selected Tx PHY: 2M\r\n"); break; case CY_BLE_PHY_MASK_LE_CODED: - tprintf("Selected Tx PHY: LE Coded\r\n"); + //tprintf("Selected Tx PHY: LE Coded\r\n"); break; } @@ -693,11 +694,11 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) ble_api_result = Cy_BLE_GATTC_ExchangeMtuReq(&mtuParam); if(ble_api_result == CY_BLE_SUCCESS) { - iprintf("GATT Exchange MTU Request successfull"); + //iprintf("GATT Exchange MTU Request successfull"); } else { - eprintf("GATT Exchange MTU Request API, errorcode = 0x%X", ble_api_result); + //eprintf("GATT Exchange MTU Request API, errorcode = 0x%X", ble_api_result); } break; } @@ -708,14 +709,14 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * not generated */ case CY_BLE_EVT_GAP_CONNECTION_UPDATE_COMPLETE: { - iprintf("BLE Stack Event: CY_BLE_EVT_GAP_CONNECTION_UPDATE_COMPLETE"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GAP_CONNECTION_UPDATE_COMPLETE"); cy_stc_ble_gap_conn_param_updated_in_controller_t new_conn_params; new_conn_params = *((cy_stc_ble_gap_conn_param_updated_in_controller_t*)eventParam); /* Store the new connection interval */ conn_interval = (new_conn_params.connIntv) * CONN_INTERVAL_MULTIPLIER; - tprintf("Updated Connection interval : %f ms\r\n", conn_interval); + //tprintf("Updated Connection interval : %f ms\r\n", conn_interval); break; } @@ -728,8 +729,8 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) case CY_BLE_EVT_GATT_CONNECT_IND: { conn_handle = *((cy_stc_ble_conn_handle_t*)eventParam); - iprintf("BLE Stack Event: CY_BLE_EVT_GATT_CONNECT_IND"); - tprintf("GATT connected\r\n"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATT_CONNECT_IND"); + //tprintf("GATT connected\r\n"); /* Start 1 second timer to calculate throughput */ xTimerStart(timer_handle, (TickType_t)0); break; @@ -738,7 +739,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) /* This event indicates that the GATT is disconnected.*/ case CY_BLE_EVT_GATT_DISCONNECT_IND: { - iprintf("BLE Stack Event: CY_BLE_EVT_GATT_DISCONNECT_IND"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATT_DISCONNECT_IND"); break; } @@ -746,7 +747,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * for MTU exchange request */ case CY_BLE_EVT_GATTC_XCHNG_MTU_RSP: { - iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_XCHNG_MTU_RSP"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_XCHNG_MTU_RSP"); cy_stc_ble_gatt_xchg_mtu_param_t *mtu_xchg_resp = (cy_stc_ble_gatt_xchg_mtu_param_t*)eventParam; @@ -756,7 +757,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) find_req_param.range.startHandle = 0x01; find_req_param.range.endHandle = 0xFFFF; - tprintf("Negotiated MTU size: %d\r\n", mtu_xchg_resp->mtu); + //tprintf("Negotiated MTU size: %d\r\n", mtu_xchg_resp->mtu); if(mtu_xchg_resp->mtu > CY_BLE_GATT_MTU) { att_mtu_size = CY_BLE_GATT_MTU; @@ -775,11 +776,11 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) ble_api_result= Cy_BLE_GATTC_DiscoverCharacteristicByUuid(¶m); if(ble_api_result == CY_BLE_SUCCESS) { - tprintf("ReadCharacteristic value success \r\n"); + //tprintf("ReadCharacteristic value success \r\n"); } else { - tprintf("ReadCharacteristicValue, errorcode = 0x%X \r\n", ble_api_result); + //tprintf("ReadCharacteristicValue, errorcode = 0x%X \r\n", ble_api_result); } break; } @@ -788,7 +789,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * from GATT Server device */ case CY_BLE_EVT_GATTC_FIND_INFO_RSP: { - iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_FIND_INFO_RSP"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_FIND_INFO_RSP"); cy_stc_ble_gattc_find_info_rsp_param_t find_info_param; @@ -810,8 +811,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) { gatt_write_val_attrHandle= find_info_param.handleValueList.list[0] | (find_info_param.handleValueList.list[1] << 8 ); - iprintf("Att Handle of Custom characteristic: 0x%X", - gatt_write_val_attrHandle); + //iprintf("Att Handle of Custom characteristic: 0x%X", gatt_write_val_attrHandle); } } @@ -828,7 +828,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) { gatt_notify_cccd_attrHandle= find_info_param.handleValueList.list[0] | (find_info_param.handleValueList.list[1] << 8 ); - iprintf("Att Handle of Custom characteristic: 0x%X",gatt_notify_cccd_attrHandle); + //iprintf("Att Handle of Custom characteristic: 0x%X",gatt_notify_cccd_attrHandle); } } @@ -847,7 +847,8 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) { cy_stc_ble_gattc_read_rsp_param_t read_info_param; read_info_param = *((cy_stc_ble_gattc_read_rsp_param_t*)eventParam); - tprintf("Characteristick value is = 0x%X \r\n", read_info_param.value.val); + message_data->pulse = read_info_param.value.val; + //tprintf("Characteristick value is = 0x%X \r\n", read_info_param.value.val); break; } @@ -855,7 +856,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) { cy_stc_ble_gattc_read_by_type_rsp_param_t read_info_param; read_info_param = *((cy_stc_ble_gattc_read_by_type_rsp_param_t*)eventParam); - tprintf("Characteristick value is = 0x%X \r\n", read_info_param.attrData.attrValue); + //tprintf("Characteristick value is = 0x%X \r\n", read_info_param.attrData.attrValue); break; } @@ -863,7 +864,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * BLE Stack will not send any further requests to the peer */ case CY_BLE_EVT_GATTC_LONG_PROCEDURE_END: { - iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_LONG_PROCEDURE_END"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_LONG_PROCEDURE_END"); break; } @@ -873,7 +874,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) { cy_stc_ble_gatt_err_param_t error_rsp; error_rsp = *((cy_stc_ble_gatt_err_param_t*)eventParam); - iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_ERROR_RSP"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_ERROR_RSP"); /* To suppress compiler warning */ (void)error_rsp; break; @@ -884,8 +885,8 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) * Cy_BLE_GATTC_StopCmd() function. */ case CY_BLE_EVT_GATTC_STOP_CMD_COMPLETE: { - iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_STOP_CMD_COMPLETE"); - tprintf("Attributes discovery complete\r\n"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_STOP_CMD_COMPLETE"); + //tprintf("Attributes discovery complete\r\n"); /* Enable notification for the custom throughput service */ ble_enable_notification(gatt_notify_cccd_attrHandle); @@ -901,7 +902,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) case CY_BLE_EVT_GATTC_WRITE_RSP: { button_flag = true; - iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_WRITE_RSP"); + //iprintf("BLE Stack Event: CY_BLE_EVT_GATTC_WRITE_RSP"); break; } @@ -932,17 +933,17 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) /* Check minimum and maximum connection interval requested by * peripheral device */ - tprintf("Connection Interval Update request received\r\n"); + //tprintf("Connection Interval Update request received\r\n"); if((conn_param->connIntvMin >= 54u) && (conn_param->connIntvMax <= 60u)) { conn_param_update_rsp.result = false; - tprintf("Connection interval update request accepted\r\n"); + //tprintf("Connection interval update request accepted\r\n"); Cy_BLE_L2CAP_LeConnectionParamUpdateResponse(&conn_param_update_rsp); } else { conn_param_update_rsp.result = true; - tprintf("Connection interval update request rejected\r\n"); + //tprintf("Connection interval update request rejected\r\n"); Cy_BLE_L2CAP_LeConnectionParamUpdateResponse(&conn_param_update_rsp); } break; @@ -952,7 +953,7 @@ static void ble_stack_event_handler(uint32_t event, void *eventParam) ***********************************************************************/ default: { - iprintf("Other BLE event: 0x%X", (uint32_t)event); + //iprintf("Other BLE event: 0x%X", (uint32_t)event); break; } } @@ -1120,14 +1121,14 @@ void rtos_timer_cb(TimerHandle_t timer_handle) /* Number of bytes */ client_throughput.rx = (notif_rx_bytes) >> 7u; notif_rx_bytes = 0u; - tprintf("GATT NOTIFICATION: Client Throughput Rx = %lu kbps\r\n", client_throughput.rx); + //tprintf("GATT NOTIFICATION: Client Throughput Rx = %lu kbps\r\n", client_throughput.rx); } if(gatt_write_tx_bytes != 0u) { client_throughput.tx = (gatt_write_tx_bytes) >> 7u; gatt_write_tx_bytes = 0u; - tprintf("GATT WRITE: Client Throughput Tx = %lu kbps\r\n", client_throughput.tx); + //tprintf("GATT WRITE: Client Throughput Tx = %lu kbps\r\n", client_throughput.tx); } diff --git a/firmware/MedButton/data_struct.h b/firmware/MedButton/data_struct.h index e30bfb3..54a99d6 100644 --- a/firmware/MedButton/data_struct.h +++ b/firmware/MedButton/data_struct.h @@ -1,10 +1,17 @@ #ifndef C_DATA_STRUCT_H #define C_DATA_STRUCT_H +#include "FreeRTOS.h" +#include "semphr.h" + struct message_struct { float latitude; float longitude; char resultTime[11]; + int pulse; + SemaphoreHandle_t mutex; + SemaphoreHandle_t semaphore_gprs; + SemaphoreHandle_t semaphore_lora; }; typedef struct message_struct message_struct; diff --git a/firmware/MedButton/gprs_task.c b/firmware/MedButton/gprs_task.c new file mode 100644 index 0000000..2dd7aac --- /dev/null +++ b/firmware/MedButton/gprs_task.c @@ -0,0 +1,116 @@ +#include "gprs_task.h" + +#define LED_RED (P12_5) +#define GPRS_SETUP (P9_2) + +cyhal_uart_t gprs_uart; +uint8_t rx_buf[64]; + +const cyhal_uart_cfg_t uart_config_gprs = +{ + .data_bits = 8, + .stop_bits = 1, + .parity = CYHAL_UART_PARITY_NONE, + .rx_buffer = rx_buf, + .rx_buffer_size = 64, +}; + + +void wait_uart_free(cyhal_uart_t *uart_obj) +{ + while (cyhal_uart_is_rx_active(uart_obj)) { cyhal_system_delay_ms(1); } + while (cyhal_uart_is_tx_active(uart_obj)) { cyhal_system_delay_ms(1); } +} + +void uart_send_cmd_and_wait(char *cmd, size_t cmd_len, cyhal_uart_t *uart_obj) +{ + cy_rslt_t result; + wait_uart_free(uart_obj); + if (cmd_len > 1) + { + result = cyhal_uart_write_async(uart_obj, cmd, cmd_len); + } + else + { + result = cyhal_uart_putc(uart_obj, cmd[0]); + } + + if (CY_RSLT_SUCCESS != result) + { + while(1) + { + cyhal_gpio_toggle(LED_RED); + cyhal_system_delay_ms(100); + } + } + + wait_uart_free(uart_obj); + // bool data_received = false; + // while(cyhal_uart_readable(uart_obj) != 0) + // { + // data_received = true; + // uint8_t received_char; + // if (CY_RSLT_SUCCESS == cyhal_uart_getc(uart_obj, &received_char, 1)) + // { + // //printf("%c", received_char); + // } + // } + // if (data_received) + // { + // //printf("\n"); + // } + cyhal_system_delay_ms(3000); +} + +static void gprs_setup(void) { + cy_rslt_t rslt; + rslt = cyhal_gpio_init(GPRS_SETUP, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, false); + + if (rslt != CY_RSLT_SUCCESS) { + //printf("Not powered\r\n"); + } + + cyhal_gpio_write(GPRS_SETUP, false); + cyhal_system_delay_ms(1000); + cyhal_gpio_write(GPRS_SETUP, true); + cyhal_system_delay_ms(2000); + cyhal_gpio_write(GPRS_SETUP, false); + cyhal_system_delay_ms(3000); +} + +void task_gprs(void* param) { + /* uart GPRS */ + message_struct *message_data = (message_struct*) param; + + cy_rslt_t result; + + cyhal_uart_t gprs_uart; + + result = cyhal_uart_init(&gprs_uart, P9_1, P9_0, NULL, &uart_config_gprs); + + if (result == CY_RSLT_SUCCESS) + { + cyhal_uart_set_baud(&gprs_uart, 115200, NULL); + } + + gprs_setup(); + + char message[50]; + while (1) { + if (xSemaphoreTake(message_data->semaphore_gprs, 5000)) { + xSemaphoreTake(message_data->mutex, portMAX_DELAY); + sprintf(message, "%s-%f,%f", message_data->resultTime, message_data->latitude, message_data->longitude); + xSemaphoreGive(message_data->mutex); + char at_cmd[] = "AT\r"; + uart_send_cmd_and_wait(at_cmd, sizeof(at_cmd), &gprs_uart); + char sms_config_cmd[] = "AT+CMGF=1\r"; + uart_send_cmd_and_wait(sms_config_cmd, sizeof(sms_config_cmd), &gprs_uart); + char sms_prepare_cmd[] = "AT+CMGS=\"+380958957865\"\r"; + uart_send_cmd_and_wait(sms_prepare_cmd, sizeof(sms_prepare_cmd), &gprs_uart); + cyhal_uart_clear(&gprs_uart); + uart_send_cmd_and_wait(message, sizeof(message), &gprs_uart); + char sms_stop_cmd[] = { (char)26 }; + uart_send_cmd_and_wait(sms_stop_cmd, 1, &gprs_uart); + } + } +} \ No newline at end of file diff --git a/firmware/MedButton/gprs_task.h b/firmware/MedButton/gprs_task.h new file mode 100644 index 0000000..fc2bc35 --- /dev/null +++ b/firmware/MedButton/gprs_task.h @@ -0,0 +1,13 @@ +#ifndef C_GPRS_TASK_H +#define C_GPRS_TASK_H + +#include "cyhal.h" +#include "cybsp.h" +#include "cy_retarget_io.h" +#include "data_struct.h" + +void task_gprs(void* param); +void uart_send_cmd_and_wait(char *cmd, size_t cmd_len, cyhal_uart_t *uart_obj); +void wait_uart_free(cyhal_uart_t *uart_obj); + +#endif //C_GPRS_TASK_H \ No newline at end of file diff --git a/firmware/MedButton/gps_task.c b/firmware/MedButton/gps_task.c new file mode 100644 index 0000000..7386ada --- /dev/null +++ b/firmware/MedButton/gps_task.c @@ -0,0 +1,165 @@ +#include "gps_task.h" +#include + + +const cyhal_uart_cfg_t uart_config = +{ + .data_bits = 8, + .stop_bits = 1, + .parity = CYHAL_UART_PARITY_NONE, + .rx_buffer = NULL, + .rx_buffer_size = 0, +}; + +const char disable_nmea[10][30] = { + "$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n", + "$PUBX,40,ZDA,0,0,0,0,0,0*44\r\n", + "PUBX,40,VTG,0,0,0,0,0,0*5E\r\n", + "PUBX,40,GSV,0,0,0,0,0,0*59\r\n", + "$PUBX,40,GSA,0,0,0,0,0,0*4E\r\n", + "$PUBX,40,RMC,0,0,0,0,0,0*47\r\n", + "$PUBX,40,GNS,0,0,0,0,0,0*41\r\n", + "$PUBX,40,GRS,0,0,0,0,0,0*5D\r\n", + "$PUBX,40,GST,0,0,0,0,0,0*5B\r\n", + "$PUBX,40,TXT,0,0,0,0,0,0*43\r\n", +}; + +void task_gps(void* param) { + /* uart GPS */ + message_struct *message_data = (message_struct*) param; + message_data->longitude = 0.0; + message_data->latitude = 0.0; + + cy_rslt_t result; + + cyhal_uart_t gps_uart; + + result = cyhal_uart_init(&gps_uart, P10_1, P10_0, NULL, &uart_config); + + if (result == CY_RSLT_SUCCESS) + { + result = cyhal_uart_set_baud(&gps_uart, 9600, NULL); + } + + // for (size_t i = 0; i < 10; i++) { + // uart_send_cmd_and_wait(disable_nmea[i], 30, &gps_uart); + // } + + /* GPS TASK */ + uint8_t c = 0; + int k, index; + char nmea[20], time[20]; + char lon[20], lat[20]; + int len; + float ignore; + + //read and parse raw NMEA sentences + + for (;;) + { + if (cyhal_uart_getc(&gps_uart, &c, 0) == CY_RSLT_SUCCESS) + { + if (c) + { + if (c == '$') + { + for (k = 0; k < 5; k++) + { + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c)) + { + cyhal_uart_getc(&gps_uart, &c, 0); + } + nmea[k] = c; // G + P + G + G + A + } + + if (strstr(nmea, "GPGGA")) + { + memset(lon, 0, sizeof lon); + memset(lat, 0, sizeof lat); + memset(time, 0, sizeof time); + index = 0; + cyhal_uart_getc(&gps_uart, &c, 0); + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c == ',')) + { + time[index] = c; + ++index; + cyhal_uart_getc(&gps_uart, &c, 0); + } + index = 0; + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c == ',')) + { + lat[index] = c; + ++index; + cyhal_uart_getc(&gps_uart, &c, 0); + } + cyhal_uart_getc(&gps_uart, &c, 0); + + index = 0; + cyhal_uart_getc(&gps_uart, &c, 0); + cyhal_uart_getc(&gps_uart, &c, 0); + while (!(c == ',')) + { + lon[index] = c; + ++index; + cyhal_uart_getc(&gps_uart, &c, 0); + } + cyhal_uart_getc(&gps_uart, &c, 0); + sscanf(lon, "%f %n", &ignore, &len); + + /// check if new longitude isn't empty + if (lon[0] != '\0') { + xSemaphoreTake(message_data->mutex, portMAX_DELAY); + message_data->longitude = NMEAtoDecimalDegrees(lon, c); + message_data->latitude = NMEAtoDecimalDegrees(lat, c); + UTCtoKyivTime(time, message_data); + xSemaphoreGive(message_data->mutex); + } + cyhal_system_delay_ms(5000); + } + } + } + } + } +} + + +float NMEAtoDecimalDegrees(const char *degree, char quadrant) +{ + // nmea format: "ddmm.mmmm" or "dddmm.mmmm" to decimal degrees + // D+M/60 + float result = 0; + if (strlen(degree) > 5) + { + char integerPart[3 + 1]; + int counter = (degree[4] == '.' ? 2 : 3); + memcpy(integerPart, degree, counter); + integerPart[counter] = 0; + degree += counter; + result = atoi(integerPart) + atof(degree) / 60.; + if (quadrant == 'W' || quadrant == 'S') + result = -result; + } + return result; +} + +char *UTCtoKyivTime(const char *utcTime, message_struct *message_data) +{ + // 172814.0 - hhmmss.ss + int i, digit, number = 0; + char c; + for (i = 0; i < 2; i++) + { + c = utcTime[i]; + if (c >= '0' && c <= '9') //to confirm it's a digit + { + digit = c - '0'; + number = number * 10 + digit; + } + } + number = (number + 2) % 24; + sprintf(message_data->resultTime, "%d:%c%c:%c%c", number, utcTime[2], utcTime[3], utcTime[4], utcTime[5]); + return 0; +} \ No newline at end of file diff --git a/firmware/MedButton/gps_task.h b/firmware/MedButton/gps_task.h new file mode 100644 index 0000000..0779b89 --- /dev/null +++ b/firmware/MedButton/gps_task.h @@ -0,0 +1,13 @@ +#ifndef C_GPS_TASK_H +#define C_GPS_TASK_H + +#include "cyhal.h" +#include "cybsp.h" +#include "cy_retarget_io.h" +#include "data_struct.h" + +void task_gps(void* param); +char *UTCtoKyivTime(const char *utcTime, message_struct *message_data); +float NMEAtoDecimalDegrees(const char *degree, char quadrant); + +#endif //C_GPS_TASK_H \ No newline at end of file diff --git a/firmware/MedButton/lora_task.c b/firmware/MedButton/lora_task.c new file mode 100644 index 0000000..353806e --- /dev/null +++ b/firmware/MedButton/lora_task.c @@ -0,0 +1,69 @@ +#include "lora_task.h" +#include "cyhal.h" +#include "cybsp.h" +#include "cy_retarget_io.h" +#include "OnethinxCore01.h" +#include "LoRaWAN_keys.h" +#include "data_struct.h" +#include + +#define LED_BLUE (P12_4) + +coreStatus_t coreStatus; +coreInfo_t coreInfo; + +uint8_t RXbuffer[64]; +uint8_t TXbuffer[64]; + + +coreConfiguration_t coreConfig = { + .Join.KeysPtr = &TTN_OTAAkeys, + .Join.DataRate = DR_AUTO, + .Join.Power = PWR_MAX, + .Join.MAXTries = 100, + .Join.SubBand_1st = EU_SUB_BANDS_DEFAULT, + .Join.SubBand_2nd = EU_SUB_BANDS_DEFAULT, + .TX.Confirmed = false, + .TX.DataRate = DR_0, + .TX.Power = PWR_MAX, + .TX.FPort = 1, +}; + +void lora_send(void* param) { + message_struct * message_data = (message_struct*) param; + + coreStatus = LoRaWAN_Init(&coreConfig); + // /* Check Onethinx Core info */ + LoRaWAN_GetInfo(&coreInfo); + /* send join using parameters in coreConfig, blocks until either success or MAXtries */ + coreStatus = LoRaWAN_Join(true); + + // /* check for successful join */ + if (!coreStatus.mac.isJoined){ + // while(1) { + cyhal_gpio_toggle(LED_BLUE); + cyhal_system_delay_ms(100); + // } + } else { + cyhal_gpio_write(LED_BLUE, true); + /*delay before first message will be sent */ + cyhal_system_delay_ms(1000); + } + + char message[50]; + while(1) { + if(xSemaphoreTake(message_data->semaphore_lora, 5000)) { + xSemaphoreTake(message_data->mutex, portMAX_DELAY); + sprintf(message, "%s-%f,%f", message_data->resultTime, message_data->latitude, message_data->longitude); + xSemaphoreGive(message_data->mutex); + coreStatus = LoRaWAN_Send((uint8_t *) message, 64, true); + cyhal_system_delay_ms(1000); // vTaskDelay + if( coreStatus.system.errorStatus == system_BusyError ){ + for(int i=0; i<10; i++){ + cyhal_gpio_toggle(LED_BLUE);; + cyhal_system_delay_ms(100); + } + } + } + } +} \ No newline at end of file diff --git a/firmware/MedButton/lora_task.h b/firmware/MedButton/lora_task.h new file mode 100644 index 0000000..f827bde --- /dev/null +++ b/firmware/MedButton/lora_task.h @@ -0,0 +1,6 @@ +#ifndef C_LORA_TASK_H +#define C_LORA_TASK_H + +void lora_send(void* param); + +#endif //C_LORA_TASK_H \ No newline at end of file diff --git a/firmware/MedButton/main.c b/firmware/MedButton/main.c index a7d2e25..4c503a6 100644 --- a/firmware/MedButton/main.c +++ b/firmware/MedButton/main.c @@ -1,91 +1,44 @@ #include "cyhal.h" #include "cybsp.h" #include "cy_retarget_io.h" -#include "OnethinxCore01.h" -#include "LoRaWAN_keys.h" #include "cy_pdl.h" #include #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "semphr.h" -#include "data_struct.h" +#include "ble_task.h" +#include "gps_task.h" +#include "gprs_task.h" +#include "lora_task.h" message_struct message_str; -SemaphoreHandle_t semaphore_gprs; -SemaphoreHandle_t semaphore_lora; - -coreStatus_t coreStatus; -coreInfo_t coreInfo; - -uint8_t RXbuffer[64]; -uint8_t TXbuffer[64]; +cyhal_timer_t timer; #define LED_BLUE (P12_4) #define LED_RED (P12_5) -#define GPRS_SETUP (P9_2) #define TASK (256u) #define TASK_GPRS (512u) +#define TASK_BLE (configMINIMAL_STACK_SIZE * 4) -coreConfiguration_t coreConfig = { - .Join.KeysPtr = &TTN_OTAAkeys, - .Join.DataRate = DR_AUTO, - .Join.Power = PWR_MAX, - .Join.MAXTries = 100, - .Join.SubBand_1st = EU_SUB_BANDS_DEFAULT, - .Join.SubBand_2nd = EU_SUB_BANDS_DEFAULT, - .TX.Confirmed = false, - .TX.DataRate = DR_0, - .TX.Power = PWR_MAX, - .TX.FPort = 1, -}; - -void task_gps(void* param); -void task_gprs(void* param); -void lora_send(void* param); -static void gpio_interrupt_handler(void *handler_arg, cyhal_gpio_event_t event); -float latitude, longitude; -char resultTime[11]; -char resultMessage[100]; -uint8_t * castedMessage; +#define TIMER_CLOCK_HZ (10000) +#define TIMER_PERIOD (9999 * 60) -const cyhal_uart_cfg_t uart_config = -{ - .data_bits = 8, - .stop_bits = 1, - .parity = CYHAL_UART_PARITY_NONE, - .rx_buffer = NULL, - .rx_buffer_size = 0, -}; +#define BLE_CMD_Q_LEN (10u) -cyhal_uart_t gprs_uart; -uint8_t rx_buf[64]; - -const cyhal_uart_cfg_t uart_config_gprs = -{ - .data_bits = 8, - .stop_bits = 1, - .parity = CYHAL_UART_PARITY_NONE, - .rx_buffer = rx_buf, - .rx_buffer_size = 64, -}; - -char disable_nmea[10][30] = { - "$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n", - "$PUBX,40,ZDA,0,0,0,0,0,0*44\r\n", - "PUBX,40,VTG,0,0,0,0,0,0*5E\r\n", - "PUBX,40,GSV,0,0,0,0,0,0*59\r\n", - "$PUBX,40,GSA,0,0,0,0,0,0*4E\r\n", - "$PUBX,40,RMC,0,0,0,0,0,0*47\r\n", - "$PUBX,40,GNS,0,0,0,0,0,0*41\r\n", - "$PUBX,40,GRS,0,0,0,0,0,0*5D\r\n", - "$PUBX,40,GST,0,0,0,0,0,0*5B\r\n", - "$PUBX,40,TXT,0,0,0,0,0,0*43\r\n", -}; +static void gpio_interrupt_handler(void *handler_arg, cyhal_gpio_event_t event); +static void isr_timer(void *callback_arg, cyhal_timer_event_t event); +void lora_timer_init(void); +/* FOR BLE */ +TaskHandle_t ble_task_handle; +QueueHandle_t ble_cmdQ; +TimerHandle_t timer_handle; +tx_rx_mode mode_flag = GATT_NOTIF_STOC; +/***************/ int main(void) { @@ -125,13 +78,24 @@ int main(void) CY_ASSERT(0); } + /* FOR BLE */ + ble_cmdQ = xQueueCreate(BLE_CMD_Q_LEN, sizeof(ble_command_type_t)); + + timer_handle = xTimerCreate("Timer", pdMS_TO_TICKS(1000), pdTRUE, + NULL, rtos_timer_cb); + + + message_str.semaphore_lora = xSemaphoreCreateBinary(); + message_str.semaphore_gprs = xSemaphoreCreateBinary(); + message_str.mutex = xSemaphoreCreateMutex(); - semaphore_lora = xSemaphoreCreateBinary(); - semaphore_gprs = xSemaphoreCreateBinary(); + lora_timer_init(); + xTaskCreate(task_gps, "GPS Task", TASK_GPRS, &message_str, 1, NULL); + xTaskCreate(task_BLE, "BLE Task", TASK_BLE, NULL, 1, &ble_task_handle); xTaskCreate(lora_send, "LoRa", TASK , &message_str, 2, NULL); xTaskCreate(task_gprs, "GPRS Task", TASK_GPRS , &message_str, 2, NULL); - xTaskCreate(task_gps, "GPS Task", TASK, &message_str, 1, NULL); + vTaskStartScheduler(); @@ -139,284 +103,68 @@ int main(void) } - +/* + Interrupt by button. Wake up gprs task +*/ static void gpio_interrupt_handler(void *handler_arg, cyhal_gpio_irq_event_t event) { BaseType_t xHigherPriorityTaskWoken = pdFALSE;; - xSemaphoreGiveFromISR(semaphore_gprs, xHigherPriorityTaskWoken); + xSemaphoreGiveFromISR(message_str.semaphore_gprs, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } -float NMEAtoDecimalDegrees(const char *degree, char quadrant) -{ - // nmea format: "ddmm.mmmm" or "dddmm.mmmm" to decimal degrees - // D+M/60 - float result = 0; - if (strlen(degree) > 5) - { - char integerPart[3 + 1]; - int counter = (degree[4] == '.' ? 2 : 3); - memcpy(integerPart, degree, counter); - integerPart[counter] = 0; - degree += counter; - result = atoi(integerPart) + atof(degree) / 60.; - if (quadrant == 'W' || quadrant == 'S') - result = -result; - } - return result; -} - -char *UTCtoKyivTime(const char *utcTime, message_struct *message_data) -{ - // 172814.0 - hhmmss.ss - int i, digit, number = 0; - char c; - for (i = 0; i < 2; i++) - { - c = utcTime[i]; - if (c >= '0' && c <= '9') //to confirm it's a digit - { - digit = c - '0'; - number = number * 10 + digit; - } - } - number = (number + 2) % 24; - sprintf(message_data->resultTime, "%d:%c%c:%c%c", number, utcTime[2], utcTime[3], utcTime[4], utcTime[5]); - return 0; -} -uint8_t rx_buf[64]; -size_t rx_length = 64; - -void wait_uart_free(cyhal_uart_t *uart_obj) -{ - while (cyhal_uart_is_rx_active(uart_obj)) { cyhal_system_delay_ms(1); } - while (cyhal_uart_is_tx_active(uart_obj)) { cyhal_system_delay_ms(1); } -} -void uart_send_cmd_and_wait(char *cmd, size_t cmd_len, cyhal_uart_t *uart_obj) +void lora_timer_init(void) { cy_rslt_t result; - wait_uart_free(uart_obj); - if (cmd_len > 1) - { - result = cyhal_uart_write_async(uart_obj, cmd, cmd_len); - } - else - { - result = cyhal_uart_putc(uart_obj, cmd[0]); - } - - if (CY_RSLT_SUCCESS != result) - { - while(1) - { - cyhal_gpio_toggle(LED_RED); - cyhal_system_delay_ms(100); - } - } - wait_uart_free(uart_obj); - bool data_received = false; - while(cyhal_uart_readable(uart_obj) != 0) + const cyhal_timer_cfg_t timer_cfg = { - data_received = true; - uint8_t received_char; - if (CY_RSLT_SUCCESS == cyhal_uart_getc(uart_obj, &received_char, 1)) - { - //printf("%c", received_char); - } - } - if (data_received) + .compare_value = 0, /* Timer compare value, not used */ + .period = TIMER_PERIOD, /* Defines the timer period */ + .direction = CYHAL_TIMER_DIR_UP, /* Timer counts up */ + .is_compare = false, /* Don't use compare mode */ + .is_continuous = true, /* Run timer indefinitely */ + .value = 0 /* Initial value of counter */ + }; + + /* Initialize the timer object. Does not use input pin ('pin' is NC) and + * does not use a pre-configured clock source ('clk' is NULL). */ + result = cyhal_timer_init(&timer, NC, NULL); + + /* timer init failed. Stop program execution */ + if (result != CY_RSLT_SUCCESS) { - //printf("\n"); - } - cyhal_system_delay_ms(3000); -} - -static void gprs_setup(void) { - cy_rslt_t rslt; - rslt = cyhal_gpio_init(GPRS_SETUP, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, false); - - if (rslt != CY_RSLT_SUCCESS) { - //printf("Not powered\r\n"); + CY_ASSERT(0); } - cyhal_gpio_write(GPRS_SETUP, false); - cyhal_system_delay_ms(1000); - cyhal_gpio_write(GPRS_SETUP, true); - cyhal_system_delay_ms(2000); - cyhal_gpio_write(GPRS_SETUP, false); - cyhal_system_delay_ms(3000); -} + /* Configure timer period and operation mode such as count direction, + duration */ + cyhal_timer_configure(&timer, &timer_cfg); -void task_gprs(void* param) { - /* uart GPRS */ - message_struct *message_data = (message_struct*) param; + /* Set the frequency of timer's clock source */ + cyhal_timer_set_frequency(&timer, TIMER_CLOCK_HZ); - cy_rslt_t result; + /* Assign the ISR to execute on timer interrupt */ + cyhal_timer_register_callback(&timer, isr_timer, NULL); - cyhal_uart_t gprs_uart; - - result = cyhal_uart_init(&gprs_uart, P9_1, P9_0, NULL, &uart_config_gprs); - - if (result == CY_RSLT_SUCCESS) - { - cyhal_uart_set_baud(&gprs_uart, 115200, NULL); - } + /* Set the event on which timer interrupt occurs and enable it */ + cyhal_timer_enable_event(&timer, CYHAL_TIMER_IRQ_TERMINAL_COUNT, + 7, true); - gprs_setup(); - - char message[100]; - while (1) { - if (xSemaphoreTake(semaphore_gprs, 1000)) { - sprintf(message, "%s-%f,%f", message_data->resultTime, message_data->latitude, message_data->longitude); - char at_cmd[] = "AT\r"; - uart_send_cmd_and_wait(at_cmd, sizeof(at_cmd), &gprs_uart); - char sms_config_cmd[] = "AT+CMGF=1\r"; - uart_send_cmd_and_wait(sms_config_cmd, sizeof(sms_config_cmd), &gprs_uart); - char sms_prepare_cmd[] = "AT+CMGS=\"+380958957865\"\r"; - uart_send_cmd_and_wait(sms_prepare_cmd, sizeof(sms_prepare_cmd), &gprs_uart); - cyhal_uart_clear(&gprs_uart); - uart_send_cmd_and_wait(message, sizeof(message), &gprs_uart); - char sms_stop_cmd[] = { (char)26 }; - uart_send_cmd_and_wait(sms_stop_cmd, 1, &gprs_uart); - } - } + /* Start the timer with the configured settings */ + cyhal_timer_start(&timer); } +/* + Interrupt by timer. Wake up lora task +*/ +static void isr_timer(void *callback_arg, cyhal_timer_event_t event) +{ + (void) callback_arg; + (void) event; -void lora_send(void* param) { - message_struct * message_data = (message_struct*) param; - - coreStatus = LoRaWAN_Init(&coreConfig); - // /* Check Onethinx Core info */ - LoRaWAN_GetInfo(&coreInfo); - /* send join using parameters in coreConfig, blocks until either success or MAXtries */ - coreStatus = LoRaWAN_Join(true); - - // /* check for successful join */ - if (!coreStatus.mac.isJoined){ - // while(1) { - cyhal_gpio_toggle(LED_BLUE); - cyhal_system_delay_ms(100); - // } - } else { - cyhal_gpio_write(LED_BLUE, true); - /*delay before first message will be sent */ - cyhal_system_delay_ms(1000); - } - - char message[50]; - while(1) { - if(xSemaphoreTake(semaphore_lora, 1000)) { - sprintf(message, "%s-%f,%f", message_data->resultTime, message_data->latitude, message_data->longitude); - coreStatus = LoRaWAN_Send((uint8_t *) message, 64, true); - cyhal_system_delay_ms(1000); // vTaskDelay - if( coreStatus.system.errorStatus == system_BusyError ){ - for(int i=0; i<10; i++){ - cyhal_gpio_toggle(LED_BLUE);; - cyhal_system_delay_ms(100); - } - } - } - } -} - -uint8_t is_float(const char *check) { - int len; - float ignore; - int ret = sscanf(check, "%f %n", &ignore, &len); - return (ret==1 && !check[len]); + BaseType_t xHigherPriorityTaskWoken = pdFALSE;; + xSemaphoreGiveFromISR(message_str.semaphore_lora, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } - -void task_gps(void* param) { - /* uart GPS */ - message_struct *message_data = (message_struct*) param; - message_data->longitude = 0.0; - message_data->latitude = 0.0; - - cy_rslt_t result; - - cyhal_uart_t gps_uart; - - result = cyhal_uart_init(&gps_uart, P10_1, P10_0, NULL, &uart_config); - - if (result == CY_RSLT_SUCCESS) - { - result = cyhal_uart_set_baud(&gps_uart, 9600, NULL); - } - - for (size_t i = 0; i < 10; i++) { - uart_send_cmd_and_wait(disable_nmea[i], 30, &gps_uart); - } - - /* GPS TASK */ - uint8_t c = 0; - int k, index; - char nmea[20], time[20]; - char lon[20], lat[20]; - - //read and parse raw NMEA sentences - - for (;;) - { - if (cyhal_uart_getc(&gps_uart, &c, 0) == CY_RSLT_SUCCESS) - { - if (c) - { - if (c == '$') - { - for (k = 0; k < 5; k++) - { - cyhal_uart_getc(&gps_uart, &c, 0); - while (!(c)) - { - cyhal_uart_getc(&gps_uart, &c, 0); - } - nmea[k] = c; // G + P + G + G + A - } - - if (strstr(nmea, "GPGGA")) - { - index = 0; - cyhal_uart_getc(&gps_uart, &c, 0); - cyhal_uart_getc(&gps_uart, &c, 0); - while (!(c == ',')) - { - time[index] = c; - ++index; - cyhal_uart_getc(&gps_uart, &c, 0); - } - index = 0; - cyhal_uart_getc(&gps_uart, &c, 0); - while (!(c == ',')) - { - lat[index] = c; - ++index; - cyhal_uart_getc(&gps_uart, &c, 0); - } - cyhal_uart_getc(&gps_uart, &c, 0); - - index = 0; - cyhal_uart_getc(&gps_uart, &c, 0); - cyhal_uart_getc(&gps_uart, &c, 0); - while (!(c == ',')) - { - lon[index] = c; - ++index; - cyhal_uart_getc(&gps_uart, &c, 0); - } - cyhal_uart_getc(&gps_uart, &c, 0); - - /// check if new longitude is float if not - don't change old data - if (is_float(lon)) { - message_data->longitude = NMEAtoDecimalDegrees(lon, c); - message_data->latitude = NMEAtoDecimalDegrees(lat, c); - UTCtoKyivTime(time, message_data); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/firmware/MedButton/openocd.tcl b/firmware/MedButton/openocd.tcl index ea8c9c0..d91fa73 100644 --- a/firmware/MedButton/openocd.tcl +++ b/firmware/MedButton/openocd.tcl @@ -1,4 +1,5 @@ source [find interface/kitprog3.cfg] source [find target/psoc6.cfg] +kitprog3 power_config on 3300 ${TARGET}.cm4 configure -rtos auto -rtos-wipe-on-reset-halt 1 -psoc6 sflash_restrictions 1 +psoc6 sflash_restrictions 1 \ No newline at end of file From 2e27f8058267dda4988537ad8bd891aabba4a5e3 Mon Sep 17 00:00:00 2001 From: Maksym Maystrenko Date: Sun, 25 Jul 2021 20:46:51 +0100 Subject: [PATCH 3/4] Added QT application example, that can read LoRa uplink messages from https://eu1.cloud.thethings.network/ --- software/Qt/Lora-MQTT-test/README.md | 11 +++ .../guidance_screenshots/api_key.png | Bin 0 -> 83892 bytes .../guidance_screenshots/storage.png | Bin 0 -> 76742 bytes .../Qt/Lora-MQTT-test/mqtt-test/.gitignore | 73 ++++++++++++++++++ software/Qt/Lora-MQTT-test/mqtt-test/main.cpp | 11 +++ .../Lora-MQTT-test/mqtt-test/mainwindow.cpp | 50 ++++++++++++ .../Qt/Lora-MQTT-test/mqtt-test/mainwindow.h | 28 +++++++ .../Qt/Lora-MQTT-test/mqtt-test/mainwindow.ui | 55 +++++++++++++ .../Qt/Lora-MQTT-test/mqtt-test/mqtt-test.pro | 31 ++++++++ 9 files changed, 259 insertions(+) create mode 100644 software/Qt/Lora-MQTT-test/README.md create mode 100644 software/Qt/Lora-MQTT-test/guidance_screenshots/api_key.png create mode 100644 software/Qt/Lora-MQTT-test/guidance_screenshots/storage.png create mode 100644 software/Qt/Lora-MQTT-test/mqtt-test/.gitignore create mode 100644 software/Qt/Lora-MQTT-test/mqtt-test/main.cpp create mode 100644 software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp create mode 100644 software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.h create mode 100644 software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.ui create mode 100644 software/Qt/Lora-MQTT-test/mqtt-test/mqtt-test.pro diff --git a/software/Qt/Lora-MQTT-test/README.md b/software/Qt/Lora-MQTT-test/README.md new file mode 100644 index 0000000..fbc63c1 --- /dev/null +++ b/software/Qt/Lora-MQTT-test/README.md @@ -0,0 +1,11 @@ +## LoRa uplink messages read from https://eu1.cloud.thethings.network/ server +This project reads most recent uplink LoRa packet for specified (in link) thethingsnetwork application. + +Items needed in order to start: +1. Activate storage integration for your application +![Storage Integration Screenshot](guidance_screenshots/storage.png) +2. Generate and copy to the code API key with "Read application traffic (uplink and downlink)" right: +![API key generation](guidance_screenshots/api_key.png) +3. Place SSL libraries libeay32.dll and ssleay32.dll into target exe folder (D:\Git\MedButton\software\Qt\Lora-MQTT-test\build-mqtt-test-Desktop_Qt_5_12_3_MinGW_64_bit-Debug\debug in my case). This is needed for https GET request. I've found dlls in by next paths in my Qt installation folder: +* "D:\Qt\Tools\mingw730_64\opt\bin\libeay32.dll" +* "D:\Qt\Tools\mingw730_64\opt\bin\ssleay32.dll" diff --git a/software/Qt/Lora-MQTT-test/guidance_screenshots/api_key.png b/software/Qt/Lora-MQTT-test/guidance_screenshots/api_key.png new file mode 100644 index 0000000000000000000000000000000000000000..4f78fc5bd8584a56825f93579b78c1842c7ad10b GIT binary patch literal 83892 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 literal 0 HcmV?d00001 diff --git a/software/Qt/Lora-MQTT-test/mqtt-test/.gitignore b/software/Qt/Lora-MQTT-test/mqtt-test/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/software/Qt/Lora-MQTT-test/mqtt-test/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/software/Qt/Lora-MQTT-test/mqtt-test/main.cpp b/software/Qt/Lora-MQTT-test/mqtt-test/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/software/Qt/Lora-MQTT-test/mqtt-test/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp new file mode 100644 index 0000000..09150aa --- /dev/null +++ b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp @@ -0,0 +1,50 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + ui->txt_box->setText("Test text!"); + + manager = new QNetworkAccessManager(); + QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), + this, SLOT(managerFinished(QNetworkReply*))); + + // Example application name - "first-random-app" + // limit=1 - return only 1 (most recent) LoRa message + request.setUrl(QUrl("https://eu1.cloud.thethings.network/api/v3/as/applications/first-random-app/packages/storage/uplink_message?limit=1")); + // API Key goes after "NNSXS." + request.setRawHeader("Authorization" , "Bearer NNSXS.DJBXJQNTIBAYD6LR5NVPUNP64EKDH54OLFKQGSI.OODUM6B6NVTPRDHJIDLUEWMU6IY3JG35Z34OWJ5EMMUVQS5XUQRA"); + request.setRawHeader("Accept" , "text/event-stream"); + request.setRawHeader("Content-Type" , "text/event-stream"); + + manager->get(request); +} + +void MainWindow::managerFinished(QNetworkReply *reply) +{ + QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + if (status_code.isValid()) + { + QString status = status_code.toString(); + QByteArray rawResponse = reply->readAll(); + QJsonDocument jsonResponse = QJsonDocument::fromJson(rawResponse); + QJsonObject jsonObject = jsonResponse.object(); + QString jsonData = jsonObject.value("result").toObject().value("uplink_message").toObject().value("frm_payload").toString(); + /* decoded from Base64 data will be stored in uplinkMsgText */ + QString uplinkMsgText = QByteArray::fromBase64(jsonData.toUtf8()); + qDebug() << "Received from server LoRa uplink_message: " << uplinkMsgText; + } +} + +MainWindow::~MainWindow() +{ + delete ui; +} + + +void MainWindow::on_btn_test_clicked() +{ +} diff --git a/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.h b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.h new file mode 100644 index 0000000..41b389e --- /dev/null +++ b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.h @@ -0,0 +1,28 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "QtNetwork/QtNetwork" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + QNetworkAccessManager *manager; + QNetworkRequest request; + +private slots: + void managerFinished(QNetworkReply *reply); + void on_btn_test_clicked(); + +private: + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.ui b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.ui new file mode 100644 index 0000000..f0a81bc --- /dev/null +++ b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.ui @@ -0,0 +1,55 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + 10 + 10 + 771 + 501 + + + + + + + 10 + 520 + 75 + 23 + + + + Test Button + + + + + + + 0 + 0 + 800 + 21 + + + + + + + + diff --git a/software/Qt/Lora-MQTT-test/mqtt-test/mqtt-test.pro b/software/Qt/Lora-MQTT-test/mqtt-test/mqtt-test.pro new file mode 100644 index 0000000..01ad38e --- /dev/null +++ b/software/Qt/Lora-MQTT-test/mqtt-test/mqtt-test.pro @@ -0,0 +1,31 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets network + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + mainwindow.h + +FORMS += \ + mainwindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target From 6bf7b18a4eea9cd35e52e5e294c28e5dc5c71df6 Mon Sep 17 00:00:00 2001 From: Maksym Maystrenko Date: Sun, 25 Jul 2021 20:57:11 +0100 Subject: [PATCH 4/4] Minor improvements --- software/Qt/Lora-MQTT-test/README.md | 2 +- software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/software/Qt/Lora-MQTT-test/README.md b/software/Qt/Lora-MQTT-test/README.md index fbc63c1..5d9f49b 100644 --- a/software/Qt/Lora-MQTT-test/README.md +++ b/software/Qt/Lora-MQTT-test/README.md @@ -4,7 +4,7 @@ This project reads most recent uplink LoRa packet for specified (in link) thethi Items needed in order to start: 1. Activate storage integration for your application ![Storage Integration Screenshot](guidance_screenshots/storage.png) -2. Generate and copy to the code API key with "Read application traffic (uplink and downlink)" right: +2. Generate and copy to the code API key with "Read application traffic (uplink and downlink)" right. Copy key with help of "copy" button on generation window: ![API key generation](guidance_screenshots/api_key.png) 3. Place SSL libraries libeay32.dll and ssleay32.dll into target exe folder (D:\Git\MedButton\software\Qt\Lora-MQTT-test\build-mqtt-test-Desktop_Qt_5_12_3_MinGW_64_bit-Debug\debug in my case). This is needed for https GET request. I've found dlls in by next paths in my Qt installation folder: * "D:\Qt\Tools\mingw730_64\opt\bin\libeay32.dll" diff --git a/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp index 09150aa..fa31ec3 100644 --- a/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp +++ b/software/Qt/Lora-MQTT-test/mqtt-test/mainwindow.cpp @@ -15,7 +15,7 @@ MainWindow::MainWindow(QWidget *parent) // Example application name - "first-random-app" // limit=1 - return only 1 (most recent) LoRa message request.setUrl(QUrl("https://eu1.cloud.thethings.network/api/v3/as/applications/first-random-app/packages/storage/uplink_message?limit=1")); - // API Key goes after "NNSXS." + // API Key goes after "Bearer" request.setRawHeader("Authorization" , "Bearer NNSXS.DJBXJQNTIBAYD6LR5NVPUNP64EKDH54OLFKQGSI.OODUM6B6NVTPRDHJIDLUEWMU6IY3JG35Z34OWJ5EMMUVQS5XUQRA"); request.setRawHeader("Accept" , "text/event-stream"); request.setRawHeader("Content-Type" , "text/event-stream");