Merge branch 'develop' of JF/PineTime into master

This commit is contained in:
JF 2020-05-17 10:29:13 +02:00 committed by Gitea
commit 8a94750e30
699 changed files with 229875 additions and 1219 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
project(pinetime VERSION 0.4.0 LANGUAGES C CXX ASM)
project(pinetime VERSION 0.5.0 LANGUAGES C CXX ASM)
set(NRF_TARGET "nrf52")

View file

@ -27,14 +27,16 @@ I've tested this project on the actual PineTime hardware.
* Project builds and runs on the Pinetime;
* Logs available via JLink RTT;
* SPI (DMA & IRQ based) LCD driver;
* BLE advertising, connection and bonding;
* Open source BLE stack : [NimBLE](https://github.com/apache/mynewt-nimble);
* BLE advertising and connection connection;
* BLE CTS client (retrieves the time from the connected device if it implements a CTS server);
* Push button to go to disable screen (and go to low power mode) / enable screen (and wake-up) and UI navigation
* Touch panel support;
* Rich user interface (using [LittleVGL](https://littlevgl.com/)) via display, touchpanel and push button.
* Digital watch face and 4 demo applications (spinning meter, analog gauche, push button and message box);
* Watchdog (automatic reset in case of firmware crash) and reset support (push and hold the button for 7 - 10s);
* BLE Notification support (still Work-In-Progress, [companion app](https://github.com/JF002/gobbledegook) needed).
* BLE Notification support (still Work-In-Progress, [companion app](https://github.com/JF002/gobbledegook) needed);
* Supported by companion app [Amazfish](https://openrepos.net/content/piggz/amazfish) (time synchronization and notifications are integrated).
## Documentation
@ -77,10 +79,6 @@ See [this page](./doc/PinetimeStubWithNrf52DK.md)
- -DOPENOCD_BIN_PATH=[path to openocd]
* Optionally, you can define MERGEHEX with the path to the ```mergehex``` tool from [NRF5X Command Line Tools](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf5x_cltools%2FUG%2Fcltools%2Fnrf5x_command_line_tools_lpage.html&cp=6_1) to be able to merge the application and softdevice into one HEX file. In this case the merged file is generated in src/pinetime-app-full.hex
- -DMERGEHEX=[Path to the mergehex executable]
JLINK
```
$ mkdir build
@ -116,18 +114,6 @@ $ make -j pinetime-app
$ make FLASH_ERASE
```
* Flash softdevice & application
```
$ make FLASH_SOFTDEVICE
$ make FLASH_pinetime-app
```
Or, with ```mergehex```
```
$ make FLASH_MERGED_pinetime-app
```
* For your information : list make targets :
@ -208,7 +194,7 @@ $ JLinkRTTClient
- https://github.com/eliotstock/memory : display the memory usage (FLASH/RAM) using the .map file from GCC.
## BLE connection, bonding and time synchronization
## BLE connection and time synchronization
At runtime, BLE advertising is started. You can then use a smartphone or computer to connect and bond to your Pinetime.
As soon as a device is bonded, Pinetime will look for a **CTS** server (**C**urrent **T**ime **S**ervice) on the connected device.
@ -223,7 +209,7 @@ Here is how to do it with an Android smartphone running NRFConnect:
- Select server configuration "Current Time Service" and tap OK
* Go back to the main screen and scan for BLE devices. A device called "PineTime" should appear
* Tap the button "Connect" next to the PineTime device. It should connect to the PineTime and switch to a new tab.
* On this tab, on the top right, there is a 3 dots button. Tap on it and select Bond. The bonding process begins, and if it is sucessful, the PineTime should update its time and display it on the screen.
* If a CTS server is found, the Pinetime should update its time with the time provided by the server.
### Using Linux and bluetoothctl
* Ensure that your bluetooth controller is enabled and working fine. I've tested this on a x86 Debian computer and on a RaspberryPi 3.

View file

@ -5,10 +5,6 @@ if (NOT NRF5_SDK_PATH)
message(FATAL_ERROR "The path to the nRF5 SDK (NRF5_SDK_PATH) must be set.")
endif ()
#if (NOT NRFJPROG)
# message(FATAL_ERROR "The path to the nrfjprog utility (NRFJPROG) must be set.")
#endif ()
# convert toolchain path to bin path
if(DEFINED ARM_NONE_EABI_TOOLCHAIN_PATH)
set(ARM_NONE_EABI_TOOLCHAIN_BIN_PATH ${ARM_NONE_EABI_TOOLCHAIN_PATH}/bin)
@ -70,22 +66,15 @@ macro(nRF5x_setup)
endif()
set(CPU_FLAGS "-mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16")
add_definitions(-DNRF52 -DNRF52832 -DNRF52832_XXAA -DNRF52_PAN_74 -DNRF52_PAN_64 -DNRF52_PAN_12 -DNRF52_PAN_58 -DNRF52_PAN_54 -DNRF52_PAN_31 -DNRF52_PAN_51 -DNRF52_PAN_36 -DNRF52_PAN_15 -DNRF52_PAN_20 -DNRF52_PAN_55 -DBOARD_PCA10040)
add_definitions(-DSOFTDEVICE_PRESENT -DS132 -DSWI_DISABLE0 -DBLE_STACK_SUPPORT_REQD -DNRF_SD_BLE_API_VERSION=6)
add_definitions(-DFREERTOS)
add_definitions(-DDEBUG_NRF_USER)
add_definitions(-D__STARTUP_CLEAR_BSS)
add_definitions(-D__HEAP_SIZE=8192)
add_definitions(-D__STACK_SIZE=2048)
include_directories(
"${NRF5_SDK_PATH}/components/softdevice/s132/headers"
"${NRF5_SDK_PATH}/components/softdevice/s132/headers/nrf52"
"${NRF5_SDK_PATH}/components/drivers_nrf/nrf_soc_nosd"
)
list(APPEND SDK_SOURCE_FILES
"${NRF5_SDK_PATH}/modules/nrfx/mdk/system_nrf52.c"
"${NRF5_SDK_PATH}/modules/nrfx/mdk/gcc_startup_nrf52.S"
)
set(SOFTDEVICE_PATH "${NRF5_SDK_PATH}/components/softdevice/s132/hex/s132_nrf52_6.1.1_softdevice.hex")
endif ()
set(COMMON_FLAGS "-MP -MD -mthumb -mabi=aapcs -Wall -g3 -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin --short-enums ${CPU_FLAGS} -Wreturn-type -Werror=return-type")
@ -242,7 +231,6 @@ macro(nRF5x_setup)
# Other external
include_directories(
"${NRF5_SDK_PATH}/external/fprintf/"
# "${NRF5_SDK_PATH}/external/utf_converter/"
)
list(APPEND SDK_SOURCE_FILES
@ -254,102 +242,24 @@ macro(nRF5x_setup)
# LCD/GFX
include_directories(
"${NRF5_SDK_PATH}/external/thedotfactory_fonts"
"${NRF5_SDK_PATH}/components/ble/ble_db_discovery"
)
list(APPEND SDK_SOURCE_FILES
"${NRF5_SDK_PATH}/components/ble/ble_db_discovery/ble_db_discovery.c"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_cts_c/ble_cts_c.c"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_ans_c/ble_ans_c.c"
# "${NRF5_SDK_PATH}/external/thedotfactory_fonts/orkney24pts.c"
)
#BLE S132
include_directories(
"${NRF5_SDK_PATH}/components/ble/common"
"${NRF5_SDK_PATH}/components/ble/ble_advertising"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_bas"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_hrs"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_dis"
"${NRF5_SDK_PATH}/components/ble/nrf_ble_gatt"
"${NRF5_SDK_PATH}/components/libraries/sensorsim"
"${NRF5_SDK_PATH}/components/ble/peer_manager"
"${NRF5_SDK_PATH}/components/ble/nrf_ble_qwr"
)
LIST(APPEND SDK_SOURCE_FILES
"${NRF5_SDK_PATH}//components/ble/common/ble_srv_common.c"
"${NRF5_SDK_PATH}/components/ble/ble_advertising/ble_advertising.c"
"${NRF5_SDK_PATH}/components/ble/common/ble_advdata.c"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_bas/ble_bas.c"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_hrs/ble_hrs.c"
"${NRF5_SDK_PATH}/components/ble/ble_services/ble_dis/ble_dis.c"
"${NRF5_SDK_PATH}/components/ble/nrf_ble_gatt/nrf_ble_gatt.c"
"${NRF5_SDK_PATH}/components/libraries/sensorsim/sensorsim.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/peer_manager.c"
"${NRF5_SDK_PATH}/components/ble/nrf_ble_qwr/nrf_ble_qwr.c"
"${NRF5_SDK_PATH}/components/ble/common/ble_conn_state.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/auth_status_tracker.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/gatt_cache_manager.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/gatts_cache_manager.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/id_manager.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/peer_data_storage.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/peer_database.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/peer_id.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/peer_manager.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/peer_manager_handler.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/pm_buffer.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/security_dispatcher.c"
"${NRF5_SDK_PATH}/components/ble/peer_manager/security_manager.c"
"${NRF5_SDK_PATH}/components/ble/common/ble_conn_state.c"
"${NRF5_SDK_PATH}/components/ble/common/ble_conn_params.c"
"${NRF5_SDK_PATH}/components/ble/common/ble_conn_state.c"
"${NRF5_SDK_PATH}/components/libraries/atomic_flags/nrf_atflags.c"
"${NRF5_SDK_PATH}/components/libraries/fds/fds.c"
"${NRF5_SDK_PATH}/components/libraries/fstorage/nrf_fstorage.c"
"${NRF5_SDK_PATH}/components/libraries/fstorage/nrf_fstorage_sd.c"
"${NRF5_SDK_PATH}/components/libraries/atomic_fifo/nrf_atfifo.c"
"${NRF5_SDK_PATH}/components/softdevice/common/nrf_sdh.c"
"${NRF5_SDK_PATH}/components/softdevice/common/nrf_sdh_ble.c"
"${NRF5_SDK_PATH}/components/softdevice/common/nrf_sdh_freertos.c"
"${NRF5_SDK_PATH}/components/softdevice/common/nrf_sdh_soc.c"
"${NRF5_SDK_PATH}/components/libraries/experimental_section_vars/nrf_section_iter.c"
"${NRF5_SDK_PATH}/components/libraries/bsp/bsp_btn_ble.c"
"${NRF5_SDK_PATH}/components/libraries/hardfault/hardfault_implementation.c"
"${NRF5_SDK_PATH}/components/libraries/hardfault/nrf52/handler/hardfault_handler_gcc.c"
)
LIST(APPEND SDK_SOURCE_FILES
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_twi.c"
)
# adds target for erasing and flashing the board with a softdevice
# adds target for erasing
if(USE_JLINK)
add_custom_target(FLASH_SOFTDEVICE
COMMAND ${NRFJPROG} --program ${SOFTDEVICE_PATH} -f ${NRF_TARGET} --sectorerase
COMMAND sleep 0.5s
COMMAND ${NRFJPROG} --reset -f ${NRF_TARGET}
COMMENT "flashing SoftDevice"
)
add_custom_target(FLASH_ERASE
COMMAND ${NRFJPROG} --eraseall -f ${NRF_TARGET}
COMMENT "erasing flashing"
)
elseif(USE_GDB_CLIENT)
add_custom_target(FLASH_SOFTDEVICE
COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'load' -ex 'kill' ${SOFTDEVICE_PATH}
COMMENT "flashing SoftDevice"
)
add_custom_target(FLASH_ERASE
COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'mon erase_mass'
COMMENT "erasing flashing"
)
elseif(USE_OPENOCD)
add_custom_target(FLASH_SOFTDEVICE
COMMAND ${OPENOCD_BIN_PATH} -c "tcl_port disabled" -c "gdb_port 3333" -c "telnet_port 4444" -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c "program \"${SOFTDEVICE_PATH}\"" -c reset -c shutdown
COMMENT "flashing SoftDevice"
)
add_custom_target(FLASH_ERASE
COMMAND ${OPENOCD_BIN_PATH} -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c init -c halt -c 'nrf5 mass_erase' -c reset -c shutdown
COMMENT "erasing flashing"
@ -391,35 +301,6 @@ macro(nRF5x_addExecutable EXECUTABLE_NAME SOURCE_FILES)
COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_NAME}.out "${EXECUTABLE_NAME}.hex"
COMMENT "post build steps for ${EXECUTABLE_NAME}")
if(MERGEHEX)
add_custom_command(TARGET ${EXECUTABLE_NAME}
POST_BUILD
COMMAND ${MERGEHEX} --merge ${EXECUTABLE_NAME}.hex ${NRF5_SDK_PATH}/components/softdevice/s132/hex/s132_nrf52_6.1.1_softdevice.hex --output ${EXECUTABLE_NAME}-full.hex
COMMENT "merging HEX files")
if(USE_JLINK)
add_custom_target("FLASH_MERGED_${EXECUTABLE_NAME}"
DEPENDS ${EXECUTABLE_NAME}
COMMAND ${NRFJPROG} --program ${EXECUTABLE_NAME}-full.hex -f ${NRF_TARGET} --sectorerase
COMMAND sleep 0.5s
COMMAND ${NRFJPROG} --reset -f ${NRF_TARGET}
COMMENT "flashing ${EXECUTABLE_NAME}-full.hex"
)
elseif(USE_GDB_CLIENT)
add_custom_target("FLASH_MERGED_${EXECUTABLE_NAME}"
DEPENDS ${EXECUTABLE_NAME}
COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'load' -ex 'kill' ${EXECUTABLE_NAME}-full.hex
COMMENT "flashing ${EXECUTABLE_NAME}-full.hex"
)
elseif(USE_OPENOCD)
add_custom_target("FLASH_MERGED_${EXECUTABLE_NAME}"
DEPENDS ${EXECUTABLE_NAME}
COMMAND ${OPENOCD_BIN_PATH} -c "tcl_port disabled" -c "gdb_port 3333" -c "telnet_port 4444" -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c "program \"${EXECUTABLE_NAME}-full.hex\"" -c reset -c shutdown
COMMENT "flashing ${EXECUTABLE_NAME}-full.hex"
)
endif()
endif()
# custom target for flashing the board
if(USE_JLINK)
add_custom_target("FLASH_${EXECUTABLE_NAME}"

View file

@ -19,9 +19,10 @@ If **CTS** is detected, it'll request the current time to the companion applicat
[List of standard BLE services](https://www.bluetooth.com/specifications/gatt/services/)
### CTS
[Current Time Service](https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.alert_notification.xml)
[Current Time Service](https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.current_time.xml)
### ANS
[Alert Notification Service](https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.current_time.xml)
[Alert Notification Service](https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.alert_notification.xml)
![ANS sequence diagram](./ble/ans_sequence.png "ANS sequence diagram")

View file

@ -5,8 +5,8 @@ GROUP(-lgcc -lc -lnosys)
MEMORY
{
FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0x5a000
RAM (rwx) : ORIGIN = 0x200057b8, LENGTH = 0xa848
FLASH (rx) : ORIGIN = 0x00000, LENGTH = 0x80000
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000
}
SECTIONS

View file

@ -1,780 +0,0 @@
#include <libraries/util/sdk_errors.h>
#include <softdevice/common/nrf_sdh.h>
#include <libraries/util/app_error.h>
#include <softdevice/common/nrf_sdh_ble.h>
#include <libraries/log/nrf_log.h>
#include <ble/nrf_ble_qwr/nrf_ble_qwr.h>
#include <ble/ble_services/ble_cts_c/ble_cts_c.h>
#include <ble/nrf_ble_gatt/nrf_ble_gatt.h>
#include <ble/ble_advertising/ble_advertising.h>
#include <ble/peer_manager/peer_manager.h>
#include <ble/peer_manager/peer_manager_handler.h>
#include <ble/ble_services/ble_hrs/ble_hrs.h>
#include <ble/ble_services/ble_bas/ble_bas.h>
#include <ble/ble_services/ble_dis/ble_dis.h>
#include <ble/ble_services/ble_ans_c/ble_ans_c.h>
#include <ble/common/ble_conn_params.h>
#include <libraries/fds/fds.h>
#include "nrf_sdh_soc.h"
#include "BleManager.h"
void ble_manager_init_stack();
void ble_manager_init_gap_params();
void ble_manager_init_gatt();
void ble_manager_init_db_discovery();
void ble_manager_init_advertising();
void ble_manager_init_peer_manager();
void ble_manager_init_services();
void ble_manager_init_connection_params();
void ble_manager_event_handler(ble_evt_t const *p_ble_evt, void *p_context);
void ble_manager_discover_handler(ble_db_discovery_evt_t *p_evt);
void ble_manager_advertising_event_handler(ble_adv_evt_t ble_adv_evt);
void ble_manager_peer_manager_event_handler(pm_evt_t const *p_evt);
void ble_manager_delete_bonds();
void ble_manager_queue_write_error_handler(uint32_t nrf_error);
void ble_manager_cts_event_handler(ble_cts_c_t *p_cts, ble_cts_c_evt_t *p_evt);
void ble_manager_cts_error_handler(uint32_t nrf_error);
void ble_manager_cts_print_time(ble_cts_c_evt_t *p_evt);
void ble_manager_conn_params_event_handler(ble_conn_params_evt_t *p_evt);
void ble_manager_conn_params_error_handler(uint32_t nrf_error);
typedef enum
{
ALERT_NOTIFICATION_DISABLED, /**< Alert Notifications has been disabled. */
ALERT_NOTIFICATION_ENABLED, /**< Alert Notifications has been enabled. */
ALERT_NOTIFICATION_ON, /**< Alert State is on. */
} ble_ans_c_alert_state_t;
void on_ans_c_evt(ble_ans_c_evt_t * p_evt);
void alert_notification_error_handler(uint32_t nrf_error);
void handle_alert_notification(ble_ans_c_evt_t * p_evt);
void supported_alert_notification_read(void);
void alert_notification_setup(void);
void control_point_setup(ble_ans_c_evt_t * p_evt);
uint16_t ble_manager_connection_handle = BLE_CONN_HANDLE_INVALID; // Handle of the current connection.
NRF_BLE_QWR_DEF(ble_manager_queue_write); // Context for the Queued Write module.
BLE_CTS_C_DEF(ble_manager_cts_client); // Current Time service instance.
NRF_BLE_GATT_DEF(ble_manager_gatt); // GATT module instance.
BLE_ADVERTISING_DEF(ble_manager_advertising); // Advertising module instance.
BLE_DB_DISCOVERY_DEF(ble_manager_db_discovery);
BLE_ANS_C_DEF(m_ans_c);
static uint8_t m_alert_message_buffer[MESSAGE_BUFFER_SIZE]; /**< Message buffer for optional notify messages. */
static ble_ans_c_alert_state_t m_new_alert_state = ALERT_NOTIFICATION_DISABLED; /**< State that holds the current state of New Alert Notifications, i.e. Enabled, Alert On, Disabled. */
static ble_ans_c_alert_state_t m_unread_alert_state = ALERT_NOTIFICATION_DISABLED; /**< State that holds the current state of Unread Alert Notifications, i.e. Enabled, Alert On, Disabled. */
static ble_uuid_t ble_manager_advertising_uuids[] = /* Universally unique service identifiers.*/
{
// {BLE_UUID_HEART_RATE_SERVICE, BLE_UUID_TYPE_BLE},
// {BLE_UUID_BATTERY_SERVICE, BLE_UUID_TYPE_BLE},
{BLE_UUID_DEVICE_INFORMATION_SERVICE, BLE_UUID_TYPE_BLE},
{BLE_UUID_CURRENT_TIME_SERVICE, BLE_UUID_TYPE_BLE}
};
static char const *day_of_week[] =
{
"Unknown",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};
static char const *month_of_year[] =
{
"Unknown",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
static char const * lit_catid[BLE_ANS_NB_OF_CATEGORY_ID] =
{
"Simple alert",
"Email",
"News",
"Incoming call",
"Missed call",
"SMS/MMS",
"Voice mail",
"Schedule",
"High prioritized alert",
"Instant message"
};
void ble_manager_init() {
ble_manager_init_stack();
ble_manager_init_gap_params();
ble_manager_init_gatt();
ble_manager_init_db_discovery();
ble_manager_init_advertising();
ble_manager_init_services();
ble_manager_init_connection_params();
}
void ble_manager_init_stack() {
ret_code_t err_code;
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
// Configure the BLE stack using the default settings.
// Fetch the start address of the application RAM.
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(BLE_MANAGER_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
// Enable BLE stack.
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
// Register a handler for BLE events.
NRF_SDH_BLE_OBSERVER(m_ble_observer, BLE_MANAGER__OBSERVER_PRIO, ble_manager_event_handler, NULL);
}
void (*OnNewTimeCallback)(current_time_char_t*);
void ble_manager_set_new_time_callback(void (*OnNewTime)(current_time_char_t*)) {
OnNewTimeCallback = OnNewTime;
}
void (*OnBleConnectionCallback)();
void ble_manager_set_ble_connection_callback(void (*OnBleConnection)()) {
OnBleConnectionCallback = OnBleConnection;
}
void (*OnBleDisconnectionCallback)();
void ble_manager_set_ble_disconnection_callback(void (*OnBleDisconnection)()) {
OnBleDisconnectionCallback = OnBleDisconnection;
}
void (*OnNewNotificationCallback)(const char* message, uint8_t size);
void ble_manager_set_new_notification_callback(void (*OnNewNotification)(const char*, uint8_t size)) {
OnNewNotificationCallback = OnNewNotification;
}
void ble_manager_event_handler(ble_evt_t const *p_ble_evt, void *p_context) {
uint32_t err_code;
switch (p_ble_evt->header.evt_id) {
case BLE_GAP_EVT_CONNECTED:
NRF_LOG_INFO("[BLE] Connected to peer");
ble_manager_connection_handle = p_ble_evt->evt.gap_evt.conn_handle;
err_code = nrf_ble_qwr_conn_handle_assign(&ble_manager_queue_write, ble_manager_connection_handle);
OnBleConnectionCallback();
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_DISCONNECTED:
NRF_LOG_INFO("[Ble] Disconnected from peer]");
ble_manager_connection_handle = BLE_CONN_HANDLE_INVALID;
if (p_ble_evt->evt.gap_evt.conn_handle == ble_manager_cts_client.conn_handle) {
ble_manager_cts_client.conn_handle = BLE_CONN_HANDLE_INVALID;
}
OnBleDisconnectionCallback();
break;
case BLE_GAP_EVT_PHY_UPDATE_REQUEST: {
NRF_LOG_INFO("[BLE] PHY update request.");
ble_gap_phys_t const phys =
{
.tx_phys = BLE_GAP_PHY_AUTO,
.rx_phys = BLE_GAP_PHY_AUTO,
};
err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
APP_ERROR_CHECK(err_code);
}
break;
case BLE_GATTC_EVT_TIMEOUT:
// Disconnect on GATT Client timeout event.
NRF_LOG_INFO("[BLE] GATT Client Timeout.");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTS_EVT_TIMEOUT:
// Disconnect on GATT Server timeout event.
NRF_LOG_INFO("[BLE] GATT Server Timeout.");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
default:
// No implementation needed.
break;
}
}
void ble_manager_init_gap_params() {
ret_code_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
err_code = sd_ble_gap_device_name_set(&sec_mode, (const uint8_t *) BLE_MANAGER_DEVICE_NAME,
strlen(BLE_MANAGER_DEVICE_NAME));
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT);
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
gap_conn_params.min_conn_interval = BLE_MANAGER_MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = BLE_MANAGER_MAX_CONN_INTERVAL;
gap_conn_params.slave_latency = BLE_MANAGER_SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout = BLE_MANAGER_CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
}
void ble_manager_init_gatt() {
ret_code_t err_code = nrf_ble_gatt_init(&ble_manager_gatt, NULL);
APP_ERROR_CHECK(err_code);
}
void ble_manager_init_db_discovery() {
ret_code_t err_code = ble_db_discovery_init(ble_manager_discover_handler);
APP_ERROR_CHECK(err_code);
}
void ble_manager_discover_handler(ble_db_discovery_evt_t *p_evt) {
ble_cts_c_on_db_disc_evt(&ble_manager_cts_client, p_evt);
ble_ans_c_on_db_disc_evt(&m_ans_c, p_evt);
}
void ble_manager_init_advertising() {
ret_code_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = true;
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
init.advdata.uuids_complete.uuid_cnt =
sizeof(ble_manager_advertising_uuids) / sizeof(ble_manager_advertising_uuids[0]);
init.advdata.uuids_complete.p_uuids = ble_manager_advertising_uuids;
init.config.ble_adv_whitelist_enabled = true;
init.config.ble_adv_fast_enabled = true;
init.config.ble_adv_fast_interval = BLE_MANAGER_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = BLE_MANAGER_ADV_DURATION;
init.evt_handler = ble_manager_advertising_event_handler;
err_code = ble_advertising_init(&ble_manager_advertising, &init);
APP_ERROR_CHECK(err_code);
ble_advertising_conn_cfg_tag_set(&ble_manager_advertising, BLE_MANAGER_CONN_CFG_TAG);
}
void ble_manager_advertising_event_handler(ble_adv_evt_t ble_adv_evt) {
uint32_t err_code;
switch (ble_adv_evt) {
case BLE_ADV_EVT_FAST:
NRF_LOG_INFO("[Advertising] Fast advertising started.");
break;
case BLE_ADV_EVT_IDLE:
NRF_LOG_INFO("[Advertising] Idling...");
break;
default:
break;
}
}
bool record_delete_next(void)
{
fds_find_token_t tok = {0};
fds_record_desc_t desc = {0};
if (fds_record_iterate(&desc, &tok) == FDS_SUCCESS)
{
ret_code_t rc = fds_record_delete(&desc);
if (rc != FDS_SUCCESS)
{
return false;
}
return true;
}
else
{
/* No records left to delete. */
return false;
}
}
void ble_manager_init_peer_manager() {
ble_gap_sec_params_t sec_param;
ret_code_t err_code;
err_code = pm_init();
if(err_code != NRF_SUCCESS) {
// Many errors can occur here, but the most probable is the "Storage full" error from sdk/components/libraries/fds/fds.c : FDS_ERR_NO_PAGES.
// TODO : it erases the whole memory, it's not nice to do that...
NRF_LOG_WARNING("Error while initializing BLE peer management, Erasing all record from memory");
do {
} while(record_delete_next());
err_code = pm_init();
}
APP_ERROR_CHECK(err_code);
memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));
// Security parameters to be used for all security procedures.
sec_param.bond = BLE_MANAGER_SEC_PARAM_BOND;
sec_param.mitm = BLE_MANAGER_SEC_PARAM_MITM;
sec_param.lesc = BLE_MANAGER_SEC_PARAM_LESC;
sec_param.keypress = BLE_MANAGER_SEC_PARAM_KEYPRESS;
sec_param.io_caps = BLE_MANAGER_SEC_PARAM_IO_CAPABILITIES;
sec_param.oob = BLE_MANAGER_SEC_PARAM_OOB;
sec_param.min_key_size = BLE_MANAGER_SEC_PARAM_MIN_KEY_SIZE;
sec_param.max_key_size = BLE_MANAGER_SEC_PARAM_MAX_KEY_SIZE;
sec_param.kdist_own.enc = 1;
sec_param.kdist_own.id = 1;
sec_param.kdist_peer.enc = 1;
sec_param.kdist_peer.id = 1;
err_code = pm_sec_params_set(&sec_param);
APP_ERROR_CHECK(err_code);
err_code = pm_register(ble_manager_peer_manager_event_handler);
APP_ERROR_CHECK(err_code);
}
void ble_manager_peer_manager_event_handler(pm_evt_t const *p_evt) {
bool delete_bonds = false;
ret_code_t err_code;
pm_handler_on_pm_evt(p_evt);
pm_handler_flash_clean(p_evt);
switch (p_evt->evt_id) {
case PM_EVT_CONN_SEC_SUCCEEDED: {
NRF_LOG_INFO("[Peer management] A link has been secured, starting service discovery.");
err_code = ble_db_discovery_start(&ble_manager_db_discovery, p_evt->conn_handle);
APP_ERROR_CHECK(err_code);
}
break;
case PM_EVT_PEERS_DELETE_SUCCEEDED:
NRF_LOG_INFO("[Peer management] All peers data has been successfuly deleted.");
ble_manager_start_advertising(&delete_bonds);
break;
case PM_EVT_STORAGE_FULL: {
NRF_LOG_INFO("[Peer management] Storage full, trying to run garbage collection on flash storage.");
// Run garbage collection on the flash.
err_code = fds_gc();
if (err_code == FDS_ERR_BUSY || err_code == FDS_ERR_NO_SPACE_IN_QUEUES)
{
NRF_LOG_INFO("[Peer management] Garbage collection issue.");
// Retry.
}
else
{
APP_ERROR_CHECK(err_code);
NRF_LOG_INFO("[Peer management] Garbage collection done.");
}
}break;
default:
break;
}
}
void ble_manager_start_advertising(void *p_erase_bonds) {
bool erase_bonds = *(bool *) p_erase_bonds;
if (erase_bonds) {
ble_manager_delete_bonds();
// Advertising is started by PM_EVT_PEERS_DELETE_SUCCEEDED event.
} else {
ret_code_t err_code = ble_advertising_start(&ble_manager_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
}
}
void handle_alert_notification(ble_ans_c_evt_t * p_evt)
{
ret_code_t err_code;
if (p_evt->uuid.uuid == BLE_UUID_UNREAD_ALERT_CHAR)
{
if (m_unread_alert_state == ALERT_NOTIFICATION_ENABLED)
{
// err_code = bsp_indication_set(BSP_INDICATE_ALERT_1);
APP_ERROR_CHECK(err_code);
m_unread_alert_state = ALERT_NOTIFICATION_ON;
NRF_LOG_INFO("Unread Alert state: On.");
NRF_LOG_INFO(" Category: %s",
(uint32_t)lit_catid[p_evt->data.alert.alert_category]);
NRF_LOG_INFO(" Number of unread alerts: %d",
p_evt->data.alert.alert_category_count);
}
}
else if (p_evt->uuid.uuid == BLE_UUID_NEW_ALERT_CHAR)
{
m_new_alert_state = ALERT_NOTIFICATION_ON;
NRF_LOG_INFO("New Alert state: On.");
NRF_LOG_INFO(" Category: %s",
(uint32_t)lit_catid[p_evt->data.alert.alert_category]);
NRF_LOG_INFO(" Number of new alerts: %d",
p_evt->data.alert.alert_category_count);
NRF_LOG_INFO(" Text String Information: (%d) %s",
p_evt->data.alert.alert_msg_length, (uint32_t)p_evt->data.alert.p_alert_msg_buf);
OnNewNotificationCallback(p_evt->data.alert.p_alert_msg_buf, p_evt->data.alert.alert_msg_length);
}
else
{
// Only Unread and New Alerts exists, thus do nothing.
}
}
void supported_alert_notification_read(void)
{
NRF_LOG_INFO("Read supported Alert Notification characteristics on the connected peer.");
ret_code_t err_code;
err_code = ble_ans_c_new_alert_read(&m_ans_c);
APP_ERROR_CHECK(err_code);
err_code = ble_ans_c_unread_alert_read(&m_ans_c);
APP_ERROR_CHECK(err_code);
}
void alert_notification_setup(void)
{
ret_code_t err_code;
err_code = ble_ans_c_enable_notif_new_alert(&m_ans_c);
APP_ERROR_CHECK(err_code);
m_new_alert_state = ALERT_NOTIFICATION_ENABLED;
NRF_LOG_INFO("New Alert State: Enabled.");
err_code = ble_ans_c_enable_notif_unread_alert(&m_ans_c);
APP_ERROR_CHECK(err_code);
m_unread_alert_state = ALERT_NOTIFICATION_ENABLED;
NRF_LOG_INFO("Unread Alert State: Enabled.");
NRF_LOG_INFO("Notifications enabled.");
}
void control_point_setup(ble_ans_c_evt_t * p_evt)
{
uint32_t err_code;
ble_ans_control_point_t setting;
if (p_evt->uuid.uuid == BLE_UUID_SUPPORTED_UNREAD_ALERT_CATEGORY_CHAR)
{
setting.command = ANS_ENABLE_UNREAD_CATEGORY_STATUS_NOTIFICATION;
setting.category = (ble_ans_category_id_t)p_evt->data.alert.alert_category;
NRF_LOG_INFO("Unread status notification enabled for received categories.");
}
else if (p_evt->uuid.uuid == BLE_UUID_SUPPORTED_NEW_ALERT_CATEGORY_CHAR)
{
setting.command = ANS_ENABLE_NEW_INCOMING_ALERT_NOTIFICATION;
setting.category = (ble_ans_category_id_t)p_evt->data.alert.alert_category;
NRF_LOG_INFO("New incoming notification enabled for received categories.");
}
else
{
return;
}
err_code = ble_ans_c_control_point_write(&m_ans_c, &setting);
APP_ERROR_CHECK(err_code);
}
void on_ans_c_evt(ble_ans_c_evt_t * p_evt)
{
ret_code_t err_code;
switch (p_evt->evt_type) {
case BLE_ANS_C_EVT_DISCOVERY_FAILED:
// TODO When another service is found, this event is sent to all the other service handled.
// In this case, this is not an error, it just tells that the service that have just been found is not this one...
NRF_LOG_INFO("[ANS] Discovery failed");
break;
case BLE_ANS_C_EVT_NOTIFICATION:
handle_alert_notification(p_evt);
NRF_LOG_INFO("[ANS] Alert Notification received from server, UUID: %X.", p_evt->uuid.uuid);
break; // BLE_ANS_C_EVT_NOTIFICATION
case BLE_ANS_C_EVT_DISCOVERY_COMPLETE:
NRF_LOG_INFO("[ANS] Alert Notification Service discovered on the server.");
err_code = ble_ans_c_handles_assign(&m_ans_c,
p_evt->conn_handle,
&p_evt->data.service);
APP_ERROR_CHECK(err_code);
supported_alert_notification_read();
alert_notification_setup();
break; // BLE_ANS_C_EVT_DISCOVERY_COMPLETE
case BLE_ANS_C_EVT_READ_RESP:
NRF_LOG_INFO("[ANS] Alert Setup received from server, UUID: %X.", p_evt->uuid.uuid);
control_point_setup(p_evt);
break; // BLE_ANS_C_EVT_READ_RESP
case BLE_ANS_C_EVT_DISCONN_COMPLETE:
NRF_LOG_INFO("[ANS] ANS : disconnecting from server");
m_new_alert_state = ALERT_NOTIFICATION_DISABLED;
m_unread_alert_state = ALERT_NOTIFICATION_DISABLED;
break; // BLE_ANS_C_EVT_DISCONN_COMPLETE
default:
// No implementation needed.
break;
}
}
void alert_notification_error_handler(uint32_t nrf_error)
{
APP_ERROR_HANDLER(nrf_error);
}
void ble_manager_init_services() {
ret_code_t err_code;
ble_hrs_init_t hrs_init;
ble_bas_init_t bas_init;
ble_dis_init_t dis_init;
ble_cts_c_init_t cts_init;
ble_ans_c_init_t ans_init_obj;
nrf_ble_qwr_init_t qwr_init = {0};
uint8_t body_sensor_location;
// Initialize Queued Write Module.
qwr_init.error_handler = ble_manager_queue_write_error_handler;
err_code = nrf_ble_qwr_init(&ble_manager_queue_write, &qwr_init);
APP_ERROR_CHECK(err_code);
// Initialize Heart Rate Service.
body_sensor_location = BLE_HRS_BODY_SENSOR_LOCATION_FINGER;
memset(&hrs_init, 0, sizeof(hrs_init));
hrs_init.evt_handler = NULL;
hrs_init.is_sensor_contact_supported = true;
hrs_init.p_body_sensor_location = &body_sensor_location;
// Here the sec level for the Heart Rate Service can be changed/increased.
hrs_init.hrm_cccd_wr_sec = SEC_OPEN;
hrs_init.bsl_rd_sec = SEC_OPEN;
// Initialize Battery Service.
// memset(&bas_init, 0, sizeof(bas_init));
//
// // Here the sec level for the Battery Service can be changed/increased.
// bas_init.bl_rd_sec = SEC_OPEN;
// bas_init.bl_cccd_wr_sec = SEC_OPEN;
// bas_init.bl_report_rd_sec = SEC_OPEN;
//
// bas_init.evt_handler = NULL;
// bas_init.support_notification = true;
// bas_init.p_report_ref = NULL;
// bas_init.initial_batt_level = 100;
//
// err_code = ble_bas_init(&m_bas, &bas_init);
// APP_ERROR_CHECK(err_code);
// Initialize Device Information Service.
memset(&dis_init, 0, sizeof(dis_init));
ble_srv_ascii_to_utf8(&dis_init.manufact_name_str, (char *) BLE_MANAGER_MANUFACTURER_NAME);
dis_init.dis_char_rd_sec = SEC_OPEN;
err_code = ble_dis_init(&dis_init);
APP_ERROR_CHECK(err_code);
// Initialize CTS.
cts_init.evt_handler = ble_manager_cts_event_handler;
cts_init.error_handler = ble_manager_cts_error_handler;
err_code = ble_cts_c_init(&ble_manager_cts_client, &cts_init);
APP_ERROR_CHECK(err_code);
// Alert Notification service
memset(&ans_init_obj, 0, sizeof(ans_init_obj));
memset(m_alert_message_buffer, 0, MESSAGE_BUFFER_SIZE);
ans_init_obj.evt_handler = on_ans_c_evt;
ans_init_obj.message_buffer_size = MESSAGE_BUFFER_SIZE;
ans_init_obj.p_message_buffer = m_alert_message_buffer;
ans_init_obj.error_handler = alert_notification_error_handler;
err_code = ble_ans_c_init(&m_ans_c, &ans_init_obj);
APP_ERROR_CHECK(err_code);
}
void ble_manager_queue_write_error_handler(uint32_t nrf_error) {
APP_ERROR_HANDLER(nrf_error);
}
void ble_manager_cts_event_handler(ble_cts_c_t *p_cts, ble_cts_c_evt_t *p_evt) {
ret_code_t err_code;
switch (p_evt->evt_type) {
case BLE_CTS_C_EVT_DISCOVERY_COMPLETE:
NRF_LOG_INFO("[CTS] Current Time Service discovered on server, requesting current time...");
err_code = ble_cts_c_handles_assign(&ble_manager_cts_client,
p_evt->conn_handle,
&p_evt->params.char_handles);
ble_cts_c_current_time_read(&ble_manager_cts_client);
APP_ERROR_CHECK(err_code);
break;
case BLE_CTS_C_EVT_DISCOVERY_FAILED:
// TODO When another service is found, this event is sent to all the other service handled.
// In this case, this is not an error, it just tells that the service that have just been found is not this one...
NRF_LOG_INFO("[CTS] Current Time Service not found on server.");
break;
case BLE_CTS_C_EVT_DISCONN_COMPLETE:
NRF_LOG_INFO("[CTS] Disconnect Complete.");
break;
case BLE_CTS_C_EVT_CURRENT_TIME:
NRF_LOG_INFO("[CTS] Current Time received.");
ble_manager_cts_print_time(p_evt);
break;
case BLE_CTS_C_EVT_INVALID_TIME:
NRF_LOG_INFO("[CTS] Invalid Time received.");
break;
default:
break;
}
}
void ble_manager_cts_error_handler(uint32_t nrf_error) {
APP_ERROR_HANDLER(nrf_error);
}
void ble_manager_cts_print_time(ble_cts_c_evt_t *p_evt) {
NRF_LOG_INFO("\r\nCurrent Time:");
NRF_LOG_INFO("\r\nDate:");
NRF_LOG_INFO("\tDay of week %s", (uint32_t) day_of_week[p_evt->
params.
current_time.
exact_time_256.
day_date_time.
day_of_week]);
if (p_evt->params.current_time.exact_time_256.day_date_time.date_time.day == 0) {
NRF_LOG_INFO("\tDay of month Unknown");
} else {
NRF_LOG_INFO("\tDay of month %i",
p_evt->params.current_time.exact_time_256.day_date_time.date_time.day);
}
NRF_LOG_INFO("\tMonth of year %s",
(uint32_t) month_of_year[p_evt->params.current_time.exact_time_256.day_date_time.date_time.month]);
if (p_evt->params.current_time.exact_time_256.day_date_time.date_time.year == 0) {
NRF_LOG_INFO("\tYear Unknown");
} else {
NRF_LOG_INFO("\tYear %i",
p_evt->params.current_time.exact_time_256.day_date_time.date_time.year);
}
NRF_LOG_INFO("\r\nTime:");
NRF_LOG_INFO("\tHours %i",
p_evt->params.current_time.exact_time_256.day_date_time.date_time.hours);
NRF_LOG_INFO("\tMinutes %i",
p_evt->params.current_time.exact_time_256.day_date_time.date_time.minutes);
NRF_LOG_INFO("\tSeconds %i",
p_evt->params.current_time.exact_time_256.day_date_time.date_time.seconds);
NRF_LOG_INFO("\tFractions %i/256 of a second",
p_evt->params.current_time.exact_time_256.fractions256);
NRF_LOG_INFO("\r\nAdjust reason:\r");
NRF_LOG_INFO("\tDaylight savings %x",
p_evt->params.current_time.adjust_reason.change_of_daylight_savings_time);
NRF_LOG_INFO("\tTime zone %x",
p_evt->params.current_time.adjust_reason.change_of_time_zone);
NRF_LOG_INFO("\tExternal update %x",
p_evt->params.current_time.adjust_reason.external_reference_time_update);
NRF_LOG_INFO("\tManual update %x",
p_evt->params.current_time.adjust_reason.manual_time_update);
OnNewTimeCallback(&p_evt->params.current_time);
}
void ble_manager_init_connection_params() {
ret_code_t err_code;
ble_conn_params_init_t cp_init;
memset(&cp_init, 0, sizeof(cp_init));
cp_init.p_conn_params = NULL;
cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;
cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;
cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;
cp_init.disconnect_on_fail = false;
cp_init.evt_handler = ble_manager_conn_params_event_handler;
cp_init.error_handler = ble_manager_conn_params_error_handler;
err_code = ble_conn_params_init(&cp_init);
APP_ERROR_CHECK(err_code);
}
void ble_manager_conn_params_event_handler(ble_conn_params_evt_t *p_evt) {
ret_code_t err_code;
if(p_evt->evt_type == BLE_CONN_PARAMS_EVT_SUCCEEDED) {
NRF_LOG_INFO("BLE connection parameters negotiation successful!");
} else if(p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED) {
NRF_LOG_ERROR("BLE connection parameters negotiation error, disconnecting.");
err_code = sd_ble_gap_disconnect(ble_manager_connection_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
APP_ERROR_CHECK(err_code);
}
}
void ble_manager_conn_params_error_handler(uint32_t nrf_error) {
APP_ERROR_HANDLER(nrf_error);
}
void ble_manager_delete_bonds() {
ret_code_t err_code;
NRF_LOG_INFO("Erase bonds!");
err_code = pm_peers_delete();
APP_ERROR_CHECK(err_code);
}

View file

@ -1,51 +0,0 @@
#pragma once
#include <ble/ble_services/ble_cts_c/ble_cts_c.h>
#ifdef __cplusplus
extern "C" {
#endif
#define BLE_MANAGER_CONN_CFG_TAG 1 /* A tag identifying the SoftDevice BLE configuration. */
#define BLE_MANAGER__OBSERVER_PRIO 3 /* Application's BLE observer priority. You shouldn't need to modify this value. */
#define BLE_MANAGER_DEVICE_NAME "PineTime" /* Name of device. Will be included in the advertising data.*/
#define BLE_MANAGER_MANUFACTURER_NAME "Codingfield"
#define BLE_MANAGER_MIN_CONN_INTERVAL MSEC_TO_UNITS(100, UNIT_1_25_MS) /* Minimum acceptable connection interval (0.4 seconds).*/
#define BLE_MANAGER_MAX_CONN_INTERVAL MSEC_TO_UNITS(650, UNIT_1_25_MS) /*Maximum acceptable connection interval (0.65 second).*/
#define BLE_MANAGER_SLAVE_LATENCY 0 /* Slave latency.*/
#define BLE_MANAGER_CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) /* Connection supervisory time-out (4 seconds).*/
#define BLE_MANAGER_ADV_INTERVAL 300 /* The advertising interval (in units of 0.625 ms. This value corresponds to 187.5 ms).*/
#define BLE_MANAGER_ADV_DURATION 18000 /* The advertising duration (180 seconds) in units of 10 milliseconds.*/
#define BLE_MANAGER_SEC_PARAM_BOND 1 /* Perform bonding. */
#define BLE_MANAGER_SEC_PARAM_MITM 0 /* Man In The Middle protection not required. */
#define BLE_MANAGER_SEC_PARAM_LESC 0 /* LE Secure Connections not enabled. */
#define BLE_MANAGER_SEC_PARAM_KEYPRESS 0 /* Keypress notifications not enabled. */
#define BLE_MANAGER_SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_NONE /* No I/O capabilities. */
#define BLE_MANAGER_SEC_PARAM_OOB 0 /* Out Of Band data not available. */
#define BLE_MANAGER_SEC_PARAM_MIN_KEY_SIZE 7 /* Minimum encryption key size. */
#define BLE_MANAGER_SEC_PARAM_MAX_KEY_SIZE 16 /* Maximum encryption key size. */
#define FIRST_CONN_PARAMS_UPDATE_DELAY 5000 /* Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
#define NEXT_CONN_PARAMS_UPDATE_DELAY 30000 /* Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
#define MAX_CONN_PARAMS_UPDATE_COUNT 3 /* Number of attempts before giving up the connection parameter negotiation. */
#define MESSAGE_BUFFER_SIZE 18 /**< Size of buffer holding optional messages in notifications. */
#define BLE_ANS_NB_OF_CATEGORY_ID 10 /**< Number of categories. */
void ble_manager_init();
void ble_manager_start_advertising(void *p_erase_bonds);
void ble_manager_init_peer_manager();
// TODO use signals from RTOS to notify new time
void ble_manager_set_new_time_callback(void (*OnNewTime)(current_time_char_t* currentTime));
void ble_manager_set_ble_disconnection_callback(void (*OnBleDisconnection)());
void ble_manager_set_ble_connection_callback(void (*OnBleConnection)());
void ble_manager_set_new_notification_callback(void (*OnNewNotification)(const char* message, uint8_t size));
#ifdef __cplusplus
}
#endif

View file

@ -23,11 +23,101 @@ nRF5x_addAppGpiote()
add_definitions(-DCONFIG_GPIO_AS_PINRESET)
add_definitions(-DDEBUG)
add_definitions(-DNIMBLE_CFG_CONTROLLER)
add_definitions(-DOS_CPUTIME_FREQ)
include_directories(.)
include_directories(libs/)
set(TINYCRYPT_SRC
libs/mynewt-nimble/ext/tinycrypt/src/aes_encrypt.c
libs/mynewt-nimble/ext/tinycrypt/src/utils.c
)
set(NIMBLE_SRC
libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c
libs/mynewt-nimble/porting/npl/freertos/src/npl_os_freertos.c
libs/mynewt-nimble/nimble/host/src/ble_hs.c
libs/mynewt-nimble/nimble/host/src/ble_hs_hci_evt.c
libs/mynewt-nimble/nimble/host/src/ble_l2cap_sig_cmd.c
libs/mynewt-nimble/nimble/host/src/ble_l2cap_sig.c
libs/mynewt-nimble/nimble/host/src/ble_l2cap.c
libs/mynewt-nimble/nimble/host/src/ble_hs_mbuf.c
libs/mynewt-nimble/nimble/host/src/ble_sm.c
libs/mynewt-nimble/nimble/host/src/ble_gap.c
libs/mynewt-nimble/nimble/host/src/ble_gatts.c
libs/mynewt-nimble/nimble/host/src/ble_gattc.c
libs/mynewt-nimble/nimble/host/src/ble_hs_conn.c
libs/mynewt-nimble/nimble/host/src/ble_att_svr.c
libs/mynewt-nimble/nimble/host/src/ble_store.c
libs/mynewt-nimble/nimble/host/src/ble_store_util.c
libs/mynewt-nimble/nimble/host/src/ble_hs_pvcy.c
libs/mynewt-nimble/nimble/host/src/ble_hs_hci.c
libs/mynewt-nimble/nimble/host/src/ble_hs_log.c
libs/mynewt-nimble/nimble/host/src/ble_hs_hci_util.c
libs/mynewt-nimble/nimble/host/src/ble_hs_hci_cmd.c
libs/mynewt-nimble/nimble/host/src/ble_hs_cfg.c
libs/mynewt-nimble/nimble/host/src/ble_uuid.c
libs/mynewt-nimble/nimble/host/src/ble_hs_id.c
libs/mynewt-nimble/nimble/host/src/ble_hs_misc.c
libs/mynewt-nimble/nimble/host/src/ble_att.c
libs/mynewt-nimble/nimble/host/src/ble_att_clt.c
libs/mynewt-nimble/nimble/host/src/ble_att_svr.c
libs/mynewt-nimble/nimble/host/src/ble_att_cmd.c
libs/mynewt-nimble/nimble/host/src/ble_hs_atomic.c
libs/mynewt-nimble/nimble/host/src/ble_hs_adv.c
libs/mynewt-nimble/nimble/host/src/ble_hs_flow.c
libs/mynewt-nimble/nimble/host/src/ble_sm.c
libs/mynewt-nimble/nimble/host/src/ble_sm_cmd.c
libs/mynewt-nimble/nimble/host/src/ble_sm_lgcy.c
libs/mynewt-nimble/nimble/host/src/ble_sm_alg.c
libs/mynewt-nimble/nimble/host/src/ble_hs_mqueue.c
libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c
libs/mynewt-nimble/nimble/host/src/ble_hs_startup.c
libs/mynewt-nimble/nimble/host/store/ram/src/ble_store_ram.c
libs/mynewt-nimble/nimble/transport/ram/src/ble_hci_ram.c
libs/mynewt-nimble/nimble/controller/src/ble_ll.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_rand.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_conn.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_ctrl.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_hci.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_conn_hci.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_utils.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_scan.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_whitelist.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_adv.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_sched.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_supp_cmd.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_hci_ev.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_rfmgmt.c
libs/mynewt-nimble/porting/nimble/src/os_cputime.c
libs/mynewt-nimble/porting/nimble/src/os_cputime_pwr2.c
libs/mynewt-nimble/porting/nimble/src/os_mbuf.c
libs/mynewt-nimble/porting/nimble/src/os_mempool.c
libs/mynewt-nimble/porting/nimble/src/hal_timer.c
libs/mynewt-nimble/porting/nimble/src/mem.c
libs/mynewt-nimble/porting/nimble/src/endian.c
libs/mynewt-nimble/porting/nimble/src/os_msys_init.c
libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_hw.c
libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c
libs/mynewt-nimble/nimble/host/services/gap/src/ble_svc_gap.c
libs/mynewt-nimble/nimble/host/services/gatt/src/ble_svc_gatt.c
libs/mynewt-nimble/nimble/host/util/src/addr.c
)
set(LVGL_SRC
libs/lv_conf.h
libs/lvgl/lvgl.h
@ -220,16 +310,23 @@ list(APPEND SOURCE_FILES
drivers/SpiMaster.cpp
drivers/Watchdog.cpp
drivers/DebugPins.cpp
BLE/BleManager.c
Components/Battery/BatteryController.cpp
Components/Ble/BleController.cpp
Components/Ble/NotificationManager.cpp
Components/DateTime/DateTimeController.cpp
Components/Brightness/BrightnessController.cpp
Components/Ble/NimbleController.cpp
Components/Ble/DeviceInformationService.cpp
Components/Ble/CurrentTimeClient.cpp
Components/Ble/AlertNotificationClient.cpp
Components/Ble/CurrentTimeService.cpp
Components/Ble/AlertNotificationService.cpp
drivers/Cst816s.cpp
FreeRTOS/port.c
FreeRTOS/port_cmsis_systick.c
FreeRTOS/port_cmsis.c
${TINYCRYPT_SRC}
${NIMBLE_SRC}
${LVGL_SRC}
${IMAGE_FILES}
@ -262,12 +359,15 @@ set(INCLUDE_FILES
drivers/SpiMaster.h
drivers/Watchdog.h
drivers/DebugPins.h
BLE/BleManager.h
Components/Battery/BatteryController.h
Components/Ble/BleController.h
Components/Ble/NotificationManager.h
Components/DateTime/DateTimeController.h
Components/Brightness/BrightnessController.h
Components/Ble/NimbleController.h
Components/Ble/DeviceInformationService.h
Components/Ble/CurrentTimeClient.h
Components/Ble/AlertNotificationClient.h
drivers/Cst816s.h
FreeRTOS/portmacro.h
FreeRTOS/portmacro_cmsis.h
@ -288,6 +388,18 @@ set(INCLUDE_FILES
include_directories(
FreeRTOS/
libs/date/includes
libs/mynewt-nimble/porting/npl/freertos/include
libs/mynewt-nimble/nimble/include
libs/mynewt-nimble/porting/nimble/include
libs/mynewt-nimble/nimble/host/include
libs/mynewt-nimble/nimble/controller/include
libs/mynewt-nimble/nimble/transport/ram/include
libs/mynewt-nimble/nimble/drivers/nrf52/include
libs/mynewt-nimble/ext/tinycrypt/include
libs/mynewt-nimble/nimble/host/services/gap/include
libs/mynewt-nimble/nimble/host/services/gatt/include
libs/mynewt-nimble/nimble/host/util/include
libs/mynewt-nimble/nimble/host/store/ram/include
)
link_directories(

View file

@ -0,0 +1,138 @@
#include <SystemTask/SystemTask.h>
#include "NotificationManager.h"
#include "AlertNotificationClient.h"
using namespace Pinetime::Controllers;
constexpr ble_uuid16_t AlertNotificationClient::ansServiceUuid;
constexpr ble_uuid16_t AlertNotificationClient::supportedNewAlertCategoryUuid;
constexpr ble_uuid16_t AlertNotificationClient::supportedUnreadAlertCategoryUuid ;
constexpr ble_uuid16_t AlertNotificationClient::newAlertUuid;
constexpr ble_uuid16_t AlertNotificationClient::unreadAlertStatusUuid;
constexpr ble_uuid16_t AlertNotificationClient::controlPointUuid;
int Pinetime::Controllers::NewAlertSubcribeCallback(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg) {
auto client = static_cast<AlertNotificationClient*>(arg);
return client->OnNewAlertSubcribe(conn_handle, error, attr);
}
AlertNotificationClient::AlertNotificationClient(Pinetime::System::SystemTask& systemTask,
Pinetime::Controllers::NotificationManager& notificationManager) :
systemTask{systemTask}, notificationManager{notificationManager}{
}
bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
if(service == nullptr && error->status == BLE_HS_EDONE) {
NRF_LOG_INFO("ANS Discovery complete");
return true;
}
if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ansServiceUuid), &service->uuid.u) == 0) {
NRF_LOG_INFO("ANS discovered : 0x%x", service->start_handle);
ansStartHandle = service->start_handle;
ansEndHandle = service->end_handle;
isDiscovered = true;
}
return false;
}
void AlertNotificationClient::Init() {
}
int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic) {
if(error->status != 0 && error->status != BLE_HS_EDONE) {
NRF_LOG_INFO("ANS Characteristic discovery ERROR");
return 0;
}
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
NRF_LOG_INFO("ANS Characteristic discovery complete");
} else {
if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&supportedNewAlertCategoryUuid), &characteristic->uuid.u) == 0) {
NRF_LOG_INFO("ANS Characteristic discovered : supportedNewAlertCategoryUuid");
supportedNewAlertCategoryHandle = characteristic->val_handle;
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&supportedUnreadAlertCategoryUuid), &characteristic->uuid.u) == 0) {
NRF_LOG_INFO("ANS Characteristic discovered : supportedUnreadAlertCategoryUuid");
supportedUnreadAlertCategoryHandle = characteristic->val_handle;
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&newAlertUuid), &characteristic->uuid.u) == 0) {
NRF_LOG_INFO("ANS Characteristic discovered : newAlertUuid");
newAlertHandle = characteristic->val_handle;
newAlertDefHandle = characteristic->def_handle;
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&unreadAlertStatusUuid), &characteristic->uuid.u) == 0) {
NRF_LOG_INFO("ANS Characteristic discovered : unreadAlertStatusUuid");
unreadAlertStatusHandle = characteristic->val_handle;
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&controlPointUuid), &characteristic->uuid.u) == 0) {
NRF_LOG_INFO("ANS Characteristic discovered : controlPointUuid");
controlPointHandle = characteristic->val_handle;
}else
NRF_LOG_INFO("ANS Characteristic discovered : 0x%x", characteristic->val_handle);
}
return 0;
}
int AlertNotificationClient::OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error *error,
ble_gatt_attr *attribute) {
if(error->status == 0) {
NRF_LOG_INFO("ANS New alert subscribe OK");
} else {
NRF_LOG_INFO("ANS New alert subscribe ERROR");
}
return 0;
}
int AlertNotificationClient::OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
uint16_t characteristicValueHandle,
const ble_gatt_dsc *descriptor) {
if(error->status == 0) {
if(characteristicValueHandle == newAlertHandle && ble_uuid_cmp(((ble_uuid_t*)&newAlertUuid), &descriptor->uuid.u)) {
if(newAlertDescriptorHandle == 0) {
NRF_LOG_INFO("ANS Descriptor discovered : %d", descriptor->handle);
newAlertDescriptorHandle = descriptor->handle;
uint8_t value[2];
value[0] = 1;
value[1] = 0;
ble_gattc_write_flat(connectionHandle, newAlertDescriptorHandle, value, sizeof(value), NewAlertSubcribeCallback, this);
}
}
}
return 0;
}
void AlertNotificationClient::OnNotification(ble_gap_event *event) {
if(event->notify_rx.attr_handle == newAlertHandle) {
size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
uint8_t data[notifSize + 1];
data[notifSize] = '\0';
os_mbuf_copydata(event->notify_rx.om, 0, notifSize, data);
char *s = (char *) &data[2];
NRF_LOG_INFO("DATA : %s", s);
notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, notifSize + 1);
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
}
}
bool AlertNotificationClient::IsDiscovered() const {
return isDiscovered;
}
uint16_t AlertNotificationClient::StartHandle() const {
return ansStartHandle;
}
uint16_t AlertNotificationClient::EndHandle() const {
return ansEndHandle;
}
uint16_t AlertNotificationClient::NewAlerthandle() const {
return newAlertHandle;
}

View file

@ -0,0 +1,82 @@
#pragma once
#include <cstdint>
#include <array>
#include <host/ble_gap.h>
namespace Pinetime {
namespace Controllers {
int NewAlertSubcribeCallback(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg);
class AlertNotificationClient {
public:
explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask,
Pinetime::Controllers::NotificationManager &notificationManager);
void Init();
bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
int OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic);
int OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute);
int OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
void OnNotification(ble_gap_event *event);
bool IsDiscovered() const;
uint16_t StartHandle() const;
uint16_t EndHandle() const;
static constexpr const ble_uuid16_t &Uuid() { return ansServiceUuid; }
uint16_t NewAlerthandle() const;
private:
static constexpr uint16_t ansServiceId{0x1811};
static constexpr uint16_t supportedNewAlertCategoryId = 0x2a47;
static constexpr uint16_t supportedUnreadAlertCategoryId = 0x2a48;
static constexpr uint16_t newAlertId = 0x2a46;
static constexpr uint16_t unreadAlertStatusId = 0x2a45;
static constexpr uint16_t controlPointId = 0x2a44;
static constexpr ble_uuid16_t ansServiceUuid{
.u {.type = BLE_UUID_TYPE_16},
.value = ansServiceId
};
static constexpr ble_uuid16_t supportedNewAlertCategoryUuid{
.u {.type = BLE_UUID_TYPE_16},
.value = supportedNewAlertCategoryId
};
static constexpr ble_uuid16_t supportedUnreadAlertCategoryUuid{
.u {.type = BLE_UUID_TYPE_16},
.value = supportedUnreadAlertCategoryId
};
static constexpr ble_uuid16_t newAlertUuid{
.u {.type = BLE_UUID_TYPE_16},
.value = newAlertId
};
static constexpr ble_uuid16_t unreadAlertStatusUuid{
.u {.type = BLE_UUID_TYPE_16},
.value = unreadAlertStatusId
};
static constexpr ble_uuid16_t controlPointUuid{
.u {.type = BLE_UUID_TYPE_16},
.value = controlPointId
};
uint16_t ansStartHandle;
uint16_t ansEndHandle;
uint16_t supportedNewAlertCategoryHandle;
uint16_t supportedUnreadAlertCategoryHandle;
uint16_t newAlertHandle;
uint16_t newAlertDescriptorHandle = 0;
uint16_t newAlertDefHandle;
uint16_t unreadAlertStatusHandle;
uint16_t controlPointHandle;
bool isDiscovered = false;
Pinetime::System::SystemTask &systemTask;
Pinetime::Controllers::NotificationManager &notificationManager;
};
}
}

View file

@ -0,0 +1,73 @@
#include <hal/nrf_rtc.h>
#include "NotificationManager.h"
#include <SystemTask/SystemTask.h>
#include "AlertNotificationService.h"
using namespace Pinetime::Controllers;
constexpr ble_uuid16_t AlertNotificationService::ansUuid;
constexpr ble_uuid16_t AlertNotificationService::ansCharUuid;
int AlertNotificationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
auto anService = static_cast<AlertNotificationService*>(arg);
return anService->OnAlert(conn_handle, attr_handle, ctxt);
}
void AlertNotificationService::Init() {
ble_gatts_count_cfg(serviceDefinition);
ble_gatts_add_svcs(serviceDefinition);
}
AlertNotificationService::AlertNotificationService ( Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::NotificationManager& notificationManager ) : m_systemTask{systemTask}, m_notificationManager{notificationManager},
characteristicDefinition{
{
.uuid = (ble_uuid_t *) &ansCharUuid,
.access_cb = AlertNotificationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_WRITE
},
{
0
}
},
serviceDefinition{
{
/* Device Information Service */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = (ble_uuid_t *) &ansUuid,
.characteristics = characteristicDefinition
},
{
0
},
}
{
}
int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt) {
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
size_t notifSize = OS_MBUF_PKTLEN(ctxt->om);
uint8_t data[notifSize + 1];
data[notifSize] = '\0';
os_mbuf_copydata(ctxt->om, 0, notifSize, data);
char *s = (char *) &data[3];
NRF_LOG_INFO("DATA : %s", s);
for(int i = 0; i <= notifSize; i++)
{
if(s[i] == 0x00)
{
s[i] = 0x0A;
}
}
m_notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, notifSize + 1);
m_systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
}
return 0;
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <cstdint>
#include <array>
#include <host/ble_gap.h>
namespace Pinetime {
namespace Controllers {
class AlertNotificationService {
public:
AlertNotificationService(Pinetime::System::SystemTask &systemTask,
Pinetime::Controllers::NotificationManager &notificationManager);
void Init();
int OnAlert(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt);
private:
static constexpr uint16_t ansId {0x1811};
static constexpr uint16_t ansCharId {0x2a46};
static constexpr ble_uuid16_t ansUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = ansId
};
static constexpr ble_uuid16_t ansCharUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = ansCharId
};
struct ble_gatt_chr_def characteristicDefinition[2];
struct ble_gatt_svc_def serviceDefinition[2];
Pinetime::System::SystemTask &m_systemTask;
NotificationManager &m_notificationManager;
};
}
}

View file

@ -1,6 +1,6 @@
#pragma once
#include <FreeRTOS.h>>
#include <FreeRTOS.h>
#include <queue.h>
namespace Pinetime {

View file

@ -0,0 +1,77 @@
#include <hal/nrf_rtc.h>
#include "CurrentTimeClient.h"
using namespace Pinetime::Controllers;
constexpr ble_uuid16_t CurrentTimeClient::ctsServiceUuid;
constexpr ble_uuid16_t CurrentTimeClient::currentTimeCharacteristicUuid;
CurrentTimeClient::CurrentTimeClient(DateTime& dateTimeController) : dateTimeController{dateTimeController} {
}
void CurrentTimeClient::Init() {
}
bool CurrentTimeClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
if(service == nullptr && error->status == BLE_HS_EDONE) {
NRF_LOG_INFO("CTS Discovery complete");
return true;
}
if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ctsServiceUuid), &service->uuid.u) == 0) {
NRF_LOG_INFO("CTS discovered : 0x%x", service->start_handle);
isDiscovered = true;
ctsStartHandle = service->start_handle;
ctsEndHandle = service->end_handle;
return false;
}
return false;
}
int CurrentTimeClient::OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic) {
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
NRF_LOG_INFO("CTS Characteristic discovery complete");
return 0;
}
if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&currentTimeCharacteristicUuid), &characteristic->uuid.u) == 0) {
NRF_LOG_INFO("CTS Characteristic discovered : 0x%x", characteristic->val_handle);
currentTimeHandle = characteristic->val_handle;
}
return 0;
}
int CurrentTimeClient::OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute) {
if(error->status == 0) {
// TODO check that attribute->handle equals the handle discovered in OnCharacteristicDiscoveryEvent
CtsData result;
os_mbuf_copydata(attribute->om, 0, sizeof(CtsData), &result);
NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", result.year,
result.month, result.dayofmonth,
result.hour, result.minute, result.second);
dateTimeController.SetTime(result.year, result.month, result.dayofmonth,
0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG));
} else {
NRF_LOG_INFO("Error retrieving current time: %d", error->status);
}
return 0;
}
bool CurrentTimeClient::IsDiscovered() const {
return isDiscovered;
}
uint16_t CurrentTimeClient::StartHandle() const {
return ctsStartHandle;
}
uint16_t CurrentTimeClient::EndHandle() const {
return ctsEndHandle;
}
uint16_t CurrentTimeClient::CurrentTimeHandle() const {
return currentTimeHandle;
}

View file

@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
#include <array>
#include <Components/DateTime/DateTimeController.h>
#include <host/ble_gap.h>
namespace Pinetime {
namespace Controllers {
class CurrentTimeClient {
public:
explicit CurrentTimeClient(DateTime& dateTimeController);
void Init();
bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
int OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic);
int OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute);
bool IsDiscovered() const;
uint16_t StartHandle() const;
uint16_t EndHandle() const;
uint16_t CurrentTimeHandle() const;
static constexpr const ble_uuid16_t* Uuid() { return &CurrentTimeClient::ctsServiceUuid; }
static constexpr const ble_uuid16_t* CurrentTimeCharacteristicUuid() { return &CurrentTimeClient::currentTimeCharacteristicUuid; }
private:
typedef struct __attribute__((packed)) {
uint16_t year;
uint8_t month;
uint8_t dayofmonth;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t millis;
uint8_t reason;
} CtsData;
static constexpr uint16_t ctsServiceId {0x1805};
static constexpr uint16_t currentTimeCharacteristicId {0x2a2b};
static constexpr ble_uuid16_t ctsServiceUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = ctsServiceId
};
static constexpr ble_uuid16_t currentTimeCharacteristicUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = currentTimeCharacteristicId
};
uint16_t currentTimeHandle;
DateTime& dateTimeController;
bool isDiscovered = false;
uint16_t ctsStartHandle;
uint16_t ctsEndHandle;
};
}
}

View file

@ -0,0 +1,83 @@
#include "CurrentTimeService.h"
#include <hal/nrf_rtc.h>
using namespace Pinetime::Controllers;
constexpr ble_uuid16_t CurrentTimeService::ctsUuid;
constexpr ble_uuid16_t CurrentTimeService::ctChrUuid;
int CTSCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
auto cts = static_cast<CurrentTimeService*>(arg);
return cts->OnTimeAccessed(conn_handle, attr_handle, ctxt);
}
void CurrentTimeService::Init() {
ble_gatts_count_cfg(serviceDefinition);
ble_gatts_add_svcs(serviceDefinition);
}
int CurrentTimeService::OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt) {
NRF_LOG_INFO("Setting time...");
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
CtsData result;
os_mbuf_copydata(ctxt->om, 0, sizeof(CtsData), &result);
NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", result.year,
result.month, result.dayofmonth,
result.hour, result.minute, result.second);
m_dateTimeController.SetTime(result.year, result.month, result.dayofmonth,
0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG));
} else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
CtsData currentDateTime;
currentDateTime.year = m_dateTimeController.Year();
currentDateTime.month = static_cast<u_int8_t>(m_dateTimeController.Month());
currentDateTime.dayofmonth = m_dateTimeController.Day();
currentDateTime.hour = m_dateTimeController.Hours();
currentDateTime.minute = m_dateTimeController.Minutes();
currentDateTime.second = m_dateTimeController.Seconds();
currentDateTime.millis = 0;
int res = os_mbuf_append(ctxt->om, &currentDateTime, sizeof(CtsData));
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
return 0;
}
CurrentTimeService::CurrentTimeService(DateTime &dateTimeController) : m_dateTimeController{dateTimeController},
characteristicDefinition{
{
.uuid = (ble_uuid_t *) &ctChrUuid,
.access_cb = CTSCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
},
{
0
}
},
serviceDefinition{
{
/* Device Information Service */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = (ble_uuid_t *) &ctsUuid,
.characteristics = characteristicDefinition
},
{
0
},
}
{
}

View file

@ -0,0 +1,48 @@
#pragma once
#include <cstdint>
#include <array>
#include <Components/DateTime/DateTimeController.h>
#include <host/ble_gap.h>
namespace Pinetime {
namespace Controllers {
class CurrentTimeService {
public:
CurrentTimeService(DateTime &dateTimeController);
void Init();
int OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt);
private:
static constexpr uint16_t ctsId {0x1805};
static constexpr uint16_t ctsCharId {0x2a2b};
static constexpr ble_uuid16_t ctsUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = ctsId
};
static constexpr ble_uuid16_t ctChrUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = ctsCharId
};
struct ble_gatt_chr_def characteristicDefinition[2];
struct ble_gatt_svc_def serviceDefinition[2];
typedef struct __attribute__((packed)) {
uint16_t year;
uint8_t month;
uint8_t dayofmonth;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t millis;
uint8_t reason;
} CtsData;
DateTime &m_dateTimeController;
};
}
}

View file

@ -0,0 +1,101 @@
#include "DeviceInformationService.h"
using namespace Pinetime::Controllers;
constexpr ble_uuid16_t DeviceInformationService::manufacturerNameUuid;
constexpr ble_uuid16_t DeviceInformationService::modelNumberUuid;
constexpr ble_uuid16_t DeviceInformationService::serialNumberUuid;
constexpr ble_uuid16_t DeviceInformationService::fwRevisionUuid;
constexpr ble_uuid16_t DeviceInformationService::deviceInfoUuid;
constexpr ble_uuid16_t DeviceInformationService::hwRevisionUuid;
int DeviceInformationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
auto deviceInformationService = static_cast<DeviceInformationService*>(arg);
return deviceInformationService->OnDeviceInfoRequested(conn_handle, attr_handle, ctxt);
}
void DeviceInformationService::Init() {
ble_gatts_count_cfg(serviceDefinition);
ble_gatts_add_svcs(serviceDefinition);
}
int DeviceInformationService::OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt) {
const char *str;
switch (ble_uuid_u16(ctxt->chr->uuid)) {
case manufacturerNameId:
str = manufacturerName;
break;
case modelNumberId:
str = modelNumber;
break;
case serialNumberId:
str = serialNumber;
break;
case fwRevisionId:
str = fwRevision;
break;
case hwRevisionId:
str = hwRevision;
break;
default:
return BLE_ATT_ERR_UNLIKELY;
}
int res = os_mbuf_append(ctxt->om, str, strlen(str));
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
DeviceInformationService::DeviceInformationService() :
characteristicDefinition{
{
.uuid = (ble_uuid_t *) &manufacturerNameUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &modelNumberUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &serialNumberUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &fwRevisionUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &hwRevisionUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
0
}
},
serviceDefinition{
{
/* Device Information Service */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = (ble_uuid_t *) &deviceInfoUuid,
.characteristics = characteristicDefinition
},
{
0
},
}
{
}

View file

@ -0,0 +1,67 @@
#pragma once
#include <cstdint>
#include <array>
#include <host/ble_gap.h>
namespace Pinetime {
namespace Controllers {
class DeviceInformationService {
public:
DeviceInformationService();
void Init();
int OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt);
private:
static constexpr uint16_t deviceInfoId {0x180a};
static constexpr uint16_t manufacturerNameId {0x2a29};
static constexpr uint16_t modelNumberId {0x2a24};
static constexpr uint16_t serialNumberId {0x2a25};
static constexpr uint16_t fwRevisionId {0x2a26};
static constexpr uint16_t hwRevisionId {0x2a27};
static constexpr char* manufacturerName = "Codingfield";
static constexpr char* modelNumber = "1";
static constexpr char* serialNumber = "9.8.7.6.5.4";
static constexpr char* fwRevision = "0.5.0";
static constexpr char* hwRevision = "1.0.0";
static constexpr ble_uuid16_t deviceInfoUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = deviceInfoId
};
static constexpr ble_uuid16_t manufacturerNameUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = manufacturerNameId
};
static constexpr ble_uuid16_t modelNumberUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = modelNumberId
};
static constexpr ble_uuid16_t serialNumberUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = serialNumberId
};
static constexpr ble_uuid16_t fwRevisionUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = fwRevisionId
};
static constexpr ble_uuid16_t hwRevisionUuid {
.u {.type = BLE_UUID_TYPE_16},
.value = hwRevisionId
};
struct ble_gatt_chr_def characteristicDefinition[6];
struct ble_gatt_svc_def serviceDefinition[2];
};
}
}

View file

@ -0,0 +1,101 @@
#include "DeviceInformationService.h"
using namespace Pinetime::Controllers;
constexpr ble_uuid16_t DeviceInformationService::manufacturerNameUuid;
constexpr ble_uuid16_t DeviceInformationService::modelNumberUuid;
constexpr ble_uuid16_t DeviceInformationService::serialNumberUuid;
constexpr ble_uuid16_t DeviceInformationService::fwRevisionUuid;
constexpr ble_uuid16_t DeviceInformationService::deviceInfoUuid;
constexpr ble_uuid16_t DeviceInformationService::hwRevisionUuid;
int DeviceInformationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
auto deviceInformationService = static_cast<DeviceInformationService*>(arg);
return deviceInformationService->OnDeviceInfoRequested(conn_handle, attr_handle, ctxt);
}
void DeviceInformationService::Init() {
ble_gatts_count_cfg(serviceDefinition);
ble_gatts_add_svcs(serviceDefinition);
}
int DeviceInformationService::OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt) {
const char *str;
switch (ble_uuid_u16(ctxt->chr->uuid)) {
case manufacturerNameId:
str = manufacturerName;
break;
case modelNumberId:
str = modelNumber;
break;
case serialNumberId:
str = serialNumber;
break;
case fwRevisionId:
str = fwRevision;
break;
case hwRevisionId:
str = hwRevision;
break;
default:
return BLE_ATT_ERR_UNLIKELY;
}
int res = os_mbuf_append(ctxt->om, str, strlen(str));
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
DeviceInformationService::DeviceInformationService() :
characteristicDefinition{
{
.uuid = (ble_uuid_t *) &manufacturerNameUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &modelNumberUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &serialNumberUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &fwRevisionUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &hwRevisionUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
0
}
},
serviceDefinition{
{
/* Device Information Service */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = (ble_uuid_t *) &deviceInfoUuid,
.characteristics = characteristicDefinition
},
{
0
},
}
{
}

View file

@ -0,0 +1,67 @@
#pragma once
#include <cstdint>
#include <array>
#include <host/ble_gap.h>
namespace Pinetime {
namespace Controllers {
class DeviceInformationService {
public:
DeviceInformationService();
void Init();
int OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt);
private:
static constexpr uint16_t deviceInfoId {0x180a};
static constexpr uint16_t manufacturerNameId {0x2a29};
static constexpr uint16_t modelNumberId {0x2a24};
static constexpr uint16_t serialNumberId {0x2a25};
static constexpr uint16_t fwRevisionId {0x2a26};
static constexpr uint16_t hwRevisionId {0x2a27};
static constexpr char* manufacturerName = "Codingfield";
static constexpr char* modelNumber = "1";
static constexpr char* serialNumber = "9.8.7.6.5.4";
static constexpr char* fwRevision = "0.5.0";
static constexpr char* hwRevision = "1.0.0";
static constexpr ble_uuid16_t deviceInfoUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = deviceInfoId
};
static constexpr ble_uuid16_t manufacturerNameUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = manufacturerNameId
};
static constexpr ble_uuid16_t modelNumberUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = modelNumberId
};
static constexpr ble_uuid16_t serialNumberUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = serialNumberId
};
static constexpr ble_uuid16_t fwRevisionUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = fwRevisionId
};
static constexpr ble_uuid16_t hwRevisionUuid {
.u {.type = BLE_UUID_TYPE_16},
.value = hwRevisionId
};
struct ble_gatt_chr_def characteristicDefinition[6];
struct ble_gatt_svc_def serviceDefinition[2];
};
}
}

View file

@ -0,0 +1,316 @@
#include <Components/DateTime/DateTimeController.h>
#include <SystemTask/SystemTask.h>
#include <Components/Ble/NotificationManager.h>
#include <hal/nrf_rtc.h>
#include "NimbleController.h"
#include <services/gatt/ble_svc_gatt.h>
#include <services/gap/ble_svc_gap.h>
#include <host/util/util.h>
#include <host/ble_hs_id.h>
#include <host/ble_hs.h>
#include <host/ble_gap.h>
using namespace Pinetime::Controllers;
// TODO I'm not satisfied by how this code looks like (AlertNotificationClient and CurrentTimeClient must
// expose too much data, too many callbacks -> NimbleController -> CTS/ANS client.
// Let's try to improve this code (and keep it working!)
NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
Pinetime::Controllers::Ble& bleController,
DateTime& dateTimeController,
Pinetime::Controllers::NotificationManager& notificationManager) :
systemTask{systemTask},
bleController{bleController},
dateTimeController{dateTimeController},
notificationManager{notificationManager},
currentTimeClient{dateTimeController},
alertNotificationClient{systemTask, notificationManager},
anService{systemTask, notificationManager},
currentTimeService{dateTimeController} {
}
int GAPEventCallback(struct ble_gap_event *event, void *arg) {
auto nimbleController = static_cast<NimbleController*>(arg);
return nimbleController->OnGAPEvent(event);
}
int CurrentTimeCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg) {
auto client = static_cast<NimbleController*>(arg);
return client->OnCTSCharacteristicDiscoveryEvent(conn_handle, error, chr);
}
int AlertNotificationCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg) {
auto client = static_cast<NimbleController*>(arg);
return client->OnANSCharacteristicDiscoveryEvent(conn_handle, error, chr);
}
int CurrentTimeReadCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg) {
auto client = static_cast<NimbleController*>(arg);
return client->OnCurrentTimeReadResult(conn_handle, error, attr);
}
int AlertNotificationDescriptorDiscoveryEventCallback(uint16_t conn_handle,
const struct ble_gatt_error *error,
uint16_t chr_val_handle,
const struct ble_gatt_dsc *dsc,
void *arg) {
auto client = static_cast<NimbleController*>(arg);
return client->OnANSDescriptorDiscoveryEventCallback(conn_handle, error, chr_val_handle, dsc);
}
void NimbleController::Init() {
while (!ble_hs_synced()) {}
ble_svc_gap_init();
ble_svc_gatt_init();
deviceInformationService.Init();
currentTimeClient.Init();
currentTimeService.Init();
anService.Init();
int res;
res = ble_hs_util_ensure_addr(0);
ASSERT(res == 0);
res = ble_hs_id_infer_auto(0, &addrType);
ASSERT(res == 0);
res = ble_svc_gap_device_name_set(deviceName);
ASSERT(res == 0);
res = ble_gatts_start();
ASSERT(res == 0);
}
void NimbleController::StartAdvertising() {
ble_svc_gap_device_name_set("Pinetime-JF");
/* set adv parameters */
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
/* advertising payload is split into advertising data and advertising
response, because all data cannot fit into single packet; name of device
is sent as response to scan request */
struct ble_hs_adv_fields rsp_fields;
/* fill all fields and parameters with zeros */
memset(&adv_params, 0, sizeof(adv_params));
memset(&fields, 0, sizeof(fields));
memset(&rsp_fields, 0, sizeof(rsp_fields));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
fields.flags = BLE_HS_ADV_F_DISC_GEN |
BLE_HS_ADV_F_BREDR_UNSUP;
// fields.uuids128 = BLE_UUID128(BLE_UUID128_DECLARE(
// 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
// 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff));
fields.num_uuids128 = 0;
fields.uuids128_is_complete = 0;;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
rsp_fields.name = (uint8_t *)"Pinetime-JF";
rsp_fields.name_len = strlen("Pinetime-JF");
rsp_fields.name_is_complete = 1;
int res;
res = ble_gap_adv_set_fields(&fields);
//ASSERT(res == 0);
res = ble_gap_adv_rsp_set_fields(&rsp_fields);
//ASSERT(res == 0);
res = ble_gap_adv_start(addrType, NULL, 10000,
&adv_params, GAPEventCallback, this);
//ASSERT(res == 0);
// TODO I've disabled these ASSERT as they sometime asserts and reset the mcu.
// For now, the advertising is restarted as soon as it ends. There may be a race condition
// that prevent the advertising from restarting reliably.
// I remove the assert to prevent this uncesseray crash, but in the long term, the management of
// the advertising should be improve (better error handling, and advertise for 3 minutes after
// the application has been woken up, for example.
}
int OnAllSvrDisco(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_svc *service,
void *arg) {
auto nimbleController = static_cast<NimbleController*>(arg);
return nimbleController->OnDiscoveryEvent(conn_handle, error, service);
return 0;
}
int NimbleController::OnGAPEvent(ble_gap_event *event) {
switch (event->type) {
case BLE_GAP_EVENT_ADV_COMPLETE:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_ADV_COMPLETE");
NRF_LOG_INFO("advertise complete; reason=%dn status=%d", event->adv_complete.reason, event->connect.status);
StartAdvertising();
break;
case BLE_GAP_EVENT_CONNECT: {
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONNECT");
/* A new connection was established or a connection attempt failed. */
NRF_LOG_INFO("connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed",
event->connect.status);
if (event->connect.status != 0) {
/* Connection failed; resume advertising. */
StartAdvertising();
bleController.Disconnect();
} else {
bleController.Connect();
connectionHandle = event->connect.conn_handle;
ble_gattc_disc_all_svcs(connectionHandle, OnAllSvrDisco, this);
}
}
break;
case BLE_GAP_EVENT_DISCONNECT:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_DISCONNECT");
NRF_LOG_INFO("disconnect; reason=%d", event->disconnect.reason);
/* Connection terminated; resume advertising. */
bleController.Disconnect();
StartAdvertising();
break;
case BLE_GAP_EVENT_CONN_UPDATE:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONN_UPDATE");
/* The central has updated the connection parameters. */
NRF_LOG_INFO("connection updated; status=%d ", event->conn_update.status);
break;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
NRF_LOG_INFO("encryption change event; status=%d ", event->enc_change.status);
return 0;
case BLE_GAP_EVENT_SUBSCRIBE:
NRF_LOG_INFO("subscribe event; conn_handle=%d attr_handle=%d "
"reason=%d prevn=%d curn=%d previ=%d curi=???\n",
event->subscribe.conn_handle,
event->subscribe.attr_handle,
event->subscribe.reason,
event->subscribe.prev_notify,
event->subscribe.cur_notify,
event->subscribe.prev_indicate);
return 0;
case BLE_GAP_EVENT_MTU:
NRF_LOG_INFO("mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
return 0;
case BLE_GAP_EVENT_REPEAT_PAIRING: {
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
*/
/* Delete the old bond. */
struct ble_gap_conn_desc desc;
ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
ble_store_util_delete_peer(&desc.peer_id_addr);
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
* continue with the pairing operation.
*/
}
return BLE_GAP_REPEAT_PAIRING_RETRY;
case BLE_GAP_EVENT_NOTIFY_RX: {
/* Peer sent us a notification or indication. */
size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
NRF_LOG_INFO("received %s; conn_handle=%d attr_handle=%d "
"attr_len=%d",
event->notify_rx.indication ?
"indication" :
"notification",
event->notify_rx.conn_handle,
event->notify_rx.attr_handle,
notifSize);
alertNotificationClient.OnNotification(event);
return 0;
}
/* Attribute data is contained in event->notify_rx.attr_data. */
default:
NRF_LOG_INFO("Advertising event : %d", event->type);
break;
}
return 0;
}
int NimbleController::OnDiscoveryEvent(uint16_t i, const ble_gatt_error *error, const ble_gatt_svc *service) {
if(service == nullptr && error->status == BLE_HS_EDONE) {
NRF_LOG_INFO("Service Discovery complete");
if(currentTimeClient.IsDiscovered()) {
ble_gattc_disc_all_chrs(connectionHandle, currentTimeClient.StartHandle(), currentTimeClient.EndHandle(),
CurrentTimeCharacteristicDiscoveredCallback, this);
} else if(alertNotificationClient.IsDiscovered()) {
ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(), alertNotificationClient.EndHandle(),
AlertNotificationCharacteristicDiscoveredCallback, this);
}
return 0;
}
alertNotificationClient.OnDiscoveryEvent(i, error, service);
currentTimeClient.OnDiscoveryEvent(i, error, service);
return 0;
}
int NimbleController::OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic) {
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
NRF_LOG_INFO("CTS characteristic Discovery complete");
ble_gattc_read(connectionHandle, currentTimeClient.CurrentTimeHandle(), CurrentTimeReadCallback, this);
return 0;
}
return currentTimeClient.OnCharacteristicDiscoveryEvent(connectionHandle, error, characteristic);
}
int NimbleController::OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic) {
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
NRF_LOG_INFO("ANS characteristic Discovery complete");
ble_gattc_disc_all_dscs(connectionHandle,
alertNotificationClient.NewAlerthandle(), alertNotificationClient.EndHandle(),
AlertNotificationDescriptorDiscoveryEventCallback, this);
return 0;
}
return alertNotificationClient.OnCharacteristicsDiscoveryEvent(connectionHandle, error, characteristic);
}
int NimbleController::OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute) {
currentTimeClient.OnCurrentTimeReadResult(connectionHandle, error, attribute);
if (alertNotificationClient.IsDiscovered()) {
ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(),
alertNotificationClient.EndHandle(),
AlertNotificationCharacteristicDiscoveredCallback, this);
}
return 0;
}
int NimbleController::OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
uint16_t characteristicValueHandle,
const ble_gatt_dsc *descriptor) {
return alertNotificationClient.OnDescriptorDiscoveryEventCallback(connectionHandle, error, characteristicValueHandle, descriptor);
}

View file

@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include "AlertNotificationService.h"
#include "AlertNotificationClient.h"
#include "DeviceInformationService.h"
#include "CurrentTimeClient.h"
#include "CurrentTimeService.h"
#include <host/ble_gap.h>
namespace Pinetime {
namespace Controllers {
class DateTime;
class NimbleController {
public:
NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController, DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager);
void Init();
void StartAdvertising();
int OnGAPEvent(ble_gap_event *event);
int OnDiscoveryEvent(uint16_t i, const ble_gatt_error *pError, const ble_gatt_svc *pSvc);
int OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic);
int OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic);
int OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute);
int OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
private:
static constexpr char* deviceName = "Pinetime-JF";
Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::Ble& bleController;
DateTime& dateTimeController;
Pinetime::Controllers::NotificationManager& notificationManager;
DeviceInformationService deviceInformationService;
CurrentTimeClient currentTimeClient;
AlertNotificationService anService;
AlertNotificationClient alertNotificationClient;
CurrentTimeService currentTimeService;
uint8_t addrType;
uint16_t connectionHandle;
};
}
}

View file

@ -63,7 +63,7 @@
#define configTICK_RATE_HZ 1024
#define configMAX_PRIORITIES ( 3 )
#define configMINIMAL_STACK_SIZE ( 120 )
#define configTOTAL_HEAP_SIZE ( 1024*10 )
#define configTOTAL_HEAP_SIZE ( 1024*20 )
#define configMAX_TASK_NAME_LEN ( 4 )
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1

View file

@ -3,11 +3,15 @@
#include <drivers/Cst816s.h>
#include <DisplayApp/LittleVgl.h>
#include <hal/nrf_rtc.h>
#include <BLE/BleManager.h>
#include <softdevice/common/nrf_sdh_freertos.h>
#include <Components/Ble/NotificationManager.h>
#include <host/ble_gatt.h>
#include <host/ble_hs_adv.h>
#include "SystemTask.h"
#include <nimble/hci_common.h>
#include <host/ble_gap.h>
#include <host/util/util.h>
#include "../main.h"
using namespace Pinetime::System;
SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, Drivers::Cst816S &touchPanel,
@ -17,7 +21,8 @@ SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, Drivers::C
Pinetime::Controllers::NotificationManager& notificationManager) :
spi{spi}, lcd{lcd}, touchPanel{touchPanel}, lvgl{lvgl}, batteryController{batteryController},
bleController{bleController}, dateTimeController{dateTimeController},
watchdog{}, watchdogView{watchdog}, notificationManager{notificationManager} {
watchdog{}, watchdogView{watchdog}, notificationManager{notificationManager},
nimbleController(*this, bleController,dateTimeController, notificationManager) {
systemTaksMsgQueue = xQueueCreate(10, 1);
}
@ -37,9 +42,11 @@ void SystemTask::Work() {
watchdog.Start();
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::Watchdog::ResetReasonToString(watchdog.ResetReason()));
APP_GPIOTE_INIT(2);
bool erase_bonds=true;
ble_manager_init_peer_manager();
nrf_sdh_freertos_init(ble_manager_start_advertising, &erase_bonds);
/* BLE */
nimbleController.Init();
nimbleController.StartAdvertising();
/* /BLE*/
spi.Init();
lcd.Init();

View file

@ -8,6 +8,7 @@
#include <Components/Battery/BatteryController.h>
#include <DisplayApp/DisplayApp.h>
#include <drivers/Watchdog.h>
#include <Components/Ble/NimbleController.h>
namespace Pinetime {
namespace System {
@ -44,6 +45,7 @@ namespace Pinetime {
Pinetime::Drivers::Watchdog watchdog;
Pinetime::Drivers::WatchdogView watchdogView;
Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Controllers::NimbleController nimbleController;
static constexpr uint8_t pinSpiSck = 2;

View file

@ -9,7 +9,8 @@ using namespace Pinetime::Drivers;
SpiMaster::SpiMaster(const SpiMaster::SpiModule spi, const SpiMaster::Parameters &params) :
spi{spi}, params{params} {
mutex = xSemaphoreCreateBinary();
ASSERT(mutex != NULL);
}
bool SpiMaster::Init() {
@ -68,6 +69,8 @@ bool SpiMaster::Init() {
NRFX_IRQ_PRIORITY_SET(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn,2);
NRFX_IRQ_ENABLE(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn);
xSemaphoreGive(mutex);
return true;
}
@ -82,6 +85,7 @@ void SpiMaster::SetupWorkaroundForFtpan58(NRF_SPIM_Type *spim, uint32_t ppi_chan
NRF_PPI->CH[ppi_channel].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel];
NRF_PPI->CH[ppi_channel].TEP = (uint32_t) &spim->TASKS_STOP;
NRF_PPI->CHENSET = 1U << ppi_channel;
spiBaseAddress->EVENTS_END = 0;
// Disable IRQ
spim->INTENCLR = (1<<6);
@ -94,13 +98,16 @@ void SpiMaster::DisableWorkaroundForFtpan58(NRF_SPIM_Type *spim, uint32_t ppi_ch
NRF_PPI->CH[ppi_channel].EEP = 0;
NRF_PPI->CH[ppi_channel].TEP = 0;
NRF_PPI->CHENSET = ppi_channel;
spiBaseAddress->EVENTS_END = 0;
spim->INTENSET = (1<<6);
spim->INTENSET = (1<<1);
spim->INTENSET = (1<<19);
}
void SpiMaster::OnEndEvent() {
if(!busy) return;
if(currentBufferAddr == 0) {
return;
}
auto s = currentBufferSize;
if(s > 0) {
@ -113,21 +120,21 @@ void SpiMaster::OnEndEvent() {
} else {
uint8_t* buffer = nullptr;
size_t size = 0;
busy = false;
if(taskToNotify != nullptr) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
nrf_gpio_pin_set(pinCsn);
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
void SpiMaster::OnStartedEvent() {
if(!busy) return;
}
void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size) {
@ -142,10 +149,9 @@ void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile
bool SpiMaster::Write(const uint8_t *data, size_t size) {
if(data == nullptr) return false;
auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
ASSERT(ok == true);
taskToNotify = xTaskGetCurrentTaskHandle();
while(busy) {
asm("nop");
}
if(size == 1) {
SetupWorkaroundForFtpan58(spiBaseAddress, 0,0);
@ -157,7 +163,6 @@ bool SpiMaster::Write(const uint8_t *data, size_t size) {
currentBufferAddr = (uint32_t)data;
currentBufferSize = size;
busy = true;
auto currentSize = std::min((size_t)255, (size_t)currentBufferSize);
PrepareTx(currentBufferAddr, currentSize);
@ -167,7 +172,7 @@ bool SpiMaster::Write(const uint8_t *data, size_t size) {
if(size == 1) {
while (spiBaseAddress->EVENTS_END == 0);
busy = false;
xSemaphoreGive(mutex);
}
return true;

View file

@ -7,6 +7,8 @@
#include <task.h>
#include "BufferProvider.h"
#include <semphr.h>
namespace Pinetime {
namespace Drivers {
class SpiMaster {
@ -51,10 +53,10 @@ namespace Pinetime {
SpiMaster::SpiModule spi;
SpiMaster::Parameters params;
volatile bool busy = false;
volatile uint32_t currentBufferAddr = 0;
volatile size_t currentBufferSize = 0;
volatile TaskHandle_t taskToNotify;
SemaphoreHandle_t mutex;
};
}
}

View file

@ -33,16 +33,16 @@ void Watchdog::Kick() {
Watchdog::ResetReasons Watchdog::ActualResetReason() const {
uint32_t resetReason;
sd_power_reset_reason_get(&resetReason);
sd_power_reset_reason_clr(0xFFFFFFFF);
if(resetReason & 0x01u) return ResetReasons::ResetPin;
if((resetReason >> 1u) & 0x01u) return ResetReasons::Watchdog;
if((resetReason >> 2u) & 0x01u) return ResetReasons::SoftReset;
if((resetReason >> 3u) & 0x01u) return ResetReasons::CpuLockup;
if((resetReason >> 16u) & 0x01u) return ResetReasons::SystemOff;
if((resetReason >> 17u) & 0x01u) return ResetReasons::LpComp;
if((resetReason >> 18u) & 0x01u) return ResetReasons::DebugInterface;
if((resetReason >> 19u) & 0x01u) return ResetReasons::NFC;
// sd_power_reset_reason_get(&resetReason);
// sd_power_reset_reason_clr(0xFFFFFFFF);
// if(resetReason & 0x01u) return ResetReasons::ResetPin;
// if((resetReason >> 1u) & 0x01u) return ResetReasons::Watchdog;
// if((resetReason >> 2u) & 0x01u) return ResetReasons::SoftReset;
// if((resetReason >> 3u) & 0x01u) return ResetReasons::CpuLockup;
// if((resetReason >> 16u) & 0x01u) return ResetReasons::SystemOff;
// if((resetReason >> 17u) & 0x01u) return ResetReasons::LpComp;
// if((resetReason >> 18u) & 0x01u) return ResetReasons::DebugInterface;
// if((resetReason >> 19u) & 0x01u) return ResetReasons::NFC;
return ResetReasons::HardReset;
}

View file

@ -1,6 +1,6 @@
{
"name": "lvgl",
"version": "6.1.1",
"version": "v6.1.2",
"keywords": "graphics, gui, embedded, littlevgl",
"description": "Graphics library to create embedded GUI with easy-to-use graphical elements, beautiful visual effects and low memory footprint. It offers anti-aliasing, opacity, and animations using only one frame buffer.",
"repository":

View file

@ -9,7 +9,7 @@
/*********************
* INCLUDES
*********************/
#include "lv_port_disp_templ.h"
#include "lv_port_disp_template.h"
/*********************
* DEFINES
@ -26,8 +26,9 @@ static void disp_init(void);
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
#if LV_USE_GPU
static void gpu_blend(lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa);
static void gpu_fill(lv_color_t * dest, uint32_t length, lv_color_t color);
static void gpu_blend(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa);
static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
const lv_area_t * fill_area, lv_color_t color);
#endif
/**********************
@ -112,10 +113,10 @@ void lv_port_disp_init(void)
/*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/
/*Blend two color array using opacity*/
disp_drv.gpu_blend = gpu_blend;
disp_drv.gpu_blend_cb = gpu_blend;
/*Fill a memory array with a color*/
disp_drv.gpu_fill = gpu_fill;
disp_drv.gpu_fill_cb = gpu_fill;
#endif
/*Finally register the driver*/
@ -151,7 +152,7 @@ static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_colo
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp);
lv_disp_flush_ready(disp_drv);
}
@ -171,11 +172,11 @@ static void gpu_blend(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_colo
/* If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color
* It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_fill_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
const lv_area_t * fill_area, lv_color_t color);
static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
const lv_area_t * fill_area, lv_color_t color)
{
/*It's an example code which should be done by your GPU*/
uint32_t x, y;
int32_t x, y;
dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
for(y = fill_area->y1; y < fill_area->y2; y++) {

View file

@ -9,7 +9,7 @@
/*********************
* INCLUDES
*********************/
#include "lv_port_fs_templ.h"
#include "lv_port_fs_template.h"
/*********************
* DEFINES
@ -85,30 +85,30 @@ void lv_port_fs_init(void)
*--------------------------------------------------*/
/* Add a simple drive to open images */
lv_fs_drv_t fs_drv; /*A driver descriptor*/
memset(&fs_drv, 0, sizeof(lv_fs_drv_t)); /*Initialization*/
lv_fs_drv_t fs_drv;
lv_fs_drv_init(&fs_drv);
/*Set up fields...*/
fs_drv.file_size = sizeof(file_t);
fs_drv.letter = 'P';
fs_drv.open = fs_open;
fs_drv.close = fs_close;
fs_drv.read = fs_read;
fs_drv.write = fs_write;
fs_drv.seek = fs_seek;
fs_drv.tell = fs_tell;
fs_drv.free = fs_free;
fs_drv.size = fs_size;
fs_drv.remove = fs_remove;
fs_drv.rename = fs_rename;
fs_drv.trunc = fs_trunc;
fs_drv.open_cb = fs_open;
fs_drv.close_cb = fs_close;
fs_drv.read_cb = fs_read;
fs_drv.write_cb = fs_write;
fs_drv.seek_cb = fs_seek;
fs_drv.tell_cb = fs_tell;
fs_drv.free_space_cb = fs_free;
fs_drv.size_cb = fs_size;
fs_drv.remove_cb = fs_remove;
fs_drv.rename_cb = fs_rename;
fs_drv.trunc_cb = fs_trunc;
fs_drv.rddir_size = sizeof(dir_t);
fs_drv.dir_close = fs_dir_close;
fs_drv.dir_open = fs_dir_open;
fs_drv.dir_read = fs_dir_read;
fs_drv.dir_close_cb = fs_dir_close;
fs_drv.dir_open_cb = fs_dir_open;
fs_drv.dir_read_cb = fs_dir_read;
lv_fs_add_drv(&fs_drv);
lv_fs_drv_register(&fs_drv);
}
/**********************
@ -315,7 +315,7 @@ static lv_fs_res_t fs_rename (lv_fs_drv_t * drv, const char * oldname, const cha
* @param free_p pointer to store the free size [kB]
* @return LV_FS_RES_OK or any error from lv_fs_res_t enum
*/
static lv_fs_res_t fs_free (uint32_t * total_p, uint32_t * free_p)
static lv_fs_res_t fs_free (lv_fs_drv_t * drv, uint32_t * total_p, uint32_t * free_p)
{
lv_fs_res_t res = LV_FS_RES_NOT_IMP;

View file

@ -9,7 +9,7 @@
/*********************
* INCLUDES
*********************/
#include "lv_port_indev_templ.h"
#include "lv_port_indev_template.h"
/*********************
* DEFINES

View file

@ -724,7 +724,7 @@ CITE_BIB_FILES =
# messages are off.
# The default value is: NO.
QUIET = NO
QUIET = YES
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
@ -733,14 +733,14 @@ QUIET = NO
# Tip: Turn warnings on while writing the documentation.
# The default value is: YES.
WARNINGS = YES
WARNINGS = NO
# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: YES.
WARN_IF_UNDOCUMENTED = YES
WARN_IF_UNDOCUMENTED = NO
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters
@ -748,7 +748,7 @@ WARN_IF_UNDOCUMENTED = YES
# markup commands wrongly.
# The default value is: YES.
WARN_IF_DOC_ERROR = YES
WARN_IF_DOC_ERROR = NO
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return

View file

@ -49,7 +49,7 @@ void lv_debug_log_error(const char * msg, uint64_t value);
{ \
if(!(expr)) { \
LV_LOG_ERROR(__func__); \
lv_debug_log_error(msg, (uint64_t)value); \
lv_debug_log_error(msg, (uint64_t)((uintptr_t)value)); \
while(1); \
} \
}

View file

@ -121,6 +121,20 @@ void lv_init(void)
LV_LOG_INFO("lv_init ready");
}
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
void lv_deinit(void)
{
lv_gc_clear_roots();
#if LV_USE_LOG
lv_log_register_print_cb(NULL);
#endif
lv_disp_set_default(NULL);
lv_mem_deinit();
lv_initialized = false;
LV_LOG_INFO("lv_deinit done");
}
#endif
/*--------------------
* Create and delete
*-------------------*/
@ -507,10 +521,12 @@ void lv_obj_clean(lv_obj_t * obj)
}
/**
* Mark the object as invalid therefore its current position will be redrawn by 'lv_refr_task'
* Mark an area of an object as invalid.
* This area will be redrawn by 'lv_refr_task'
* @param obj pointer to an object
* @param area the area to redraw
*/
void lv_obj_invalidate(const lv_obj_t * obj)
void lv_obj_invalidate_area(const lv_obj_t * obj, const lv_area_t * area)
{
LV_ASSERT_OBJ(obj, LV_OBJX_NAME);
@ -521,31 +537,56 @@ void lv_obj_invalidate(const lv_obj_t * obj)
lv_disp_t * disp = lv_obj_get_disp(obj_scr);
if(obj_scr == lv_disp_get_scr_act(disp) || obj_scr == lv_disp_get_layer_top(disp) ||
obj_scr == lv_disp_get_layer_sys(disp)) {
/*Truncate recursively to the parents*/
lv_area_t area_trunc;
lv_obj_t * par = lv_obj_get_parent(obj);
bool union_ok = true;
/*Start with the original coordinates*/
lv_coord_t ext_size = obj->ext_draw_pad;
lv_area_copy(&area_trunc, &obj->coords);
area_trunc.x1 -= ext_size;
area_trunc.y1 -= ext_size;
area_trunc.x2 += ext_size;
area_trunc.y2 += ext_size;
/*Check through all parents*/
/*Truncate the area to the object*/
lv_area_t obj_coords;
lv_coord_t ext_size = obj->ext_draw_pad;
lv_area_copy(&obj_coords, &obj->coords);
obj_coords.x1 -= ext_size;
obj_coords.y1 -= ext_size;
obj_coords.x2 += ext_size;
obj_coords.y2 += ext_size;
bool is_common;
lv_area_t area_trunc;
is_common = lv_area_intersect(&area_trunc, area, &obj_coords);
if(is_common == false) return; /*The area is not on the object*/
/*Truncate recursively to the parents*/
lv_obj_t * par = lv_obj_get_parent(obj);
while(par != NULL) {
union_ok = lv_area_intersect(&area_trunc, &area_trunc, &par->coords);
if(union_ok == false) break; /*If no common parts with parent break;*/
is_common = lv_area_intersect(&area_trunc, &area_trunc, &par->coords);
if(is_common == false) break; /*If no common parts with parent break;*/
if(lv_obj_get_hidden(par)) return; /*If the parent is hidden then the child is hidden and won't be drawn*/
par = lv_obj_get_parent(par);
}
if(union_ok) lv_inv_area(disp, &area_trunc);
if(is_common) lv_inv_area(disp, &area_trunc);
}
}
/**
* Mark the object as invalid therefore its current position will be redrawn by 'lv_refr_task'
* @param obj pointer to an object
*/
void lv_obj_invalidate(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, LV_OBJX_NAME);
/*Truncate the area to the object*/
lv_area_t obj_coords;
lv_coord_t ext_size = obj->ext_draw_pad;
lv_area_copy(&obj_coords, &obj->coords);
obj_coords.x1 -= ext_size;
obj_coords.y1 -= ext_size;
obj_coords.x2 += ext_size;
obj_coords.y2 += ext_size;
lv_obj_invalidate_area(obj, &obj_coords);
}
/*=====================
* Setter functions
*====================*/
@ -1458,7 +1499,9 @@ lv_res_t lv_event_send(lv_obj_t * obj, lv_event_t event, const void * data)
*/
lv_res_t lv_event_send_func(lv_event_cb_t event_xcb, lv_obj_t * obj, lv_event_t event, const void * data)
{
if(obj != NULL) {
LV_ASSERT_OBJ(obj, LV_OBJX_NAME);
}
/* Build a simple linked list from the objects used in the events
* It's important to know if an this object was deleted by a nested event

View file

@ -269,6 +269,15 @@ typedef struct
*/
void lv_init(void);
/**
* Deinit the 'lv' library
* Currently only implemented when not using custorm allocators, or GC is enabled.
*/
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
void lv_deinit(void);
#endif
/*--------------------
* Create and delete
*-------------------*/
@ -303,6 +312,15 @@ void lv_obj_del_async(struct _lv_obj_t *obj);
*/
void lv_obj_clean(lv_obj_t * obj);
/**
* Mark an area of an object as invalid.
* This area will be redrawn by 'lv_refr_task'
* @param obj pointer to an object
* @param area the area to redraw
*/
void lv_obj_invalidate_area(const lv_obj_t * obj, const lv_area_t * area);
/**
* Mark the object as invalid therefore its current position will be redrawn by 'lv_refr_task'
* @param obj pointer to an object

View file

@ -179,6 +179,7 @@ void lv_draw_label(const lv_area_t * coords, const lv_area_t * mask, const lv_st
char *bidi_txt = lv_draw_get_buf(line_end - line_start + 1);
lv_bidi_process_paragraph(txt + line_start, bidi_txt, line_end - line_start, bidi_dir, NULL, 0);
#else
(void)bidi_dir;
const char *bidi_txt = txt + line_start;
#endif

View file

@ -85,7 +85,7 @@ lv_img_cache_entry_t * lv_img_cache_open(const void * src, const lv_style_t * st
bool match = false;
lv_img_src_t src_type = lv_img_src_get_type(cache[i].dec_dsc.src);
if(src_type == LV_IMG_SRC_VARIABLE) {
if(cache[i].dec_dsc.src == src) match = true;
if(cache[i].dec_dsc.src == src && cache[i].dec_dsc.style == style) match = true;
} else if(src_type == LV_IMG_SRC_FILE) {
if(strcmp(cache[i].dec_dsc.src, src) == 0) match = true;
}

View file

@ -509,6 +509,7 @@ void lv_img_decoder_built_in_close(lv_img_decoder_t * decoder, lv_img_decoder_ds
}
#endif
if(user_data->palette) lv_mem_free(user_data->palette);
if(user_data->opa) lv_mem_free(user_data->opa);
lv_mem_free(user_data);

View file

@ -75,7 +75,7 @@ typedef struct _lv_font_struct
/*Pointer to the font in a font pack (must have the same line height)*/
uint8_t line_height; /**< The real line height where any text fits*/
uint8_t base_line; /**< Base line measured from the top of the line_height*/
int8_t base_line; /**< Base line measured from the top of the line_height*/
uint8_t subpx :2; /**< An element of `lv_font_subpx_t`*/
void * dsc; /**< Store implementation specific or run_time data or caching here*/
#if LV_USE_USER_DATA

View file

@ -257,7 +257,7 @@ static int8_t get_kern_value(const lv_font_t * font, uint32_t gid_left, uint32_t
/*Kern classes*/
const lv_font_fmt_txt_kern_classes_t * kdsc = fdsc->kern_dsc;
uint8_t left_class = kdsc->left_class_mapping[gid_left];
uint8_t right_class = kdsc->left_class_mapping[gid_right];
uint8_t right_class = kdsc->right_class_mapping[gid_right];
/* If class = 0, kerning not exist for that glyph
* else got the value form `class_pair_values` 2D array*/
@ -475,5 +475,5 @@ static uint8_t rle_next(void)
*/
static int32_t unicode_list_compare(const void * ref, const void * element)
{
return (*(uint16_t *)ref) - (*(uint16_t *)element);
return ((int32_t)(*(uint16_t *)ref)) - ((int32_t)(*(uint16_t *)element));
}

View file

@ -12,12 +12,12 @@ extern "C" {
#endif
/* In the font converter use this list as range:
61441, 61448, 61451, 61452, 61452, 61453, 61457, 61459, 61461, 61465,
61468, 61473, 61478, 61479, 61480, 61502, 61512, 61515, 61516, 61517,
61521, 61522, 61523, 61524, 61543, 61544, 61550, 61552, 61553, 61556,
61559, 61560, 61561, 61563, 61587, 61589, 61636, 61637, 61639, 61671,
61674, 61683, 61724, 61732, 61787, 61931, 62016, 62017, 62018, 62019,
62020, 62087, 62099, 62212, 62189, 62810, 63426, 63650
61441, 61448, 61451, 61452, 61453, 61457, 61459, 61461, 61465, 61468,
61473, 61478, 61479, 61480, 61502, 61512, 61515, 61516, 61517, 61521,
61522, 61523, 61524, 61543, 61544, 61550, 61552, 61553, 61556, 61559,
61560, 61561, 61563, 61587, 61589, 61636, 61637, 61639, 61671, 61674,
61683, 61724, 61732, 61787, 61931, 62016, 62017, 62018, 62019, 62020,
62087, 62099, 62212, 62189, 62810, 63426, 63650
*/
#define LV_SYMBOL_AUDIO "\xef\x80\x81" /*61441, 0xF001*/
@ -93,7 +93,6 @@ enum {
_LV_STR_SYMBOL_CLOSE,
_LV_STR_SYMBOL_POWER,
_LV_STR_SYMBOL_SETTINGS,
_LV_STR_SYMBOL_TRASH,
_LV_STR_SYMBOL_HOME,
_LV_STR_SYMBOL_DOWNLOAD,
_LV_STR_SYMBOL_DRIVE,
@ -113,6 +112,8 @@ enum {
_LV_STR_SYMBOL_RIGHT,
_LV_STR_SYMBOL_PLUS,
_LV_STR_SYMBOL_MINUS,
_LV_STR_SYMBOL_EYE_OPEN,
_LV_STR_SYMBOL_EYE_CLOSE,
_LV_STR_SYMBOL_WARNING,
_LV_STR_SYMBOL_SHUFFLE,
_LV_STR_SYMBOL_UP,
@ -125,6 +126,7 @@ enum {
_LV_STR_SYMBOL_COPY,
_LV_STR_SYMBOL_SAVE,
_LV_STR_SYMBOL_CHARGE,
_LV_STR_SYMBOL_PASTE,
_LV_STR_SYMBOL_BELL,
_LV_STR_SYMBOL_KEYBOARD,
_LV_STR_SYMBOL_GPS,
@ -135,7 +137,12 @@ enum {
_LV_STR_SYMBOL_BATTERY_2,
_LV_STR_SYMBOL_BATTERY_1,
_LV_STR_SYMBOL_BATTERY_EMPTY,
_LV_STR_SYMBOL_USB,
_LV_STR_SYMBOL_BLUETOOTH,
_LV_STR_SYMBOL_TRASH,
_LV_STR_SYMBOL_BACKSPACE,
_LV_STR_SYMBOL_SD_CARD,
_LV_STR_SYMBOL_NEW_LINE,
_LV_STR_SYMBOL_DUMMY,
};

View file

@ -127,6 +127,7 @@ lv_disp_t * lv_disp_drv_register(lv_disp_drv_t * driver)
memset(&disp->inv_area_joined, 0, sizeof(disp->inv_area_joined));
memset(&disp->inv_areas, 0, sizeof(disp->inv_areas));
lv_ll_init(&disp->scr_ll, sizeof(lv_obj_t));
disp->last_activity_time = 0;
if(disp_def == NULL) disp_def = disp;

View file

@ -148,9 +148,11 @@ bool lv_bidi_letter_is_neutral(uint32_t letter)
uint16_t lv_bidi_get_logical_pos(const char * str_in, char **bidi_txt, uint32_t len, lv_bidi_dir_t base_dir, uint32_t visual_pos, bool *is_rtl)
{
uint32_t pos_conv_len = get_txt_len(str_in, len);
void *buf = lv_draw_get_buf(len + pos_conv_len * sizeof(uint16_t));
uint32_t txt_buf_size = len + 1;
txt_buf_size = (txt_buf_size + 3) & (~0x3);
void *buf = lv_draw_get_buf(txt_buf_size + pos_conv_len * sizeof(uint16_t));
if (bidi_txt) *bidi_txt = buf;
uint16_t *pos_conv_buf = (uint16_t*) ((char*)buf + len);
uint16_t *pos_conv_buf = (uint16_t*) ((char*)buf + txt_buf_size);
lv_bidi_process_paragraph(str_in, bidi_txt? *bidi_txt: NULL, len, base_dir, pos_conv_buf, pos_conv_len);
if (is_rtl) *is_rtl = IS_RTL_POS(pos_conv_buf[visual_pos]);
return GET_POS(pos_conv_buf[visual_pos]);
@ -159,9 +161,11 @@ uint16_t lv_bidi_get_logical_pos(const char * str_in, char **bidi_txt, uint32_t
uint16_t lv_bidi_get_visual_pos(const char * str_in, char **bidi_txt, uint16_t len, lv_bidi_dir_t base_dir, uint32_t logical_pos, bool *is_rtl)
{
uint32_t pos_conv_len = get_txt_len(str_in, len);
void *buf = lv_draw_get_buf(len + pos_conv_len * sizeof(uint16_t));
uint32_t txt_buf_size = len + 1;
txt_buf_size = (txt_buf_size + 3) & (~0x3);
void *buf = lv_draw_get_buf(txt_buf_size + pos_conv_len * sizeof(uint16_t));
if (bidi_txt) *bidi_txt = buf;
uint16_t *pos_conv_buf = (uint16_t*) ((char*)buf + len);
uint16_t *pos_conv_buf = (uint16_t*) ((char*)buf + txt_buf_size);
lv_bidi_process_paragraph(str_in, bidi_txt? *bidi_txt: NULL, len, base_dir, pos_conv_buf, pos_conv_len);
for (uint16_t i = 0; i < pos_conv_len; i++){
if (GET_POS(pos_conv_buf[i]) == logical_pos){

View file

@ -104,9 +104,9 @@ enum {
# define LV_COLOR_GET_B1(c) (c).ch.blue
# define LV_COLOR_GET_A1(c) 1
# define LV_COLOR_SET_R8(c, v) (c).ch.red = (uint8_t)((v) & 0x7);
# define LV_COLOR_SET_G8(c, v) (c).ch.green = (uint8_t)((v) & 0x7);
# define LV_COLOR_SET_B8(c, v) (c).ch.blue = (uint8_t)((v) & 0x3);
# define LV_COLOR_SET_R8(c, v) (c).ch.red = (uint8_t)(v) & 0x7U;
# define LV_COLOR_SET_G8(c, v) (c).ch.green = (uint8_t)(v) & 0x7U;
# define LV_COLOR_SET_B8(c, v) (c).ch.blue = (uint8_t)(v) & 0x3U;
# define LV_COLOR_SET_A8(c, v) do {} while(0)
# define LV_COLOR_GET_R8(c) (c).ch.red
@ -114,10 +114,10 @@ enum {
# define LV_COLOR_GET_B8(c) (c).ch.blue
# define LV_COLOR_GET_A8(c) 0xFF
# define LV_COLOR_SET_R16(c, v) (c).ch.red = (uint8_t)(((uint8_t)(v)) & 0x1F);
# define LV_COLOR_SET_G16(c, v) (c).ch.green = (uint8_t)((v) & 0x3F);
# define LV_COLOR_SET_R16(c, v) (c).ch.red = (uint8_t)(v) & 0x1FU;
# define LV_COLOR_SET_G16(c, v) (c).ch.green = (uint8_t)(v) & 0x3FU;
# define LV_COLOR_SET_G16_SWAP(c, v) {(c).ch.green_h = (uint8_t)(((v) >> 3) & 0x7); (c).ch.green_l = (uint8_t)((v) & 0x7);}
# define LV_COLOR_SET_B16(c, v) (c).ch.blue = (uint8_t)((v) & 0x1F);
# define LV_COLOR_SET_B16(c, v) (c).ch.blue = (uint8_t)(v) & 0x1FU;
# define LV_COLOR_SET_A16(c, v) do {} while(0)
# define LV_COLOR_GET_R16(c) (c).ch.red
@ -344,7 +344,6 @@ static inline uint8_t lv_color_to8(lv_color_t color)
static inline uint16_t lv_color_to16(lv_color_t color)
{
#if LV_COLOR_DEPTH == 1
if(color.full == 0)
return 0;
@ -374,8 +373,6 @@ static inline uint16_t lv_color_to16(lv_color_t color)
LV_COLOR_SET_B16(ret, LV_COLOR_GET_B(color) >> 3); /* 8 - 5 = 3*/
return ret.full;
#endif
return 0;
}
static inline uint32_t lv_color_to32(lv_color_t color)
@ -464,14 +461,14 @@ static inline uint8_t lv_color_brightness(lv_color_t color)
/* The most simple macro to create a color from R,G and B values */
#if LV_COLOR_DEPTH == 1
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){.full = (b8 >> 7 | g8 >> 7 | r8 >> 7)})
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){.full = (uint8_t)((b8 >> 7) | (g8 >> 7) | (r8 >> 7))})
#elif LV_COLOR_DEPTH == 8
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){{b8 >> 6, g8 >> 5, r8 >> 5}})
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){{(uint8_t)((b8 >> 6) & 0x3U), (uint8_t)((g8 >> 5) & 0x7U), (uint8_t)((r8 >> 5) & 0x7U)}})
#elif LV_COLOR_DEPTH == 16
#if LV_COLOR_16_SWAP == 0
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){{b8 >> 3, g8 >> 2, r8 >> 3}})
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){{(uint16_t)((b8 >> 3) & 0x1FU), (uint16_t)((g8 >> 2) & 0x3FU), (uint16_t)((r8 >> 3) & 0x1FU)}})
#else
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){{g8 >> 5, r8 >> 3, b8 >> 3, (g8 >> 2) & 0x7}})
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){{(uint16_t)((g8 >> 5) & 0x7U), (uint16_t)((r8 >> 3) & 0x1FU), (uint16_t)((b8 >> 3) & 0x1FU), (uint16_t)((g8 >> 2) & 0x7U)}})
#endif
#elif LV_COLOR_DEPTH == 32
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){{b8, g8, r8, 0xff}}) /*Fix 0xff alpha*/

View file

@ -8,6 +8,11 @@
*********************/
#include "lv_gc.h"
#include "string.h"
#if defined(LV_GC_INCLUDE)
#include LV_GC_INCLUDE
#endif /* LV_ENABLE_GC */
/*********************
* DEFINES
@ -35,6 +40,12 @@ LV_ROOTS
* GLOBAL FUNCTIONS
**********************/
void lv_gc_clear_roots(void)
{
#define LV_CLEAR_ROOT(root_type, root_name) memset(&LV_GC_ROOT(root_name), 0, sizeof(LV_GC_ROOT(root_name)));
LV_ITERATE_ROOTS(LV_CLEAR_ROOT)
}
/**********************
* STATIC FUNCTIONS
**********************/

View file

@ -30,21 +30,21 @@ extern "C" {
* DEFINES
*********************/
#define LV_GC_ROOTS(prefix) \
prefix lv_ll_t _lv_task_ll; /*Linked list to store the lv_tasks*/ \
prefix lv_ll_t _lv_disp_ll; /*Linked list of screens*/ \
prefix lv_ll_t _lv_indev_ll; /*Linked list of screens*/ \
prefix lv_ll_t _lv_drv_ll; \
prefix lv_ll_t _lv_file_ll; \
prefix lv_ll_t _lv_anim_ll; \
prefix lv_ll_t _lv_group_ll; \
prefix lv_ll_t _lv_img_defoder_ll; \
prefix lv_img_cache_entry_t * _lv_img_cache_array; \
prefix void * _lv_task_act; \
prefix void * _lv_draw_buf;
#define LV_ITERATE_ROOTS(f) \
f(lv_ll_t, _lv_task_ll) /*Linked list to store the lv_tasks*/ \
f(lv_ll_t, _lv_disp_ll) /*Linked list of screens*/ \
f(lv_ll_t, _lv_indev_ll) /*Linked list of screens*/ \
f(lv_ll_t, _lv_drv_ll) \
f(lv_ll_t, _lv_file_ll) \
f(lv_ll_t, _lv_anim_ll) \
f(lv_ll_t, _lv_group_ll) \
f(lv_ll_t, _lv_img_defoder_ll) \
f(lv_img_cache_entry_t*, _lv_img_cache_array) \
f(void*, _lv_task_act) \
f(void*, _lv_draw_buf)
#define LV_NO_PREFIX
#define LV_ROOTS LV_GC_ROOTS(LV_NO_PREFIX)
#define LV_DEFINE_ROOT(root_type, root_name) root_type root_name;
#define LV_ROOTS LV_ITERATE_ROOTS(LV_DEFINE_ROOT)
#if LV_ENABLE_GC == 1
#if LV_MEM_CUSTOM != 1
@ -52,7 +52,8 @@ extern "C" {
#endif /* LV_MEM_CUSTOM */
#else /* LV_ENABLE_GC */
#define LV_GC_ROOT(x) x
LV_GC_ROOTS(extern)
#define LV_EXTERN_ROOT(root_type, root_name) extern root_type root_name;
LV_ITERATE_ROOTS(LV_EXTERN_ROOT)
#endif /* LV_ENABLE_GC */
/**********************
@ -63,6 +64,8 @@ LV_GC_ROOTS(extern)
* GLOBAL PROTOTYPES
**********************/
void lv_gc_clear_roots(void);
/**********************
* MACROS
**********************/

View file

@ -22,6 +22,11 @@ extern "C" {
#define LV_MATH_MAX(a, b) ((a) > (b) ? (a) : (b))
#define LV_MATH_ABS(x) ((x) > 0 ? (x) : (-(x)))
#define LV_IS_SIGNED(t) (((t)(-1)) < ((t) 0))
#define LV_UMAX_OF(t) (((0x1ULL << ((sizeof(t) * 8ULL) - 1ULL)) - 1ULL) | (0xFULL << ((sizeof(t) * 8ULL) - 4ULL)))
#define LV_SMAX_OF(t) (((0x1ULL << ((sizeof(t) * 8ULL) - 1ULL)) - 1ULL) | (0x7ULL << ((sizeof(t) * 8ULL) - 4ULL)))
#define LV_MAX_OF(t) ((unsigned long) (LV_IS_SIGNED(t) ? LV_SMAX_OF(t) : LV_UMAX_OF(t)))
#define LV_TRIGO_SIN_MAX 32767
#define LV_TRIGO_SHIFT 15 /**< >> LV_TRIGO_SHIFT to normalize*/

View file

@ -23,7 +23,7 @@
#define LV_MEM_ADD_JUNK 0
#endif
#ifdef LV_MEM_ENV64
#ifdef LV_ARCH_64
#define MEM_UNIT uint64_t
#else
#define MEM_UNIT uint32_t
@ -102,6 +102,21 @@ void lv_mem_init(void)
#endif
}
/**
* Clean up the memory buffer which frees all the allocated memories.
* @note It work only if `LV_MEM_CUSTOM == 0`
*/
void lv_mem_deinit(void)
{
#if LV_MEM_CUSTOM == 0
memset(work_mem, 0x00, (LV_MEM_SIZE / sizeof(MEM_UNIT)) * sizeof(MEM_UNIT));
lv_mem_ent_t * full = (lv_mem_ent_t *)work_mem;
full->header.s.used = 0;
/*The total mem size id reduced by the first header and the close patterns */
full->header.s.d_size = LV_MEM_SIZE - sizeof(lv_mem_header_t);
#endif
}
/**
* Allocate a memory dynamically
* @param size size of the memory to allocate in bytes
@ -113,7 +128,7 @@ void * lv_mem_alloc(size_t size)
return &zero_mem;
}
#ifdef LV_MEM_ENV64
#ifdef LV_ARCH_64
/*Round the size up to 8*/
if(size & 0x7) {
size = size & (~0x7);
@ -262,7 +277,7 @@ void * lv_mem_realloc(void * data_p, size_t new_size)
#else /* LV_ENABLE_GC */
void * lv_mem_realloc(void * data_p, uint32_t new_size)
void * lv_mem_realloc(void * data_p, size_t new_size)
{
void * new_p = LV_MEM_CUSTOM_REALLOC(data_p, new_size);
if(new_p == NULL) LV_LOG_WARN("Couldn't allocate memory");
@ -432,7 +447,7 @@ static void * ent_alloc(lv_mem_ent_t * e, size_t size)
*/
static void ent_trunc(lv_mem_ent_t * e, size_t size)
{
#ifdef LV_MEM_ENV64
#ifdef LV_ARCH_64
/*Round the size up to 8*/
if(size & 0x7) {
size = size & (~0x7);
@ -456,11 +471,11 @@ static void ent_trunc(lv_mem_ent_t * e, size_t size)
uint8_t * e_data = &e->first_data;
lv_mem_ent_t * after_new_e = (lv_mem_ent_t *)&e_data[size];
after_new_e->header.s.used = 0;
after_new_e->header.s.d_size = e->header.s.d_size - size - sizeof(lv_mem_header_t);
after_new_e->header.s.d_size = (uint32_t)e->header.s.d_size - size - sizeof(lv_mem_header_t);
}
/* Set the new size for the original entry */
e->header.s.d_size = size;
e->header.s.d_size = (uint32_t)size;
}
#endif

View file

@ -55,6 +55,12 @@ typedef struct
*/
void lv_mem_init(void);
/**
* Clean up the memory buffer which frees all the allocated memories.
* @note It work only if `LV_MEM_CUSTOM == 0`
*/
void lv_mem_deinit(void);
/**
* Allocate a memory dynamically
* @param size size of the memory to allocate in bytes

View file

@ -8,6 +8,7 @@
*********************/
#include "lv_txt.h"
#include "lv_math.h"
#include "lv_log.h"
/*********************
* DEFINES
@ -108,8 +109,14 @@ void lv_txt_get_size(lv_point_t * size_res, const char * text, const lv_font_t *
/*Calc. the height and longest line*/
while(text[line_start] != '\0') {
new_line_start += lv_txt_get_next_line(&text[line_start], font, letter_space, max_width, flag);
if ((unsigned long)size_res->y + (unsigned long)letter_height + (unsigned long)line_space > LV_MAX_OF(lv_coord_t)) {
LV_LOG_WARN("lv_txt_get_size: integer overflow while calculating text height");
return;
} else {
size_res->y += letter_height;
size_res->y += line_space;
}
/*Calculate the the longest line*/
act_line_length = lv_txt_get_width(&text[line_start], new_line_start - line_start, font, letter_space, flag);
@ -118,7 +125,7 @@ void lv_txt_get_size(lv_point_t * size_res, const char * text, const lv_font_t *
line_start = new_line_start;
}
/*Ma ke the text one line taller if the last character is '\n' or '\r'*/
/*Make the text one line taller if the last character is '\n' or '\r'*/
if((line_start != 0) && (text[line_start - 1] == '\n' || text[line_start - 1] == '\r')) {
size_res->y += letter_height + line_space;
}
@ -200,8 +207,12 @@ static uint16_t lv_txt_get_next_word(const char * txt, const lv_font_t * font,
letter_w = lv_font_get_glyph_width(font, letter, letter_next);
cur_w += letter_w;
if(letter_w > 0) {
cur_w += letter_space;
}
/* Test if this character fits within max_width */
if(break_index == NO_BREAK_FOUND && cur_w > max_width) {
if(break_index == NO_BREAK_FOUND && (cur_w - letter_space) > max_width) {
break_index = i;
break_letter_count = word_len - 1;
/* break_index is now pointing at the character that doesn't fit */
@ -219,9 +230,6 @@ static uint16_t lv_txt_get_next_word(const char * txt, const lv_font_t * font,
/* Update the output width */
if( word_w_ptr != NULL && break_index == NO_BREAK_FOUND ) *word_w_ptr = cur_w;
if(letter_w > 0) {
cur_w += letter_space;
}
i = i_next;
i_next = i_next_next;

View file

@ -18,7 +18,7 @@ extern "C" {
* DEFINES
*********************/
// Check windows
#ifdef __WIN64
#ifdef _WIN64
#define LV_ARCH_64
#endif

View file

@ -815,7 +815,8 @@ static lv_res_t lv_btnm_signal(lv_obj_t * btnm, lv_signal_t sign, void * param)
} else if(sign == LV_SIGNAL_RELEASED) {
if(ext->btn_id_pr != LV_BTNM_BTN_NONE) {
/*Toggle the button if enabled*/
if(button_is_tgl_enabled(ext->ctrl_bits[ext->btn_id_pr])) {
if(button_is_tgl_enabled(ext->ctrl_bits[ext->btn_id_pr]) &&
!button_is_inactive(ext->ctrl_bits[ext->btn_id_pr])) {
if(button_get_tgl_state(ext->ctrl_bits[ext->btn_id_pr])) {
ext->ctrl_bits[ext->btn_id_pr] &= (~LV_BTNM_CTRL_TGL_STATE);
} else {
@ -863,8 +864,9 @@ static lv_res_t lv_btnm_signal(lv_obj_t * btnm, lv_signal_t sign, void * param)
lv_indev_type_t indev_type = lv_indev_get_type(indev);
/*If not focused by an input device assume the last input device*/
if(indev_type == LV_INDEV_TYPE_NONE) {
indev_type = lv_indev_get_type(lv_indev_get_next(NULL));
if(indev == NULL) {
indev = lv_indev_get_next(NULL);
indev_type = lv_indev_get_type(indev);
}
if(indev_type == LV_INDEV_TYPE_POINTER) {
@ -1082,7 +1084,7 @@ static void invalidate_button_area(const lv_obj_t * btnm, uint16_t btn_idx)
btn_area.x2 += btnm_area.x1;
btn_area.y2 += btnm_area.y1;
lv_inv_area(lv_obj_get_disp(btnm), &btn_area);
lv_obj_invalidate_area(btnm, &btn_area);
}
/**

View file

@ -816,7 +816,7 @@ static void lv_chart_draw_div(lv_obj_t * chart, const lv_area_t * mask)
}
p1.x = 0 + x_ofs;
p2.x = w + x_ofs;
p2.x = w - 1 + x_ofs;
for(div_i = div_i_start; div_i <= div_i_end; div_i++) {
p1.y = (int32_t)((int32_t)(h - style->line.width) * div_i) / (ext->hdiv_cnt + 1);
p1.y += y_ofs;
@ -836,7 +836,7 @@ static void lv_chart_draw_div(lv_obj_t * chart, const lv_area_t * mask)
}
p1.y = 0 + y_ofs;
p2.y = h + y_ofs;
p2.y = h + y_ofs - 1;
for(div_i = div_i_start; div_i <= div_i_end; div_i++) {
p1.x = (int32_t)((int32_t)(w - style->line.width) * div_i) / (ext->vdiv_cnt + 1);
p1.x += x_ofs;
@ -951,7 +951,7 @@ static void lv_chart_draw_points(lv_obj_t * chart, const lv_area_t * mask)
y_tmp = (int32_t)((int32_t)ser->points[p_act] - ext->ymin) * h;
y_tmp = y_tmp / (ext->ymax - ext->ymin);
cir_a.y1 = h - y_tmp + y_ofs;
cir_a.y1 = h - y_tmp + y_ofs - 1;
cir_a.y2 = cir_a.y1 + style_point.body.radius;
cir_a.y1 -= style_point.body.radius;
@ -1496,13 +1496,13 @@ static void lv_chart_inv_lines(lv_obj_t * chart, uint16_t i)
if(i < ext->point_cnt - 1) {
coords.x1 = ((w * i) / (ext->point_cnt - 1)) + x_ofs - ext->series.width;
coords.x2 = ((w * (i + 1)) / (ext->point_cnt - 1)) + x_ofs + ext->series.width;
lv_inv_area(lv_obj_get_disp(chart), &coords);
lv_obj_invalidate_area(chart, &coords);
}
if(i > 0) {
coords.x1 = ((w * (i - 1)) / (ext->point_cnt - 1)) + x_ofs - ext->series.width;
coords.x2 = ((w * i) / (ext->point_cnt - 1)) + x_ofs + ext->series.width;
lv_inv_area(lv_obj_get_disp(chart), &coords);
lv_obj_invalidate_area(chart, &coords);
}
}
}

View file

@ -750,7 +750,7 @@ static void invalidate_indic(lv_obj_t * cpicker)
{
lv_area_t indic_area = get_indic_area(cpicker);
lv_inv_area(lv_obj_get_disp(cpicker), &indic_area);
lv_obj_invalidate_area(cpicker, &indic_area);
}
static lv_area_t get_indic_area(lv_obj_t * cpicker)

View file

@ -775,6 +775,7 @@ static lv_res_t lv_ddlist_scrl_signal(lv_obj_t * scrl, lv_signal_t sign, void *
/* Include the ancient signal function */
res = ancestor_scrl_signal(scrl, sign, param);
if(res != LV_RES_OK) return res;
if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, "");
lv_obj_t * ddlist = lv_obj_get_parent(scrl);
@ -806,6 +807,10 @@ static lv_res_t release_handler(lv_obj_t * ddlist)
{
lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
/*Only deal with clickable drop down lists*/
if(!lv_obj_get_click(ddlist))
return LV_RES_OK;
if(ext->opened == 0) { /*Open the list*/
ext->opened = 1;
lv_obj_set_drag(lv_page_get_scrl(ddlist), true);

View file

@ -193,7 +193,7 @@ uint8_t lv_gauge_get_label_count(const lv_obj_t * gauge);
* @param gauge pointer to a gauge object
* @return number of the scale units
*/
static inline uint8_t lv_gauge_get_line_count(const lv_obj_t * gauge)
static inline uint16_t lv_gauge_get_line_count(const lv_obj_t * gauge)
{
return lv_lmeter_get_line_count(gauge);
}

View file

@ -355,6 +355,9 @@ static bool lv_img_design(lv_obj_t * img, const lv_area_t * mask, lv_design_mode
if(ext->cf == LV_IMG_CF_TRUE_COLOR || ext->cf == LV_IMG_CF_RAW) cover = lv_area_is_in(mask, &img->coords);
const lv_style_t * style = lv_img_get_style(img, LV_IMG_STYLE_MAIN);
if(style->image.opa < LV_OPA_MAX) return false;
return cover;
} else if(mode == LV_DESIGN_DRAW_MAIN) {
if(ext->h == 0 || ext->w == 0) return true;

View file

@ -308,7 +308,6 @@ void lv_label_set_array_text(lv_obj_t * label, const char * array, uint16_t size
void lv_label_set_static_text(lv_obj_t * label, const char * text)
{
LV_ASSERT_OBJ(label, LV_OBJX_NAME);
LV_ASSERT_STR(text);
lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
if(ext->static_txt == 0 && ext->text != NULL) {
@ -1303,10 +1302,18 @@ static void lv_label_refr_text(lv_obj_t * label)
p.y -= style->text.line_space; /*Trim the last line space*/
uint32_t letter_id = lv_label_get_letter_on(label, &p);
/*Save letters under the dots and replace them with dots*/
uint32_t i;
/*Be sure there is space for the dots*/
size_t txt_len = strlen(ext->text);
uint32_t byte_id = lv_txt_encoded_get_byte_id(ext->text, letter_id);
while(byte_id + LV_LABEL_DOT_NUM > txt_len) {
byte_id -= lv_txt_encoded_size(&ext->text[byte_id]);
letter_id--;
}
/*Save letters under the dots and replace them with dots*/
uint32_t byte_id_ori = byte_id;
uint32_t i;
uint8_t len = 0;
for(i = 0; i <= LV_LABEL_DOT_NUM; i++) {
len += lv_txt_encoded_size(&ext->text[byte_id]);

View file

@ -179,6 +179,13 @@ lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * img_src, const char * t
{
LV_ASSERT_OBJ(list, LV_OBJX_NAME);
lv_obj_t * last_btn = lv_list_get_prev_btn(list, NULL);
/*The coordinates may changed due to autofit so revert them at the end*/
lv_coord_t pos_x_ori = lv_obj_get_x(list);
lv_coord_t pos_y_ori = lv_obj_get_y(list);
lv_list_ext_t * ext = lv_obj_get_ext_attr(list);
ext->size++;
/*Create a list element with the image an the text*/
@ -197,7 +204,22 @@ lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * img_src, const char * t
lv_page_glue_obj(liste, true);
lv_btn_set_layout(liste, LV_LAYOUT_ROW_M);
lv_layout_t list_layout = lv_list_get_layout(list);
bool layout_ver = false;
if(list_layout == LV_LAYOUT_COL_M || list_layout == LV_LAYOUT_COL_L || list_layout == LV_LAYOUT_COL_R) {
layout_ver = true;
}
if(layout_ver) {
lv_btn_set_fit2(liste, LV_FIT_FLOOD, LV_FIT_TIGHT);
} else {
lv_coord_t w = last_btn ? lv_obj_get_width(last_btn) : (LV_DPI * 3) / 2;
lv_btn_set_fit2(liste, LV_FIT_NONE, LV_FIT_TIGHT);
lv_obj_set_width(liste, w);
}
lv_obj_set_protect(liste, LV_PROTECT_PRESS_LOST);
lv_obj_set_signal_cb(liste, lv_list_btn_signal);
@ -233,6 +255,8 @@ lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * img_src, const char * t
}
#endif
lv_obj_set_pos(list, pos_x_ori, pos_y_ori);
return liste;
}
@ -399,16 +423,23 @@ void lv_list_set_style(lv_obj_t * list, lv_list_style_t type, const lv_style_t *
while(btn != NULL) {
/*If a column layout set the buttons' width to list width*/
if(layout == LV_LAYOUT_COL_M || layout == LV_LAYOUT_COL_L || layout == LV_LAYOUT_COL_R) {
lv_btn_set_fit2(list, LV_FIT_FLOOD, LV_FIT_TIGHT);
lv_btn_set_fit2(btn, LV_FIT_FLOOD, LV_FIT_TIGHT);
}
/*If a row layout set the buttons' width according to the content*/
else if (layout == LV_LAYOUT_ROW_M || layout == LV_LAYOUT_ROW_T || layout == LV_LAYOUT_ROW_B) {
lv_btn_set_fit(list, LV_FIT_TIGHT);
lv_btn_set_fit(btn, LV_FIT_TIGHT);
}
btn = lv_list_get_prev_btn(list, btn);
}
if(layout == LV_LAYOUT_COL_M || layout == LV_LAYOUT_COL_L || layout == LV_LAYOUT_COL_R) {
lv_page_set_scrl_fit2(list, LV_FIT_FLOOD, LV_FIT_TIGHT);
} else if (layout == LV_LAYOUT_ROW_M || layout == LV_LAYOUT_ROW_T || layout == LV_LAYOUT_ROW_B) {
lv_page_set_scrl_fit2(list, LV_FIT_TIGHT, LV_FIT_TIGHT);
lv_cont_set_fit2(list, LV_FIT_NONE, LV_FIT_TIGHT);
}
lv_page_set_scrl_layout(list, layout);
}

View file

@ -150,16 +150,16 @@ lv_obj_t * lv_page_create(lv_obj_t * par, const lv_obj_t * copy)
ext->scrl = lv_cont_create(new_page, copy_ext->scrl);
lv_obj_set_signal_cb(ext->scrl, lv_page_scrollable_signal);
lv_page_set_sb_mode(new_page, copy_ext->sb.mode);
/* Add the signal function only if 'scrolling' is created
* because everything has to be ready before any signal is received*/
lv_obj_set_signal_cb(new_page, lv_page_signal);
lv_obj_set_design_cb(new_page, lv_page_design);
lv_page_set_style(new_page, LV_PAGE_STYLE_BG, lv_page_get_style(copy, LV_PAGE_STYLE_BG));
lv_page_set_style(new_page, LV_PAGE_STYLE_SCRL, lv_page_get_style(copy, LV_PAGE_STYLE_SCRL));
lv_page_set_style(new_page, LV_PAGE_STYLE_SB, lv_page_get_style(copy, LV_PAGE_STYLE_SB));
/* Add the signal function only if 'scrolling' is created
* because everything has to be ready before any signal is received*/
lv_obj_set_signal_cb(new_page, lv_page_signal);
lv_obj_set_design_cb(new_page, lv_page_design);
lv_page_set_sb_mode(new_page, copy_ext->sb.mode);
/*Refresh the style with new signal function*/
lv_obj_refresh_style(new_page);
@ -828,6 +828,7 @@ static lv_res_t lv_page_signal(lv_obj_t * page, lv_signal_t sign, void * param)
lv_page_ext_t * ext = lv_obj_get_ext_attr(page);
lv_obj_t * child;
if(sign == LV_SIGNAL_CHILD_CHG) { /*Automatically move children to the scrollable object*/
if(ext->scrl == NULL) return LV_RES_OK;
const lv_style_t * style_bg = lv_page_get_style(page, LV_PAGE_STYLE_BG);
const lv_style_t * style_scrl = lv_page_get_style(page, LV_PAGE_STYLE_SCRL);
lv_fit_t fit_left = lv_page_get_scrl_fit_left(page);
@ -1073,7 +1074,6 @@ static lv_res_t lv_page_scrollable_signal(lv_obj_t * scrl, lv_signal_t sign, voi
/*Hide scrollbars if required*/
if(page_ext->sb.mode == LV_SB_MODE_DRAG) {
lv_disp_t * disp = lv_obj_get_disp(page);
lv_area_t sb_area_tmp;
if(page_ext->sb.hor_draw) {
lv_area_copy(&sb_area_tmp, &page_ext->sb.hor_area);
@ -1081,7 +1081,7 @@ static lv_res_t lv_page_scrollable_signal(lv_obj_t * scrl, lv_signal_t sign, voi
sb_area_tmp.y1 += page->coords.y1;
sb_area_tmp.x2 += page->coords.x1;
sb_area_tmp.y2 += page->coords.y1;
lv_inv_area(disp, &sb_area_tmp);
lv_obj_invalidate_area(page, &sb_area_tmp);
page_ext->sb.hor_draw = 0;
}
if(page_ext->sb.ver_draw) {
@ -1090,10 +1090,12 @@ static lv_res_t lv_page_scrollable_signal(lv_obj_t * scrl, lv_signal_t sign, voi
sb_area_tmp.y1 += page->coords.y1;
sb_area_tmp.x2 += page->coords.x1;
sb_area_tmp.y2 += page->coords.y1;
lv_inv_area(disp, &sb_area_tmp);
lv_obj_invalidate_area(page, &sb_area_tmp);
page_ext->sb.ver_draw = 0;
}
}
} else if(sign == LV_SIGNAL_CLEANUP) {
page_ext->scrl = NULL;
}
return res;
@ -1150,7 +1152,6 @@ static void lv_page_sb_refresh(lv_obj_t * page)
}
/*Invalidate the current (old) scrollbar areas*/
lv_disp_t * disp = lv_obj_get_disp(page);
lv_area_t sb_area_tmp;
if(ext->sb.hor_draw != 0) {
lv_area_copy(&sb_area_tmp, &ext->sb.hor_area);
@ -1158,7 +1159,7 @@ static void lv_page_sb_refresh(lv_obj_t * page)
sb_area_tmp.y1 += page->coords.y1;
sb_area_tmp.x2 += page->coords.x1;
sb_area_tmp.y2 += page->coords.y1;
lv_inv_area(disp, &sb_area_tmp);
lv_obj_invalidate_area(page, &sb_area_tmp);
}
if(ext->sb.ver_draw != 0) {
lv_area_copy(&sb_area_tmp, &ext->sb.ver_area);
@ -1166,7 +1167,7 @@ static void lv_page_sb_refresh(lv_obj_t * page)
sb_area_tmp.y1 += page->coords.y1;
sb_area_tmp.x2 += page->coords.x1;
sb_area_tmp.y2 += page->coords.y1;
lv_inv_area(disp, &sb_area_tmp);
lv_obj_invalidate_area(page, &sb_area_tmp);
}
if(ext->sb.mode == LV_SB_MODE_DRAG && lv_indev_is_dragging(lv_indev_get_act()) == false) {
@ -1228,7 +1229,7 @@ static void lv_page_sb_refresh(lv_obj_t * page)
sb_area_tmp.y1 += page->coords.y1;
sb_area_tmp.x2 += page->coords.x1;
sb_area_tmp.y2 += page->coords.y1;
lv_inv_area(disp, &sb_area_tmp);
lv_obj_invalidate_area(page, &sb_area_tmp);
}
if(ext->sb.ver_draw != 0) {
lv_area_copy(&sb_area_tmp, &ext->sb.ver_area);
@ -1236,7 +1237,7 @@ static void lv_page_sb_refresh(lv_obj_t * page)
sb_area_tmp.y1 += page->coords.y1;
sb_area_tmp.x2 += page->coords.x1;
sb_area_tmp.y2 += page->coords.y1;
lv_inv_area(disp, &sb_area_tmp);
lv_obj_invalidate_area(page, &sb_area_tmp);
}
}

View file

@ -149,8 +149,9 @@ void lv_roller_set_options(lv_obj_t * roller, const char * options, lv_roller_mo
/* Make sure the roller's height and the scrollable's height is refreshed.
* They are refreshed in `LV_SIGNAL_COORD_CHG` but if the new options has the same width
* that signal won't be called. (It called because LV_FIT_TIGHT hor fit)*/
* that signal won't be called. (It's called because of LV_FIT_TIGHT hor fit)*/
refr_height(roller);
refr_position(roller, LV_ANIM_OFF);
} else {
ext->mode = LV_ROLLER_MODE_INIFINITE;
@ -508,6 +509,7 @@ static lv_res_t lv_roller_scrl_signal(lv_obj_t * roller_scrl, lv_signal_t sign,
/* Include the ancient signal function */
res = ancestor_scrl_signal(roller_scrl, sign, param);
if(res != LV_RES_OK) return res;
if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
lv_indev_t * indev = lv_indev_get_act();
int32_t id = -1;

View file

@ -405,11 +405,15 @@ static void lv_spinbox_updatevalue(lv_obj_t * spinbox)
char buf[LV_SPINBOX_MAX_DIGIT_COUNT + 8];
memset(buf, 0, sizeof(buf));
char * buf_p = buf;
uint8_t cur_shift_left = 0;
if (ext->range_min < 0) { // hide sign if there are only positive values
/*Add the sign*/
(*buf_p) = ext->value >= 0 ? '+' : '-';
buf_p++;
} else {
/*Cursor need shift to left*/
cur_shift_left++;
}
int32_t i;
@ -467,7 +471,7 @@ static void lv_spinbox_updatevalue(lv_obj_t * spinbox)
if(cur_pos > intDigits) cur_pos++; /*Skip teh decimal point*/
cur_pos += ext->digit_padding_left;
cur_pos += (ext->digit_padding_left - cur_shift_left);
lv_ta_set_cursor_pos(spinbox, cur_pos);
}

View file

@ -275,6 +275,13 @@ uint16_t lv_sw_get_anim_time(const lv_obj_t * sw)
*/
static lv_res_t lv_sw_signal(lv_obj_t * sw, lv_signal_t sign, void * param)
{
lv_res_t res;
if(sign == LV_SIGNAL_GET_TYPE) {
res = ancestor_signal(sw, sign, param);
if(res != LV_RES_OK) return res;
return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
}
lv_sw_ext_t * ext = lv_obj_get_ext_attr(sw);
/*Save the current (old) value before slider signal modifies it. It will be required in the
@ -289,12 +296,9 @@ static lv_res_t lv_sw_signal(lv_obj_t * sw, lv_signal_t sign, void * param)
lv_event_cb_t event_cb = sw->event_cb;
sw->event_cb = NULL;
lv_res_t res;
/* Include the ancient signal function */
res = ancestor_signal(sw, sign, param);
if(res != LV_RES_OK) return res;
if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
sw->event_cb = event_cb;

View file

@ -484,7 +484,9 @@ void lv_ta_set_text(lv_obj_t * ta, const char * txt)
if(lv_ta_get_accepted_chars(ta) || lv_ta_get_max_length(ta)) {
lv_label_set_text(ext->label, "");
lv_ta_set_cursor_pos(ta, LV_TA_CURSOR_LAST);
if(ext->pwd_mode != 0) {
ext->pwd_tmp[0] = '\0'; /*Clear the password too*/
}
uint32_t i = 0;
while(txt[i] != '\0') {
uint32_t c = lv_txt_encoded_next(txt, &i);
@ -731,6 +733,7 @@ void lv_ta_set_one_line(lv_obj_t * ta, bool en)
lv_ta_ext_t * ext = lv_obj_get_ext_attr(ta);
if(ext->one_line == en) return;
lv_label_align_t old_align = lv_label_get_align(ext->label);
if(en) {
const lv_style_t * style_ta = lv_obj_get_style(ta);
@ -758,7 +761,8 @@ void lv_ta_set_one_line(lv_obj_t * ta, bool en)
}
placeholder_update(ta);
refr_cursor_area(ta);
/* `refr_cursor_area` is called at the end of lv_ta_set_text_align */
lv_ta_set_text_align(ta, old_align);
}
/**
@ -943,6 +947,7 @@ void lv_ta_set_cursor_blink_time(lv_obj_t * ta, uint16_t time)
a.path_cb = lv_anim_path_step;
lv_anim_create(&a);
} else {
lv_anim_del(ta, (lv_anim_exec_xcb_t)cursor_blink_anim);
ext->cursor.state = 1;
}
#else
@ -1589,14 +1594,13 @@ static void cursor_blink_anim(lv_obj_t * ta, lv_anim_value_t show)
if(show != ext->cursor.state) {
ext->cursor.state = show == 0 ? 0 : 1;
if(ext->cursor.type != LV_CURSOR_NONE && (ext->cursor.type & LV_CURSOR_HIDDEN) == 0) {
lv_disp_t * disp = lv_obj_get_disp(ta);
lv_area_t area_tmp;
lv_area_copy(&area_tmp, &ext->cursor.area);
area_tmp.x1 += ext->label->coords.x1;
area_tmp.y1 += ext->label->coords.y1;
area_tmp.x2 += ext->label->coords.x1;
area_tmp.y2 += ext->label->coords.y1;
lv_inv_area(disp, &area_tmp);
lv_obj_invalidate_area(ta, &area_tmp);
}
}
}
@ -1791,14 +1795,13 @@ static void refr_cursor_area(lv_obj_t * ta)
}
/*Save the new area*/
lv_disp_t * disp = lv_obj_get_disp(ta);
lv_area_t area_tmp;
lv_area_copy(&area_tmp, &ext->cursor.area);
area_tmp.x1 += ext->label->coords.x1;
area_tmp.y1 += ext->label->coords.y1;
area_tmp.x2 += ext->label->coords.x1;
area_tmp.y2 += ext->label->coords.y1;
lv_inv_area(disp, &area_tmp);
lv_obj_invalidate_area(ta, &area_tmp);
lv_area_copy(&ext->cursor.area, &cur_area);
@ -1807,7 +1810,7 @@ static void refr_cursor_area(lv_obj_t * ta)
area_tmp.y1 += ext->label->coords.y1;
area_tmp.x2 += ext->label->coords.x1;
area_tmp.y2 += ext->label->coords.y1;
lv_inv_area(disp, &area_tmp);
lv_obj_invalidate_area(ta, &area_tmp);
}
static void placeholder_update(lv_obj_t * ta)

View file

@ -114,8 +114,17 @@ lv_obj_t * lv_tabview_create(lv_obj_t * par, const lv_obj_t * copy)
/* Set a size which fits into the parent.
* Don't use `par` directly because if the tabview is created on a page it is moved to the
* scrollable so the parent has changed */
lv_obj_set_size(new_tabview, lv_obj_get_width_fit(lv_obj_get_parent(new_tabview)),
lv_obj_get_height_fit(lv_obj_get_parent(new_tabview)));
lv_coord_t w;
lv_coord_t h;
if(par) {
w = lv_obj_get_width_fit(lv_obj_get_parent(new_tabview));
h = lv_obj_get_height_fit(lv_obj_get_parent(new_tabview));
} else {
w = lv_disp_get_hor_res(NULL);
h = lv_disp_get_ver_res(NULL);
}
lv_obj_set_size(new_tabview, w, h);
ext->content = lv_cont_create(new_tabview, NULL);
ext->btns = lv_btnm_create(new_tabview, NULL);
@ -914,12 +923,14 @@ static void tabpage_pressing_handler(lv_obj_t * tabview, lv_obj_t * tabpage)
p = ((tabpage->coords.x1 - tabview->coords.x1) * (indic_size + tabs_style->body.padding.inner)) /
lv_obj_get_width(tabview);
{
uint16_t id = ext->tab_cur;
if(lv_obj_get_base_dir(tabview) == LV_BIDI_DIR_RTL) {
id = (ext->tab_cnt - (id + 1));
}
lv_obj_set_x(ext->indic, indic_size * id + tabs_style->body.padding.inner * id +
indic_style->body.padding.left - p);
}
break;
case LV_TABVIEW_BTNS_POS_LEFT:
case LV_TABVIEW_BTNS_POS_RIGHT:

View file

@ -97,8 +97,17 @@ lv_obj_t * lv_tileview_create(lv_obj_t * par, const lv_obj_t * copy)
/* Set a size which fits into the parent.
* Don't use `par` directly because if the tileview is created on a page it is moved to the
* scrollable so the parent has changed */
lv_obj_set_size(new_tileview, lv_obj_get_width_fit(lv_obj_get_parent(new_tileview)),
lv_obj_get_height_fit(lv_obj_get_parent(new_tileview)));
lv_coord_t w;
lv_coord_t h;
if(par) {
w = lv_obj_get_width_fit(lv_obj_get_parent(new_tileview));
h = lv_obj_get_height_fit(lv_obj_get_parent(new_tileview));
} else {
w = lv_disp_get_hor_res(NULL);
h = lv_disp_get_ver_res(NULL);
}
lv_obj_set_size(new_tileview, w, h);
lv_obj_set_drag_throw(lv_page_get_scrl(new_tileview), false);
lv_page_set_scrl_fit(new_tileview, LV_FIT_TIGHT);
@ -216,6 +225,7 @@ void lv_tileview_set_tile_act(lv_obj_t * tileview, lv_coord_t x, lv_coord_t y, l
for(tile_id = 0; tile_id < ext->valid_pos_cnt; tile_id++) {
if(ext->valid_pos[tile_id].x == x && ext->valid_pos[tile_id].y == y) {
valid = true;
break;
}
}
@ -486,9 +496,9 @@ static void tileview_scrl_event_cb(lv_obj_t * scrl, lv_event_t event)
lv_tileview_ext_t * ext = lv_obj_get_ext_attr(tileview);
if(lv_indev_is_dragging(indev) && (ext->drag_hor || ext->drag_ver)) {
indev->proc.types.pointer.drag_in_prog = 0;
drag_end_handler(tileview);
}
drag_end_handler(tileview);
}
}

View file

@ -75,10 +75,18 @@ lv_obj_t * lv_win_create(lv_obj_t * par, const lv_obj_t * copy)
/* Set a size which fits into the parent.
* Don't use `par` directly because if the window is created on a page it is moved to the
* scrollable so the parent has changed */
lv_obj_set_size(new_win, lv_obj_get_width_fit(lv_obj_get_parent(new_win)),
lv_obj_get_height_fit(lv_obj_get_parent(new_win)));
lv_coord_t w;
lv_coord_t h;
if(par) {
w = lv_obj_get_width_fit(lv_obj_get_parent(new_win));
h = lv_obj_get_height_fit(lv_obj_get_parent(new_win));
} else {
w = lv_disp_get_hor_res(NULL);
h = lv_disp_get_ver_res(NULL);
}
lv_obj_set_size(new_win, w, h);
lv_obj_set_pos(new_win, 0, 0);
lv_obj_set_style(new_win, &lv_style_pretty);
ext->page = lv_page_create(new_win, NULL);

5
src/libs/mynewt-nimble/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Dummy NPL build
*.o
/porting/examples/dummy/dummy
/porting/examples/linux/nimble-linux
/porting/examples/linux_blemesh/nimble-linux-blemesh

View file

@ -0,0 +1,30 @@
# Can't easily add license to rat-excludes file.
.rat-excludes
# Ignore documentation folder
docs
# Non-source files
RELEASE_NOTES.md
.gitignore
README.md
pts-gap.txt
pts-gatt.txt
pts-l2cap.txt
pts-sm.txt
94654-20170317-085122560.tpg
94654-20170317-085441153.pts
uncrustify.cfg
.style_ignored_dirs
# tinycrypt - BSD License.
tinycrypt
# Bluetooth Mesh - Apache 2.0 License
mesh
# Queue implementation - BSD License
queue.h
# mbuf implementation - BSD License
os_mbuf.c

View file

@ -0,0 +1,4 @@
# Skip those directories while doing style checks in the CI. Do not add '/' at
# the beginning!
ext/tinycrypt

View file

@ -0,0 +1,167 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
language: go
_addons: &addon_conf
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-multilib
- gcc-7-multilib
go:
- "1.12"
git:
depth: false
matrix:
include:
# Style checking
- os: linux
language: python
python:
- "3.5"
addons:
apt:
packages:
- "python3-pip"
env:
- TEST=STYLE
- DEBUG=1
# newt build <targets>
- os: linux
addons: *addon_conf
env:
- TEST=BUILD_TARGETS
- VM_AMOUNT=4
- TARGET_SET=1
- os: linux
addons: *addon_conf
env:
- TEST=BUILD_TARGETS
- VM_AMOUNT=4
- TARGET_SET=2
- os: linux
addons: *addon_conf
env:
- TEST=BUILD_TARGETS
- VM_AMOUNT=4
- TARGET_SET=3
- os: linux
addons: *addon_conf
env:
- TEST=BUILD_TARGETS
- VM_AMOUNT=4
- TARGET_SET=4
# newt test all (Linux)
- os: linux
addons: *addon_conf
env:
- TEST=TEST_ALL
- VM_AMOUNT=2
- TARGET_SET=1
- os: linux
addons: *addon_conf
env:
- TEST=TEST_ALL
- VM_AMOUNT=2
- TARGET_SET=2
# ports
- os: linux
addons: *addon_conf
env:
- TEST=BUILD_PORTS
- VM_AMOUNT=1
- TARGET_SET=1
# newt test all
- os: osx
osx_image: xcode9.2
env:
- TEST=TEST_ALL
- VM_AMOUNT=3
- TARGET_SET=1
- os: osx
osx_image: xcode9.2
env:
- TEST=TEST_ALL
- VM_AMOUNT=3
- TARGET_SET=2
- os: osx
osx_image: xcode9.2
env:
- TEST=TEST_ALL
- VM_AMOUNT=3
- TARGET_SET=3
- os: windows
env:
- TEST=BUILD_TARGETS_WINDOWS
- VM_AMOUNT=1
- TARGET_SET=1
before_install:
- printenv
- export GOPATH=$HOME/gopath
- go version
install:
- git clone https://github.com/JuulLabs-OSS/mynewt-travis-ci $HOME/ci
- chmod +x $HOME/ci/*.sh
- |
if [ "${TEST}" == "STYLE" ]; then
pip3 install requests
else
$HOME/ci/${TRAVIS_OS_NAME}_travis_install.sh
fi
before_script:
- |
if [ "${TEST}" == "STYLE" ]; then
$HOME/ci/install_uncrustify.sh
else
newt version
gcc --version
if [ "${TEST}" != "TEST_ALL" ]; then arm-none-eabi-gcc --version; fi
cp -R $HOME/ci/mynewt-nimble-project.yml project.yml
mkdir -p targets
cp -R $HOME/ci/mynewt-nimble-targets targets
$HOME/ci/prepare_test.sh $VM_AMOUNT
mkdir -p repos && pushd repos/
git clone --depth=1 https://github.com/apache/mynewt-core apache-mynewt-core
git clone --depth=1 https://github.com/JuulLabs-OSS/mcuboot mcuboot
git clone --depth=1 https://github.com/apache/mynewt-mcumgr apache-mynewt-mcumgr
popd
fi
script:
- |
if [ "${TEST}" == "STYLE" ]; then
python3 $HOME/ci/check_style.py
else
$HOME/ci/run_test.sh
fi
cache:
directories:
- $HOME/TOOLCHAIN
- $HOME/Library/Caches/Homebrew

View file

@ -0,0 +1,267 @@
# Coding Style for Apache NimBLE
Apache NimBLE project is part of Apache Mynewt projct and follows its coding
style.
# Coding Style for Apache Mynewt Core
This document is meant to define the coding style for Apache Mynewt, and
all subprojects of Apache Mynewt. This covers C and Assembly coding
conventions, *only*. Other languages (such as Go), have their own
coding conventions.
## Headers
* All files that are newly written, should have the Apache License clause
at the top of them.
* For files that are copied from another source, but contain an Apache
compatible license, the original license header shall be maintained.
* For more information on applying the Apache license, the definitive
source is here: http://www.apache.org/dev/apply-license.html
* The Apache License clause for the top of files is as follows:
```no-highlight
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
```
## Whitespace and Braces
* Code must be indented to 4 spaces, tabs should not be used.
* Do not add whitespace at the end of a line.
* Put space after keywords (for, if, return, switch, while).
* for, else, if, while statements must have braces around their
code blocks, i.e., do:
```
if (x) {
assert(0);
} else {
assert(0);
}
```
Not:
```
if (x)
assert(0);
else
assert(0);
```
* Braces for statements must be on the same line as the statement. Good:
```
for (i = 0; i < 10; i++) {
if (i == 5) {
break;
} else {
continue;
}
}
```
Not:
```
for (i = 0; i < 10; i++)
{ <-- brace must be on same line as for
if (i == 5) {
break;
} <-- no new line between else
else {
continue;
}
}
```
* After a function declaration, the braces should be on a newline, i.e. do:
```
static void *
function(int var1, int var2)
{
```
not:
```
static void *
function(int var1, int var2) {
```
## Line Length and Wrap
* Line length should never exceed 79 columns.
* When you have to wrap a long statement, put the operator at the end of the
line. i.e.:
```
if (x &&
y == 10 &&
b)
```
Not:
```
if (x
&& y == 10
&& b)
```
## Comments
* No C++ style comments allowed.
* When using a single line comment, put it above the line of code that you
intend to comment, i.e., do:
```
/* check variable */
if (a) {
```
Not:
```
if (a) { /* check variable */
```
* All public APIs should be commented with Doxygen style comments describing
purpose, parameters and return values. Private APIs need not be documented.
## Header files
* Header files must contain the following structure:
* Apache License (see above)
* ```#ifdef``` aliasing, to prevent multiple includes
* ```#include``` directives for other required header files
* ```#ifdef __cplusplus``` wrappers to maintain C++ friendly APIs
* Contents of the header file
* ```#ifdef``` aliasing, shall be in the following format, where
the package name is "os" and the file name is "callout.h":
```no-highlight
#ifndef _OS_CALLOUT_H
#define _OS_CALLOUT_H
```
* ```#include``` directives must happen prior to the cplusplus
wrapper.
* The cplusplus wrapper must have the following format, and precedes
any contents of the header file:
```no-highlight
#ifdef __cplusplus
#extern "C" {
##endif
```
## Naming
* Names of functions, structures and variables must be in all lowercase.
* Names should be as short as possible, but no shorter.
* Globally visible names must be prefixed with the name of the module,
followed by the '_' character, i.e.:
```
os_callout_init(&c)
```
Not:
```
callout_init(c)
```
## Functions
* No spaces after function names when calling a function, i.e, do:
```
rc = function(a)
```
Not:
```
rc = function (a)
```
* Arguments to function calls should have spaces between the comma, i.e. do:
```
rc = function(a, b)
```
Not:
```
rc = function(a,b)
```
* The function type must be on a line by itself preceding the function, i.e. do:
```
static void *
function(int var1, int var2)
{
```
Not:
```
static void *function(int var1, int var2)
{
```
* In general, for functions that return values that denote success or error, 0
shall be success, and non-zero shall be the failure code.
## Variables and Macros
* Do not use typedefs for structures. This makes it impossible for
applications to use pointers to those structures opaquely.
* typedef may be used for non-structure types, where it is beneficial to
hide or alias the underlying type used (e.g. ```os_time_t```.) Indicate
typedefs by applying the ```_t``` marker to them.
* Place all function-local variable definitions at the top of the function body, before any statements.
## Compiler Directives
* Code must compile cleanly with -Wall enabled.

View file

@ -0,0 +1,217 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This product bundles queue.h 8.5, which is available under the "3-clause BSD"
license. For details, see porting/nimble/include/os/queue.h
This product partly derives from FreeBSD, which is available under the
"3-clause BSD" license. For details, see:
* porting/nimble/src/os_mbuf.c
This product bundles Gary S. Brown's CRC32 implementation, which is available
under the following license:
COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
code or tables extracted from it, as desired without restriction.
This product bundles tinycrypt, which is available under the "3-clause BSD"
license. For details, and bundled files see:
* ext/tinycrypt/LICENSE

View file

@ -0,0 +1,8 @@
Apache Mynewt NimBLE
Copyright 2015-2020 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
Portions of this software were developed at
Runtime Inc, copyright 2015.

View file

@ -0,0 +1,169 @@
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
-->
<img src="http://mynewt.apache.org/img/logo.svg" width="250" alt="Apache Mynewt">
## Overview
Apache NimBLE is an open-source Bluetooth 5.1 stack (both Host & Controller)
that completely replaces the proprietary SoftDevice on Nordic chipsets. It is
part of [Apache Mynewt project](https://github.com/apache/mynewt-core).
Features highlight:
- Support for 251 byte packet size
- Support for all 4 roles concurrently - Broadcaster, Observer, Peripheral and Central
- Support for up to 32 simultaneous connections.
- Legacy and SC (secure connections) SMP support (pairing and bonding).
- Advertising Extensions.
- Coded (aka Long Range) and 2M PHYs.
- Bluetooth Mesh.
## Supported hardware
Controller supports Nordic nRF51 and nRF52 chipsets. Host runs on any board
and architecture [supported](https://github.com/apache/mynewt-core#overview)
by Apache Mynewt OS.
## Browsing
If you are browsing around the source tree, and want to see some of the
major functional chunks, here are a few pointers:
- nimble/controller: Contains code for controller including Link Layer and HCI implementation
([controller](https://github.com/apache/mynewt-nimble/tree/master/nimble/controller))
- nimble/drivers: Contains drivers for supported radio transceivers (Nordic nRF51 and nRF52)
([drivers](https://github.com/apache/mynewt-nimble/tree/master/nimble/drivers))
- nimble/host: Contains code for host subsystem. This includes protocols like
L2CAP and ATT, support for HCI commands and events, Generic Access Profile (GAP),
Generic Attribute Profile (GATT) and Security Manager (SM).
([host](https://github.com/apache/mynewt-nimble/tree/master/nimble/host))
- nimble/host/mesh: Contains code for Bluetooth Mesh subsystem.
([mesh](https://github.com/apache/mynewt-nimble/tree/master/nimble/host/mesh))
- nimble/transport: Contains code for supported transport protocols between host
and controller. This includes UART, emSPI and RAM (used in combined build when
host and controller run on same CPU)
([transport](https://github.com/apache/mynewt-nimble/tree/master/nimble/transport))
- porting: Contains implementation of NimBLE Porting Layer (NPL) for supported
operating systems
([porting](https://github.com/apache/mynewt-nimble/tree/master/porting))
- ext: Contains external libraries used by NimBLE. Those are used if not
provided by OS
([ext](https://github.com/apache/mynewt-nimble/tree/master/ext))
- kernel: Contains the core of the RTOS ([kernel/os](https://github.com/apache/mynewt-core/tree/master/kernel/os))
## Sample Applications
There are also some sample applications that show how to Apache Mynewt NimBLE
stack. These sample applications are located in the `apps/` directory of
Apache Mynewt [repo](https://github.com/apache/mynewt-core). Some examples:
* [blecent](https://github.com/apache/mynewt-nimble/tree/master/apps/blecent):
A basic central device with no user interface. This application scans for
a peripheral that supports the alert notification service (ANS). Upon
discovering such a peripheral, blecent connects and performs a characteristic
read, characteristic write, and notification subscription.
* [blehci](https://github.com/apache/mynewt-nimble/tree/master/apps/blehci):
Implements a BLE controller-only application. A separate host-only
implementation, such as Linux's BlueZ, can interface with this application via
HCI over UART.
* [bleprph](https://github.com/apache/mynewt-nimble/tree/master/apps/bleprph): An
implementation of a minimal BLE peripheral.
* [btshell](https://github.com/apache/mynewt-nimble/tree/master/apps/btshell): A
shell-like application allowing to configure and use most of NimBLE
functionality from command line.
* [bleuart](https://github.com/apache/mynewt-core/tree/master/apps/bleuart):
Implements a simple BLE peripheral that supports the Nordic
UART / Serial Port Emulation service
(https://developer.nordicsemi.com/nRF5_SDK/nRF51_SDK_v8.x.x/doc/8.0.0/s110/html/a00072.html).
# Getting Help
If you are having trouble using or contributing to Apache Mynewt NimBLE, or just
want to talk to a human about what you're working on, you can contact us via the
[developers mailing list](mailto:dev@mynewt.apache.org).
Although not a formal channel, you can also find a number of core developers
on the #mynewt channel on Freenode IRC or #general channel on [Mynewt Slack](https://mynewt.slack.com/join/shared_invite/enQtNjA1MTg0NzgyNzg3LTcyMmZiOGQzOGMxM2U4ODFmMTIwNjNmYTE5Y2UwYjQwZWIxNTE0MTUzY2JmMTEzOWFjYWZkNGM0YmM4MzAxNWQ)
Also, be sure to checkout the [Frequently Asked Questions](https://mynewt.apache.org/faq/answers)
for some help troubleshooting first.
# Contributing
Anybody who works with Apache Mynewt can be a contributing member of the
community that develops and deploys it. The process of releasing an operating
system for microcontrollers is never done: and we welcome your contributions
to that effort.
More information can be found at the Community section of the Apache Mynewt
website, located [here](https://mynewt.apache.org/community).
## Pull Requests
Apache Mynewt welcomes pull request via Github. Discussions are done on Github,
but depending on the topic, can also be relayed to the official Apache Mynewt
developer mailing list dev@mynewt.apache.org.
If you are suggesting a new feature, please email the developer list directly,
with a description of the feature you are planning to work on.
## Filing Bugs
Bugs can be filed on the
[Apache Mynewt NimBLE Issues](https://github.com/apache/mynewt-nimble/issues).
Please label the issue as a "Bug".
Where possible, please include a self-contained reproduction case!
## Feature Requests
Feature requests should also be filed on the
[Apache Mynewt NimBLE Bug Tracker](https://github.com/apache/mynewt-nimble/issues).
Please label the issue as a "Feature" or "Enhancement" depending on the scope.
## Writing Tests
We love getting newt tests! Apache Mynewt is a huge undertaking, and improving
code coverage is a win for every Apache Mynewt user.
<!--
TODO
## Writing Documentation
Contributing to documentation (in addition to writing tests), is a great way
to get involved with the Apache Mynewt project.
The Mynewt NimBLE documentation is found in [/docs](/docs).
-->
# License
The code in this repository is all under either the Apache 2 license, or a
license compatible with the Apache 2 license. See the LICENSE file for more
information.

View file

@ -0,0 +1,33 @@
# RELEASE NOTES
18 March 2020 - Apache NimBLE v1.3.0
For full release notes, please visit the
[Apache Mynewt Wiki](https://cwiki.apache.org/confluence/display/MYNEWT/Release+Notes).
Apache NimBLE is an open-source Bluetooth 5.1 stack (both Host & Controller) that completely
replaces the proprietary SoftDevice on Nordic chipsets.
New features in this version of NimBLE include:
* Support for Bluetooth Core Specification 5.1
* New blestress test application
* Dialog DA1469x CMAC driver
* Support for LE Secure Connections out-of-band (OOB) association model
* Support for automated generation of syscfg for ports
* Qualification related bugfixes
* BLE Mesh improvements - fixes and resync with latest Zephyr code
* RIOT OS port fixes and improvements
* btshell sample application improvements
* improvements for bttester application
* Controller duplicates filtering improvements
* Multi PHY support improvements
* Memory and CPU usage optimizations
* Use of packed structs for HCI (code size reduction)
* Linux sample improvements
* PTS test instructions updates
* Clock managements improvements in controller
If working on next-generation RTOS and Bluetooth protocol stack
sounds exciting to you, get in touch, by sending a mail to the Apache Mynewt
Developer's list, dev@mynewt.apache.org.

View file

@ -0,0 +1,33 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
pkg.name: "apps/advertiser"
pkg.type: app
pkg.description: "Basic advertiser application"
pkg.author: "Krzysztof Kopyściński <krzysztof.kopyscinski@codecoup.pl>"
pkg.deps:
- "@apache-mynewt-core/kernel/os"
- "@apache-mynewt-core/sys/console/full"
- "@apache-mynewt-core/sys/log/full"
- "@apache-mynewt-core/sys/stats/full"
- "@apache-mynewt-core/sys/log/modlog"
- "@apache-mynewt-nimble/nimble/host/util"
- "@apache-mynewt-nimble/nimble/host/services/gap"
- "@apache-mynewt-nimble/nimble/transport"

View file

@ -0,0 +1,136 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "os/os.h"
#include "sysinit/sysinit.h"
#include "log/log.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
static const char *device_name = "Apache Mynewt";
/* adv_event() calls advertise(), so forward declaration is required */
static void advertise(void);
static void
set_ble_addr(void)
{
int rc;
ble_addr_t addr;
/* generate new non-resolvable private address */
rc = ble_hs_id_gen_rnd(1, &addr);
assert(rc == 0);
/* set generated address */
rc = ble_hs_id_set_rnd(addr.val);
assert(rc == 0);
}
static int
adv_event(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_ADV_COMPLETE:
MODLOG_DFLT(INFO, "Advertising completed, termination code: %d\n",
event->adv_complete.reason);
advertise();
return 0;
default:
MODLOG_DFLT(ERROR, "Advertising event not handled\n");
return 0;
}
}
static void
advertise(void)
{
int rc;
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
/* set adv parameters */
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_NON;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
memset(&fields, 0, sizeof(fields));
/* Fill the fields with advertising data - flags, tx power level, name */
fields.flags = BLE_HS_ADV_F_DISC_GEN;
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
assert(rc == 0);
MODLOG_DFLT(INFO, "Starting advertising...\n");
/* As own address type we use hard-coded value, because we generate
NRPA and by definition it's random */
rc = ble_gap_adv_start(BLE_OWN_ADDR_RANDOM, NULL, 10000,
&adv_params, adv_event, NULL);
assert(rc == 0);
}
static void
on_sync(void)
{
set_ble_addr();
/* begin advertising */
advertise();
}
static void
on_reset(int reason)
{
MODLOG_DFLT(INFO, "Resetting state; reason=%d\n", reason);
}
int
main(int argc, char **argv)
{
int rc;
/* Initialize all packages. */
sysinit();
ble_hs_cfg.sync_cb = on_sync;
ble_hs_cfg.reset_cb = on_reset;
rc = ble_svc_gap_device_name_set(device_name);
assert(rc == 0);
/* As the last thing, process events from default event queue. */
while (1) {
os_eventq_run(os_eventq_dflt_get());
}
return 0;
}

View file

@ -0,0 +1,36 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
pkg.name: apps/blecent
pkg.type: app
pkg.description: Simple BLE central application.
pkg.author: "Apache Mynewt <dev@mynewt.apache.org>"
pkg.homepage: "http://mynewt.apache.org/"
pkg.keywords:
pkg.deps:
- "@apache-mynewt-core/kernel/os"
- "@apache-mynewt-core/sys/console/full"
- "@apache-mynewt-core/sys/log/full"
- "@apache-mynewt-core/sys/log/modlog"
- "@apache-mynewt-core/sys/stats/full"
- nimble/host
- nimble/host/util
- nimble/host/services/gap
- nimble/host/services/gatt
- nimble/host/store/ram
- nimble/transport

View file

@ -0,0 +1,111 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef H_BLECENT_
#define H_BLECENT_
#include "os/mynewt.h"
#include "modlog/modlog.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ble_hs_adv_fields;
struct ble_gap_conn_desc;
struct ble_hs_cfg;
union ble_store_value;
union ble_store_key;
#define BLECENT_SVC_ALERT_UUID 0x1811
#define BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47
#define BLECENT_CHR_NEW_ALERT 0x2A46
#define BLECENT_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48
#define BLECENT_CHR_UNR_ALERT_STAT_UUID 0x2A45
#define BLECENT_CHR_ALERT_NOT_CTRL_PT 0x2A44
/** Misc. */
void print_bytes(const uint8_t *bytes, int len);
void print_mbuf(const struct os_mbuf *om);
char *addr_str(const void *addr);
void print_uuid(const ble_uuid_t *uuid);
void print_conn_desc(const struct ble_gap_conn_desc *desc);
void print_adv_fields(const struct ble_hs_adv_fields *fields);
/** Peer. */
struct peer_dsc {
SLIST_ENTRY(peer_dsc) next;
struct ble_gatt_dsc dsc;
};
SLIST_HEAD(peer_dsc_list, peer_dsc);
struct peer_chr {
SLIST_ENTRY(peer_chr) next;
struct ble_gatt_chr chr;
struct peer_dsc_list dscs;
};
SLIST_HEAD(peer_chr_list, peer_chr);
struct peer_svc {
SLIST_ENTRY(peer_svc) next;
struct ble_gatt_svc svc;
struct peer_chr_list chrs;
};
SLIST_HEAD(peer_svc_list, peer_svc);
struct peer;
typedef void peer_disc_fn(const struct peer *peer, int status, void *arg);
struct peer {
SLIST_ENTRY(peer) next;
uint16_t conn_handle;
/** List of discovered GATT services. */
struct peer_svc_list svcs;
/** Keeps track of where we are in the service discovery process. */
uint16_t disc_prev_chr_val;
struct peer_svc *cur_svc;
/** Callback that gets executed when service discovery completes. */
peer_disc_fn *disc_cb;
void *disc_cb_arg;
};
int peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb,
void *disc_cb_arg);
const struct peer_dsc *
peer_dsc_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid);
const struct peer_chr *
peer_chr_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid);
const struct peer_svc *
peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid);
int peer_delete(uint16_t conn_handle);
int peer_add(uint16_t conn_handle);
int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,525 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include "os/mynewt.h"
#include "bsp/bsp.h"
/* BLE */
#include "nimble/ble.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
/* Mandatory services. */
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
/* Application-specified header. */
#include "blecent.h"
static int blecent_gap_event(struct ble_gap_event *event, void *arg);
/**
* Application callback. Called when the read of the ANS Supported New Alert
* Category characteristic has completed.
*/
static int
blecent_on_read(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status,
conn_handle);
if (error->status == 0) {
MODLOG_DFLT(INFO, " attr_handle=%d value=", attr->handle);
print_mbuf(attr->om);
}
MODLOG_DFLT(INFO, "\n");
return 0;
}
/**
* Application callback. Called when the write to the ANS Alert Notification
* Control Point characteristic has completed.
*/
static int
blecent_on_write(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO,
"Write complete; status=%d conn_handle=%d attr_handle=%d\n",
error->status, conn_handle, attr->handle);
return 0;
}
/**
* Application callback. Called when the attempt to subscribe to notifications
* for the ANS Unread Alert Status characteristic has completed.
*/
static int
blecent_on_subscribe(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO, "Subscribe complete; status=%d conn_handle=%d "
"attr_handle=%d\n",
error->status, conn_handle, attr->handle);
return 0;
}
/**
* Performs three concurrent GATT operations against the specified peer:
* 1. Reads the ANS Supported New Alert Category characteristic.
* 2. Writes the ANS Alert Notification Control Point characteristic.
* 3. Subscribes to notifications for the ANS Unread Alert Status
* characteristic.
*
* If the peer does not support a required service, characteristic, or
* descriptor, then the peer lied when it claimed support for the alert
* notification service! When this happens, or if a GATT procedure fails,
* this function immediately terminates the connection.
*/
static void
blecent_read_write_subscribe(const struct peer *peer)
{
const struct peer_chr *chr;
const struct peer_dsc *dsc;
uint8_t value[2];
int rc;
/* Read the supported-new-alert-category characteristic. */
chr = peer_chr_find_uuid(peer,
BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
BLE_UUID16_DECLARE(BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID));
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer doesn't support the Supported New "
"Alert Category characteristic\n");
goto err;
}
rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle,
blecent_on_read, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to read characteristic; rc=%d\n",
rc);
goto err;
}
/* Write two bytes (99, 100) to the alert-notification-control-point
* characteristic.
*/
chr = peer_chr_find_uuid(peer,
BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
BLE_UUID16_DECLARE(BLECENT_CHR_ALERT_NOT_CTRL_PT));
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer doesn't support the Alert "
"Notification Control Point characteristic\n");
goto err;
}
value[0] = 99;
value[1] = 100;
rc = ble_gattc_write_flat(peer->conn_handle, chr->chr.val_handle,
value, sizeof value, blecent_on_write, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to write characteristic; rc=%d\n",
rc);
}
/* Subscribe to notifications for the Unread Alert Status characteristic.
* A central enables notifications by writing two bytes (1, 0) to the
* characteristic's client-characteristic-configuration-descriptor (CCCD).
*/
dsc = peer_dsc_find_uuid(peer,
BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
BLE_UUID16_DECLARE(BLECENT_CHR_UNR_ALERT_STAT_UUID),
BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16));
if (dsc == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer lacks a CCCD for the Unread Alert "
"Status characteristic\n");
goto err;
}
value[0] = 1;
value[1] = 0;
rc = ble_gattc_write_flat(peer->conn_handle, dsc->dsc.handle,
value, sizeof value, blecent_on_subscribe, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to subscribe to characteristic; "
"rc=%d\n", rc);
goto err;
}
return;
err:
/* Terminate the connection. */
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
/**
* Called when service discovery of the specified peer has completed.
*/
static void
blecent_on_disc_complete(const struct peer *peer, int status, void *arg)
{
if (status != 0) {
/* Service discovery failed. Terminate the connection. */
MODLOG_DFLT(ERROR, "Error: Service discovery failed; status=%d "
"conn_handle=%d\n", status, peer->conn_handle);
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return;
}
/* Service discovery has completed successfully. Now we have a complete
* list of services, characteristics, and descriptors that the peer
* supports.
*/
MODLOG_DFLT(ERROR, "Service discovery complete; status=%d "
"conn_handle=%d\n", status, peer->conn_handle);
/* Now perform three concurrent GATT procedures against the peer: read,
* write, and subscribe to notifications.
*/
blecent_read_write_subscribe(peer);
}
/**
* Initiates the GAP general discovery procedure.
*/
static void
blecent_scan(void)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
return;
}
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
disc_params.filter_duplicates = 1;
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 1;
/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
blecent_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n",
rc);
}
}
/**
* Indicates whether we should tre to connect to the sender of the specified
* advertisement. The function returns a positive result if the device
* advertises connectability and support for the Alert Notification service.
*/
static int
blecent_should_connect(const struct ble_gap_disc_desc *disc)
{
struct ble_hs_adv_fields fields;
int rc;
int i;
/* The device has to be advertising connectability. */
if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND &&
disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) {
return 0;
}
rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data);
if (rc != 0) {
return rc;
}
/* The device has to advertise support for the Alert Notification
* service (0x1811).
*/
for (i = 0; i < fields.num_uuids16; i++) {
if (ble_uuid_u16(&fields.uuids16[i].u) == BLECENT_SVC_ALERT_UUID) {
return 1;
}
}
return 0;
}
/**
* Connects to the sender of the specified advertisement of it looks
* interesting. A device is "interesting" if it advertises connectability and
* support for the Alert Notification service.
*/
static void
blecent_connect_if_interesting(const struct ble_gap_disc_desc *disc)
{
uint8_t own_addr_type;
int rc;
/* Don't do anything if we don't care about this advertiser. */
if (!blecent_should_connect(disc)) {
return;
}
/* Scanning must be stopped before a connection can be initiated. */
rc = ble_gap_disc_cancel();
if (rc != 0) {
MODLOG_DFLT(DEBUG, "Failed to cancel scan; rc=%d\n", rc);
return;
}
/* Figure out address to use for connect (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
return;
}
/* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for
* timeout.
*/
rc = ble_gap_connect(own_addr_type, &disc->addr, 30000, NULL,
blecent_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to connect to device; addr_type=%d "
"addr=%s\n; rc=%d",
disc->addr.type, addr_str(disc->addr.val), rc);
return;
}
}
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that is
* established. blecent uses the same callback for all connections.
*
* @param event The event being signalled.
* @param arg Application-specified argument; unused by
* blecent.
*
* @return 0 if the application successfully handled the
* event; nonzero on failure. The semantics
* of the return code is specific to the
* particular GAP event being signalled.
*/
static int
blecent_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_conn_desc desc;
struct ble_hs_adv_fields fields;
int rc;
switch (event->type) {
case BLE_GAP_EVENT_DISC:
rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
event->disc.length_data);
if (rc != 0) {
return 0;
}
/* An advertisment report was received during GAP discovery. */
print_adv_fields(&fields);
/* Try to connect to the advertiser if it looks interesting. */
blecent_connect_if_interesting(&event->disc);
return 0;
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
if (event->connect.status == 0) {
/* Connection successfully established. */
MODLOG_DFLT(INFO, "Connection established ");
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
/* Remember peer. */
rc = peer_add(event->connect.conn_handle);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to add peer; rc=%d\n", rc);
return 0;
}
/* Perform service discovery. */
rc = peer_disc_all(event->connect.conn_handle,
blecent_on_disc_complete, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc);
return 0;
}
} else {
/* Connection attempt failed; resume scanning. */
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
event->connect.status);
blecent_scan();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Forget about peer. */
peer_delete(event->disconnect.conn.conn_handle);
/* Resume scanning. */
blecent_scan();
return 0;
case BLE_GAP_EVENT_DISC_COMPLETE:
MODLOG_DFLT(INFO, "discovery complete; reason=%d\n",
event->disc_complete.reason);
return 0;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
MODLOG_DFLT(INFO, "encryption change event; status=%d ",
event->enc_change.status);
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
return 0;
case BLE_GAP_EVENT_NOTIFY_RX:
/* Peer sent us a notification or indication. */
MODLOG_DFLT(INFO, "received %s; conn_handle=%d attr_handle=%d "
"attr_len=%d\n",
event->notify_rx.indication ?
"indication" :
"notification",
event->notify_rx.conn_handle,
event->notify_rx.attr_handle,
OS_MBUF_PKTLEN(event->notify_rx.om));
/* Attribute data is contained in event->notify_rx.attr_data. */
return 0;
case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
return 0;
case BLE_GAP_EVENT_REPEAT_PAIRING:
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
*/
/* Delete the old bond. */
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
assert(rc == 0);
ble_store_util_delete_peer(&desc.peer_id_addr);
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
* continue with the pairing operation.
*/
return BLE_GAP_REPEAT_PAIRING_RETRY;
default:
return 0;
}
}
static void
blecent_on_reset(int reason)
{
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static void
blecent_on_sync(void)
{
int rc;
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Begin scanning for a peripheral to connect to. */
blecent_scan();
}
/**
* main
*
* All application logic and NimBLE host work is performed in default task.
*
* @return int NOTE: this function should never return!
*/
int
main(void)
{
int rc;
/* Initialize OS */
sysinit();
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-blecent");
assert(rc == 0);
/* os start should never return. If it does, this should be an error */
while (1) {
os_eventq_run(os_eventq_dflt_get());
}
return 0;
}

View file

@ -0,0 +1,209 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "blecent.h"
/**
* Utility function to log an array of bytes.
*/
void
print_bytes(const uint8_t *bytes, int len)
{
int i;
for (i = 0; i < len; i++) {
MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
}
}
void
print_mbuf(const struct os_mbuf *om)
{
int colon;
colon = 0;
while (om != NULL) {
if (colon) {
MODLOG_DFLT(DEBUG, ":");
} else {
colon = 1;
}
print_bytes(om->om_data, om->om_len);
om = SLIST_NEXT(om, om_next);
}
}
char *
addr_str(const void *addr)
{
static char buf[6 * 2 + 5 + 1];
const uint8_t *u8p;
u8p = addr;
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
return buf;
}
void
print_uuid(const ble_uuid_t *uuid)
{
char buf[BLE_UUID_STR_LEN];
MODLOG_DFLT(DEBUG, "%s", ble_uuid_to_str(uuid, buf));
}
/**
* Logs information about a connection to the console.
*/
void
print_conn_desc(const struct ble_gap_conn_desc *desc)
{
MODLOG_DFLT(DEBUG, "handle=%d our_ota_addr_type=%d our_ota_addr=%s ",
desc->conn_handle, desc->our_ota_addr.type,
addr_str(desc->our_ota_addr.val));
MODLOG_DFLT(DEBUG, "our_id_addr_type=%d our_id_addr=%s ",
desc->our_id_addr.type, addr_str(desc->our_id_addr.val));
MODLOG_DFLT(DEBUG, "peer_ota_addr_type=%d peer_ota_addr=%s ",
desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val));
MODLOG_DFLT(DEBUG, "peer_id_addr_type=%d peer_id_addr=%s ",
desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val));
MODLOG_DFLT(DEBUG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d "
"encrypted=%d authenticated=%d bonded=%d",
desc->conn_itvl, desc->conn_latency,
desc->supervision_timeout,
desc->sec_state.encrypted,
desc->sec_state.authenticated,
desc->sec_state.bonded);
}
void
print_adv_fields(const struct ble_hs_adv_fields *fields)
{
char s[BLE_HS_ADV_MAX_SZ];
const uint8_t *u8p;
int i;
if (fields->flags != 0) {
MODLOG_DFLT(DEBUG, " flags=0x%02x\n", fields->flags);
}
if (fields->uuids16 != NULL) {
MODLOG_DFLT(DEBUG, " uuids16(%scomplete)=",
fields->uuids16_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids16; i++) {
print_uuid(&fields->uuids16[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uuids32 != NULL) {
MODLOG_DFLT(DEBUG, " uuids32(%scomplete)=",
fields->uuids32_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids32; i++) {
print_uuid(&fields->uuids32[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uuids128 != NULL) {
MODLOG_DFLT(DEBUG, " uuids128(%scomplete)=",
fields->uuids128_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids128; i++) {
print_uuid(&fields->uuids128[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->name != NULL) {
assert(fields->name_len < sizeof s - 1);
memcpy(s, fields->name, fields->name_len);
s[fields->name_len] = '\0';
MODLOG_DFLT(DEBUG, " name(%scomplete)=%s\n",
fields->name_is_complete ? "" : "in", s);
}
if (fields->tx_pwr_lvl_is_present) {
MODLOG_DFLT(DEBUG, " tx_pwr_lvl=%d\n", fields->tx_pwr_lvl);
}
if (fields->slave_itvl_range != NULL) {
MODLOG_DFLT(DEBUG, " slave_itvl_range=");
print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->svc_data_uuid16 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid16=");
print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->public_tgt_addr != NULL) {
MODLOG_DFLT(DEBUG, " public_tgt_addr=");
u8p = fields->public_tgt_addr;
for (i = 0; i < fields->num_public_tgt_addrs; i++) {
MODLOG_DFLT(DEBUG, "public_tgt_addr=%s ", addr_str(u8p));
u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->appearance_is_present) {
MODLOG_DFLT(DEBUG, " appearance=0x%04x\n", fields->appearance);
}
if (fields->adv_itvl_is_present) {
MODLOG_DFLT(DEBUG, " adv_itvl=0x%04x\n", fields->adv_itvl);
}
if (fields->svc_data_uuid32 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid32=");
print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->svc_data_uuid128 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid128=");
print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uri != NULL) {
MODLOG_DFLT(DEBUG, " uri=");
print_bytes(fields->uri, fields->uri_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->mfg_data != NULL) {
MODLOG_DFLT(DEBUG, " mfg_data=");
print_bytes(fields->mfg_data, fields->mfg_data_len);
MODLOG_DFLT(DEBUG, "\n");
}
}

View file

@ -0,0 +1,807 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include "host/ble_hs.h"
#include "blecent.h"
static void *peer_svc_mem;
static struct os_mempool peer_svc_pool;
static void *peer_chr_mem;
static struct os_mempool peer_chr_pool;
static void *peer_dsc_mem;
static struct os_mempool peer_dsc_pool;
static void *peer_mem;
static struct os_mempool peer_pool;
static SLIST_HEAD(, peer) peers;
static struct peer_svc *
peer_svc_find_range(struct peer *peer, uint16_t attr_handle);
static struct peer_svc *
peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
struct peer_svc **out_prev);
int
peer_svc_is_empty(const struct peer_svc *svc);
uint16_t
chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr);
int
chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr);
static struct peer_chr *
peer_chr_find(const struct peer_svc *svc, uint16_t chr_def_handle,
struct peer_chr **out_prev);
static void
peer_disc_chrs(struct peer *peer);
static int
peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg);
static struct peer *
peer_find(uint16_t conn_handle)
{
struct peer *peer;
SLIST_FOREACH(peer, &peers, next) {
if (peer->conn_handle == conn_handle) {
return peer;
}
}
return NULL;
}
static void
peer_disc_complete(struct peer *peer, int rc)
{
peer->disc_prev_chr_val = 0;
/* Notify caller that discovery has completed. */
if (peer->disc_cb != NULL) {
peer->disc_cb(peer, rc, peer->disc_cb_arg);
}
}
static struct peer_dsc *
peer_dsc_find_prev(const struct peer_chr *chr, uint16_t dsc_handle)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
prev = NULL;
SLIST_FOREACH(dsc, &chr->dscs, next) {
if (dsc->dsc.handle >= dsc_handle) {
break;
}
prev = dsc;
}
return prev;
}
static struct peer_dsc *
peer_dsc_find(const struct peer_chr *chr, uint16_t dsc_handle,
struct peer_dsc **out_prev)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
prev = peer_dsc_find_prev(chr, dsc_handle);
if (prev == NULL) {
dsc = SLIST_FIRST(&chr->dscs);
} else {
dsc = SLIST_NEXT(prev, next);
}
if (dsc != NULL && dsc->dsc.handle != dsc_handle) {
dsc = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return dsc;
}
static int
peer_dsc_add(struct peer *peer, uint16_t chr_val_handle,
const struct ble_gatt_dsc *gatt_dsc)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
struct peer_svc *svc;
struct peer_chr *chr;
svc = peer_svc_find_range(peer, chr_val_handle);
if (svc == NULL) {
/* Can't find service for discovered descriptor; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
chr = peer_chr_find(svc, chr_val_handle, NULL);
if (chr == NULL) {
/* Can't find characteristic for discovered descriptor; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
dsc = peer_dsc_find(chr, gatt_dsc->handle, &prev);
if (dsc != NULL) {
/* Descriptor already discovered. */
return 0;
}
dsc = os_memblock_get(&peer_dsc_pool);
if (dsc == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(dsc, 0, sizeof *dsc);
dsc->dsc = *gatt_dsc;
if (prev == NULL) {
SLIST_INSERT_HEAD(&chr->dscs, dsc, next);
} else {
SLIST_NEXT(prev, next) = dsc;
}
return 0;
}
static void
peer_disc_dscs(struct peer *peer)
{
struct peer_chr *chr;
struct peer_svc *svc;
int rc;
/* Search through the list of discovered characteristics for the first
* characteristic that contains undiscovered descriptors. Then, discover
* all descriptors belonging to that characteristic.
*/
SLIST_FOREACH(svc, &peer->svcs, next) {
SLIST_FOREACH(chr, &svc->chrs, next) {
if (!chr_is_empty(svc, chr) &&
SLIST_EMPTY(&chr->dscs) &&
peer->disc_prev_chr_val <= chr->chr.def_handle) {
rc = ble_gattc_disc_all_dscs(peer->conn_handle,
chr->chr.val_handle,
chr_end_handle(svc, chr),
peer_dsc_disced, peer);
if (rc != 0) {
peer_disc_complete(peer, rc);
}
peer->disc_prev_chr_val = chr->chr.val_handle;
return;
}
}
}
/* All descriptors discovered. */
peer_disc_complete(peer, 0);
}
static int
peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_dsc_add(peer, chr_val_handle, dsc);
break;
case BLE_HS_EDONE:
/* All descriptors in this characteristic discovered; start discovering
* descriptors in the next characteristic.
*/
if (peer->disc_prev_chr_val > 0) {
peer_disc_dscs(peer);
}
rc = 0;
break;
default:
/* Error; abort discovery. */
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
uint16_t
chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr)
{
const struct peer_chr *next_chr;
next_chr = SLIST_NEXT(chr, next);
if (next_chr != NULL) {
return next_chr->chr.def_handle - 1;
} else {
return svc->svc.end_handle;
}
}
int
chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr)
{
return chr_end_handle(svc, chr) <= chr->chr.val_handle;
}
static struct peer_chr *
peer_chr_find_prev(const struct peer_svc *svc, uint16_t chr_val_handle)
{
struct peer_chr *prev;
struct peer_chr *chr;
prev = NULL;
SLIST_FOREACH(chr, &svc->chrs, next) {
if (chr->chr.val_handle >= chr_val_handle) {
break;
}
prev = chr;
}
return prev;
}
static struct peer_chr *
peer_chr_find(const struct peer_svc *svc, uint16_t chr_val_handle,
struct peer_chr **out_prev)
{
struct peer_chr *prev;
struct peer_chr *chr;
prev = peer_chr_find_prev(svc, chr_val_handle);
if (prev == NULL) {
chr = SLIST_FIRST(&svc->chrs);
} else {
chr = SLIST_NEXT(prev, next);
}
if (chr != NULL && chr->chr.val_handle != chr_val_handle) {
chr = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return chr;
}
static void
peer_chr_delete(struct peer_chr *chr)
{
struct peer_dsc *dsc;
while ((dsc = SLIST_FIRST(&chr->dscs)) != NULL) {
SLIST_REMOVE_HEAD(&chr->dscs, next);
os_memblock_put(&peer_dsc_pool, dsc);
}
os_memblock_put(&peer_chr_pool, chr);
}
static int
peer_chr_add(struct peer *peer, uint16_t svc_start_handle,
const struct ble_gatt_chr *gatt_chr)
{
struct peer_chr *prev;
struct peer_chr *chr;
struct peer_svc *svc;
svc = peer_svc_find(peer, svc_start_handle, NULL);
if (svc == NULL) {
/* Can't find service for discovered characteristic; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
chr = peer_chr_find(svc, gatt_chr->def_handle, &prev);
if (chr != NULL) {
/* Characteristic already discovered. */
return 0;
}
chr = os_memblock_get(&peer_chr_pool);
if (chr == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(chr, 0, sizeof *chr);
chr->chr = *gatt_chr;
if (prev == NULL) {
SLIST_INSERT_HEAD(&svc->chrs, chr, next);
} else {
SLIST_NEXT(prev, next) = chr;
}
return 0;
}
static int
peer_chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_chr_add(peer, peer->cur_svc->svc.start_handle, chr);
break;
case BLE_HS_EDONE:
/* All characteristics in this service discovered; start discovering
* characteristics in the next service.
*/
if (peer->disc_prev_chr_val > 0) {
peer_disc_chrs(peer);
}
rc = 0;
break;
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
static void
peer_disc_chrs(struct peer *peer)
{
struct peer_svc *svc;
int rc;
/* Search through the list of discovered service for the first service that
* contains undiscovered characteristics. Then, discover all
* characteristics belonging to that service.
*/
SLIST_FOREACH(svc, &peer->svcs, next) {
if (!peer_svc_is_empty(svc) && SLIST_EMPTY(&svc->chrs)) {
peer->cur_svc = svc;
rc = ble_gattc_disc_all_chrs(peer->conn_handle,
svc->svc.start_handle,
svc->svc.end_handle,
peer_chr_disced, peer);
if (rc != 0) {
peer_disc_complete(peer, rc);
}
return;
}
}
/* All characteristics discovered. */
peer_disc_dscs(peer);
}
int
peer_svc_is_empty(const struct peer_svc *svc)
{
return svc->svc.end_handle <= svc->svc.start_handle;
}
static struct peer_svc *
peer_svc_find_prev(struct peer *peer, uint16_t svc_start_handle)
{
struct peer_svc *prev;
struct peer_svc *svc;
prev = NULL;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (svc->svc.start_handle >= svc_start_handle) {
break;
}
prev = svc;
}
return prev;
}
static struct peer_svc *
peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
struct peer_svc **out_prev)
{
struct peer_svc *prev;
struct peer_svc *svc;
prev = peer_svc_find_prev(peer, svc_start_handle);
if (prev == NULL) {
svc = SLIST_FIRST(&peer->svcs);
} else {
svc = SLIST_NEXT(prev, next);
}
if (svc != NULL && svc->svc.start_handle != svc_start_handle) {
svc = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return svc;
}
static struct peer_svc *
peer_svc_find_range(struct peer *peer, uint16_t attr_handle)
{
struct peer_svc *svc;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (svc->svc.start_handle <= attr_handle &&
svc->svc.end_handle >= attr_handle) {
return svc;
}
}
return NULL;
}
const struct peer_svc *
peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid)
{
const struct peer_svc *svc;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (ble_uuid_cmp(&svc->svc.uuid.u, uuid) == 0) {
return svc;
}
}
return NULL;
}
const struct peer_chr *
peer_chr_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid)
{
const struct peer_svc *svc;
const struct peer_chr *chr;
svc = peer_svc_find_uuid(peer, svc_uuid);
if (svc == NULL) {
return NULL;
}
SLIST_FOREACH(chr, &svc->chrs, next) {
if (ble_uuid_cmp(&chr->chr.uuid.u, chr_uuid) == 0) {
return chr;
}
}
return NULL;
}
const struct peer_dsc *
peer_dsc_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid)
{
const struct peer_chr *chr;
const struct peer_dsc *dsc;
chr = peer_chr_find_uuid(peer, svc_uuid, chr_uuid);
if (chr == NULL) {
return NULL;
}
SLIST_FOREACH(dsc, &chr->dscs, next) {
if (ble_uuid_cmp(&dsc->dsc.uuid.u, dsc_uuid) == 0) {
return dsc;
}
}
return NULL;
}
static int
peer_svc_add(struct peer *peer, const struct ble_gatt_svc *gatt_svc)
{
struct peer_svc *prev;
struct peer_svc *svc;
svc = peer_svc_find(peer, gatt_svc->start_handle, &prev);
if (svc != NULL) {
/* Service already discovered. */
return 0;
}
svc = os_memblock_get(&peer_svc_pool);
if (svc == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(svc, 0, sizeof *svc);
svc->svc = *gatt_svc;
SLIST_INIT(&svc->chrs);
if (prev == NULL) {
SLIST_INSERT_HEAD(&peer->svcs, svc, next);
} else {
SLIST_INSERT_AFTER(prev, svc, next);
}
return 0;
}
static void
peer_svc_delete(struct peer_svc *svc)
{
struct peer_chr *chr;
while ((chr = SLIST_FIRST(&svc->chrs)) != NULL) {
SLIST_REMOVE_HEAD(&svc->chrs, next);
peer_chr_delete(chr);
}
os_memblock_put(&peer_svc_pool, svc);
}
static int
peer_svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_svc_add(peer, service);
break;
case BLE_HS_EDONE:
/* All services discovered; start discovering characteristics. */
if (peer->disc_prev_chr_val > 0) {
peer_disc_chrs(peer);
}
rc = 0;
break;
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
int
peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, void *disc_cb_arg)
{
struct peer_svc *svc;
struct peer *peer;
int rc;
peer = peer_find(conn_handle);
if (peer == NULL) {
return BLE_HS_ENOTCONN;
}
/* Undiscover everything first. */
while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
SLIST_REMOVE_HEAD(&peer->svcs, next);
peer_svc_delete(svc);
}
peer->disc_prev_chr_val = 1;
peer->disc_cb = disc_cb;
peer->disc_cb_arg = disc_cb_arg;
rc = ble_gattc_disc_all_svcs(conn_handle, peer_svc_disced, peer);
if (rc != 0) {
return rc;
}
return 0;
}
int
peer_delete(uint16_t conn_handle)
{
struct peer_svc *svc;
struct peer *peer;
int rc;
peer = peer_find(conn_handle);
if (peer == NULL) {
return BLE_HS_ENOTCONN;
}
SLIST_REMOVE(&peers, peer, peer, next);
while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
SLIST_REMOVE_HEAD(&peer->svcs, next);
peer_svc_delete(svc);
}
rc = os_memblock_put(&peer_pool, peer);
if (rc != 0) {
return BLE_HS_EOS;
}
return 0;
}
int
peer_add(uint16_t conn_handle)
{
struct peer *peer;
/* Make sure the connection handle is unique. */
peer = peer_find(conn_handle);
if (peer != NULL) {
return BLE_HS_EALREADY;
}
peer = os_memblock_get(&peer_pool);
if (peer == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(peer, 0, sizeof *peer);
peer->conn_handle = conn_handle;
SLIST_INSERT_HEAD(&peers, peer, next);
return 0;
}
static void
peer_free_mem(void)
{
free(peer_mem);
peer_mem = NULL;
free(peer_svc_mem);
peer_svc_mem = NULL;
free(peer_chr_mem);
peer_chr_mem = NULL;
free(peer_dsc_mem);
peer_dsc_mem = NULL;
}
int
peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs)
{
int rc;
/* Free memory first in case this function gets called more than once. */
peer_free_mem();
peer_mem = malloc(
OS_MEMPOOL_BYTES(max_peers, sizeof (struct peer)));
if (peer_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_pool, max_peers,
sizeof (struct peer), peer_mem,
"peer_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_svc_mem = malloc(
OS_MEMPOOL_BYTES(max_svcs, sizeof (struct peer_svc)));
if (peer_svc_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_svc_pool, max_svcs,
sizeof (struct peer_svc), peer_svc_mem,
"peer_svc_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_chr_mem = malloc(
OS_MEMPOOL_BYTES(max_chrs, sizeof (struct peer_chr)));
if (peer_chr_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_chr_pool, max_chrs,
sizeof (struct peer_chr), peer_chr_mem,
"peer_chr_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_dsc_mem = malloc(
OS_MEMPOOL_BYTES(max_dscs, sizeof (struct peer_dsc)));
if (peer_dsc_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_dsc_pool, max_dscs,
sizeof (struct peer_dsc), peer_dsc_mem,
"peer_dsc_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
return 0;
err:
peer_free_mem();
return rc;
}

View file

@ -0,0 +1,30 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
syscfg.vals:
# DEBUG logging is a bit noisy; use INFO.
LOG_LEVEL: 1
# Default task settings
OS_MAIN_STACK_SIZE: 336
# Disable peripheral and broadcaster roles.
BLE_ROLE_BROADCASTER: 0
BLE_ROLE_CENTRAL: 1
BLE_ROLE_OBSERVER: 1
BLE_ROLE_PERIPHERAL: 0

View file

@ -0,0 +1,9 @@
# BLE Cycling Speed and Cadence peripheral app.
The source files are located in the src/ directory.
pkg.yml contains the base definition of the app.
syscfg.yml contains setting definitions and overrides.

View file

@ -0,0 +1,40 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
pkg.name: apps/blecsc
pkg.type: app
pkg.description: BLE peripheral cycling speed and cadence sensor.
pkg.author: "Maciej Jurczak"
pkg.email: "mjurczak@gmail.com"
pkg.homepage: "http://mynewt.apache.org/"
pkg.keywords:
pkg.deps:
- "@apache-mynewt-core/kernel/os"
- "@apache-mynewt-core/sys/console/full"
- "@apache-mynewt-core/sys/log/full"
- "@apache-mynewt-core/sys/log/modlog"
- "@apache-mynewt-core/sys/stats/full"
- "@apache-mynewt-core/sys/sysinit"
- "@apache-mynewt-core/sys/id"
- nimble/controller
- nimble/host
- nimble/host/services/gap
- nimble/host/services/gatt
- nimble/host/store/config
- nimble/transport

View file

@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef H_BLECSC_SENSOR_
#define H_BLECSC_SENSOR_
#include "modlog/modlog.h"
#include "nimble/ble.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Cycling Speed and Cadence configuration */
#define GATT_CSC_UUID 0x1816
#define GATT_CSC_MEASUREMENT_UUID 0x2A5B
#define GATT_CSC_FEATURE_UUID 0x2A5C
#define GATT_SENSOR_LOCATION_UUID 0x2A5D
#define GATT_SC_CONTROL_POINT_UUID 0x2A55
/* Device Information configuration */
#define GATT_DEVICE_INFO_UUID 0x180A
#define GATT_MANUFACTURER_NAME_UUID 0x2A29
#define GATT_MODEL_NUMBER_UUID 0x2A24
/*CSC Measurement flags*/
#define CSC_MEASUREMENT_WHEEL_REV_PRESENT 0x01
#define CSC_MEASUREMENT_CRANK_REV_PRESENT 0x02
/* CSC feature flags */
#define CSC_FEATURE_WHEEL_REV_DATA 0x01
#define CSC_FEATURE_CRANK_REV_DATA 0x02
#define CSC_FEATURE_MULTIPLE_SENSOR_LOC 0x04
/* Sensor location enum */
#define SENSOR_LOCATION_OTHER 0
#define SENSOR_LOCATION_TOP_OF_SHOE 1
#define SENSOR_LOCATION_IN_SHOE 2
#define SENSOR_LOCATION_HIP 3
#define SENSOR_LOCATION_FRONT_WHEEL 4
#define SENSOR_LOCATION_LEFT_CRANK 5
#define SENSOR_LOCATION_RIGHT_CRANK 6
#define SENSOR_LOCATION_LEFT_PEDAL 7
#define SENSOR_LOCATION_RIGHT_PEDAL 8
#define SENSOR_LOCATION_FROT_HUB 9
#define SENSOR_LOCATION_REAR_DROPOUT 10
#define SENSOR_LOCATION_CHAINSTAY 11
#define SENSOR_LOCATION_REAR_WHEEL 12
#define SENSOR_LOCATION_REAR_HUB 13
#define SENSOR_LOCATION_CHEST 14
#define SENSOR_LOCATION_SPIDER 15
#define SENSOR_LOCATION_CHAIN_RING 16
/* SC Control Point op codes */
#define SC_CP_OP_SET_CUMULATIVE_VALUE 1
#define SC_CP_OP_START_SENSOR_CALIBRATION 2
#define SC_CP_OP_UPDATE_SENSOR_LOCATION 3
#define SC_CP_OP_REQ_SUPPORTED_SENSOR_LOCATIONS 4
#define SC_CP_OP_RESPONSE 16
/*SC Control Point response values */
#define SC_CP_RESPONSE_SUCCESS 1
#define SC_CP_RESPONSE_OP_NOT_SUPPORTED 2
#define SC_CP_RESPONSE_INVALID_PARAM 3
#define SC_CP_RESPONSE_OP_FAILED 4
/* CSC simulation configuration */
#define CSC_FEATURES (CSC_FEATURE_WHEEL_REV_DATA | \
CSC_FEATURE_CRANK_REV_DATA |\
CSC_FEATURE_MULTIPLE_SENSOR_LOC)
struct ble_csc_measurement_state {
uint32_t cumulative_wheel_rev;
uint16_t last_wheel_evt_time;
uint16_t cumulative_crank_rev;
uint16_t last_crank_evt_time;
};
extern uint16_t csc_measurement_handle;
extern uint16_t csc_control_point_handle;
int gatt_svr_init(struct ble_csc_measurement_state * csc_measurement_state);
int gatt_svr_chr_notify_csc_measurement(uint16_t conn_handle);
void gatt_svr_set_cp_indicate(uint8_t indication_status);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,385 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "os/mynewt.h"
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "blecsc_sens.h"
#define CSC_ERR_CCC_DESC_IMPROPERLY_CONFIGURED 0x81
static const char *manuf_name = "Apache Mynewt";
static const char *model_num = "Mynewt CSC Sensor";
static const uint8_t csc_supported_sensor_locations[] = {
SENSOR_LOCATION_FRONT_WHEEL,
SENSOR_LOCATION_REAR_DROPOUT,
SENSOR_LOCATION_CHAINSTAY,
SENSOR_LOCATION_REAR_WHEEL
};
static uint8_t sensor_location = SENSOR_LOCATION_REAR_DROPOUT;
static struct ble_csc_measurement_state * measurement_state;
uint16_t csc_measurement_handle;
uint16_t csc_control_point_handle;
uint8_t csc_cp_indication_status;
static int
gatt_svr_chr_access_csc_measurement(uint16_t conn_handle,
uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static int
gatt_svr_chr_access_csc_feature(uint16_t conn_handle,
uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static int
gatt_svr_chr_access_sensor_location(uint16_t conn_handle,
uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static int
gatt_svr_chr_access_sc_control_point(uint16_t conn_handle,
uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static int
gatt_svr_chr_access_device_info(uint16_t conn_handle,
uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
{
/* Service: Cycling Speed and Cadence */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(GATT_CSC_UUID),
.characteristics = (struct ble_gatt_chr_def[]) { {
/* Characteristic: Cycling Speed and Cadence Measurement */
.uuid = BLE_UUID16_DECLARE(GATT_CSC_MEASUREMENT_UUID),
.access_cb = gatt_svr_chr_access_csc_measurement,
.val_handle = &csc_measurement_handle,
.flags = BLE_GATT_CHR_F_NOTIFY,
}, {
/* Characteristic: Cycling Speed and Cadence features */
.uuid = BLE_UUID16_DECLARE(GATT_CSC_FEATURE_UUID),
.access_cb = gatt_svr_chr_access_csc_feature,
.flags = BLE_GATT_CHR_F_READ,
}, {
/* Characteristic: Sensor Location */
.uuid = BLE_UUID16_DECLARE(GATT_SENSOR_LOCATION_UUID),
.access_cb = gatt_svr_chr_access_sensor_location,
.flags = BLE_GATT_CHR_F_READ,
}, {
/* Characteristic: SC Control Point*/
.uuid = BLE_UUID16_DECLARE(GATT_SC_CONTROL_POINT_UUID),
.access_cb = gatt_svr_chr_access_sc_control_point,
.val_handle = &csc_control_point_handle,
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE,
}, {
0, /* No more characteristics in this service */
}, }
},
{
/* Service: Device Information */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(GATT_DEVICE_INFO_UUID),
.characteristics = (struct ble_gatt_chr_def[]) { {
/* Characteristic: * Manufacturer name */
.uuid = BLE_UUID16_DECLARE(GATT_MANUFACTURER_NAME_UUID),
.access_cb = gatt_svr_chr_access_device_info,
.flags = BLE_GATT_CHR_F_READ,
}, {
/* Characteristic: Model number string */
.uuid = BLE_UUID16_DECLARE(GATT_MODEL_NUMBER_UUID),
.access_cb = gatt_svr_chr_access_device_info,
.flags = BLE_GATT_CHR_F_READ,
}, {
0, /* No more characteristics in this service */
}, }
},
{
0, /* No more services */
},
};
static int
gatt_svr_chr_access_csc_measurement(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
return BLE_ATT_ERR_READ_NOT_PERMITTED;
}
static int
gatt_svr_chr_access_csc_feature(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
static const uint16_t csc_feature = CSC_FEATURES;
int rc;
assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
rc = os_mbuf_append(ctxt->om, &csc_feature, sizeof(csc_feature));
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
static int
gatt_svr_chr_access_sensor_location(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
int rc;
assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
rc = os_mbuf_append(ctxt->om, &sensor_location, sizeof(sensor_location));
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
static int
gatt_svr_chr_access_sc_control_point(uint16_t conn_handle,
uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
uint8_t op_code;
uint8_t new_sensor_location;
uint8_t new_cumulative_wheel_rev_arr[4];
struct os_mbuf *om_indication;
uint8_t response = SC_CP_RESPONSE_OP_NOT_SUPPORTED;
int ii;
int rc;
assert(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR);
if (!csc_cp_indication_status) {
MODLOG_DFLT(INFO, "SC Control Point; CCC descriptor "
"improperly configured");
return CSC_ERR_CCC_DESC_IMPROPERLY_CONFIGURED;
}
/* Read control point op code*/
rc = os_mbuf_copydata(ctxt->om, 0, sizeof(op_code), &op_code);
if (rc != 0){
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
MODLOG_DFLT(INFO, "SC Control Point; opcode=%d\n", op_code);
/* Allocate response buffer */
om_indication = ble_hs_mbuf_att_pkt();
switch(op_code){
#if (CSC_FEATURES & CSC_FEATURE_WHEEL_REV_DATA)
case SC_CP_OP_SET_CUMULATIVE_VALUE:
/* Read new cumulative wheel revolutions value*/
rc = os_mbuf_copydata(ctxt->om, 1,
sizeof(new_cumulative_wheel_rev_arr),
new_cumulative_wheel_rev_arr);
if (rc != 0){
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
measurement_state->cumulative_wheel_rev =
get_le32(new_cumulative_wheel_rev_arr);
MODLOG_DFLT(INFO, "SC Control Point; Set cumulative value = %d\n",
measurement_state->cumulative_wheel_rev);
response = SC_CP_RESPONSE_SUCCESS;
break;
#endif
#if (CSC_FEATURES & CSC_FEATURE_MULTIPLE_SENSOR_LOC)
case SC_CP_OP_UPDATE_SENSOR_LOCATION:
/* Read new sensor location value*/
rc = os_mbuf_copydata(ctxt->om, 1, 1, &new_sensor_location);
if (rc != 0){
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
MODLOG_DFLT(INFO, "SC Control Point; Sensor location update = %d\n",
new_sensor_location);
/* Verify if requested new location is on supported locations list */
response = SC_CP_RESPONSE_INVALID_PARAM;
for (ii = 0; ii < sizeof(csc_supported_sensor_locations); ii++){
if (new_sensor_location == csc_supported_sensor_locations[ii]){
sensor_location = new_sensor_location;
response = SC_CP_RESPONSE_SUCCESS;
break;
}
}
break;
case SC_CP_OP_REQ_SUPPORTED_SENSOR_LOCATIONS:
response = SC_CP_RESPONSE_SUCCESS;
break;
#endif
default:
break;
}
/* Append response value */
rc = os_mbuf_append(om_indication, &response, sizeof(response));
if (rc != 0){
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
#if (CSC_FEATURES & CSC_FEATURE_MULTIPLE_SENSOR_LOC)
/* In case of supported locations request append locations list */
if (op_code == SC_CP_OP_REQ_SUPPORTED_SENSOR_LOCATIONS){
rc = os_mbuf_append(om_indication, &csc_supported_sensor_locations,
sizeof(csc_supported_sensor_locations));
}
if (rc != 0){
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
#endif
rc = ble_gattc_indicate_custom(conn_handle, csc_control_point_handle,
om_indication);
return rc;
}
static int
gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
uint16_t uuid;
int rc;
uuid = ble_uuid_u16(ctxt->chr->uuid);
if (uuid == GATT_MODEL_NUMBER_UUID) {
rc = os_mbuf_append(ctxt->om, model_num, strlen(model_num));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
if (uuid == GATT_MANUFACTURER_NAME_UUID) {
rc = os_mbuf_append(ctxt->om, manuf_name, strlen(manuf_name));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
int
gatt_svr_chr_notify_csc_measurement(uint16_t conn_handle)
{
int rc;
struct os_mbuf *om;
uint8_t data_buf[11];
uint8_t data_offset = 1;
memset(data_buf, 0, sizeof(data_buf));
#if (CSC_FEATURES & CSC_FEATURE_WHEEL_REV_DATA)
data_buf[0] |= CSC_MEASUREMENT_WHEEL_REV_PRESENT;
put_le16(&(data_buf[5]), measurement_state->last_wheel_evt_time);
put_le32(&(data_buf[1]), measurement_state->cumulative_wheel_rev);
data_offset += 6;
#endif
#if (CSC_FEATURES & CSC_FEATURE_CRANK_REV_DATA)
data_buf[0] |= CSC_MEASUREMENT_CRANK_REV_PRESENT;
put_le16(&(data_buf[data_offset]),
measurement_state->cumulative_crank_rev);
put_le16(&(data_buf[data_offset + 2]),
measurement_state->last_crank_evt_time);
data_offset += 4;
#endif
om = ble_hs_mbuf_from_flat(data_buf, data_offset);
rc = ble_gattc_notify_custom(conn_handle, csc_measurement_handle, om);
return rc;
}
void
gatt_svr_set_cp_indicate(uint8_t indication_status)
{
csc_cp_indication_status = indication_status;
}
void
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
char buf[BLE_UUID_STR_LEN];
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
ctxt->svc.handle);
break;
case BLE_GATT_REGISTER_OP_CHR:
MODLOG_DFLT(DEBUG, "registering characteristic %s with "
"def_handle=%d val_handle=%d\n",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle,
ctxt->chr.val_handle);
break;
case BLE_GATT_REGISTER_OP_DSC:
MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
ctxt->dsc.handle);
break;
default:
assert(0);
break;
}
}
int
gatt_svr_init(struct ble_csc_measurement_state * csc_measurement_state)
{
int rc;
rc = ble_gatts_count_cfg(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
rc = ble_gatts_add_svcs(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
measurement_state = csc_measurement_state;
return 0;
}

View file

@ -0,0 +1,310 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "os/mynewt.h"
#include "console/console.h"
#include "config/config.h"
#include "nimble/ble.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "blecsc_sens.h"
/* Wheel size for simulation calculations */
#define CSC_SIM_WHEEL_CIRCUMFERENCE_MM 2000
/* Simulated cadence lower limit */
#define CSC_SIM_CRANK_RPM_MIN 20
/* Simulated cadence upper limit */
#define CSC_SIM_CRANK_RPM_MAX 100
/* Simulated speed lower limit */
#define CSC_SIM_SPEED_KPH_MIN 0
/* Simulated speed upper limit */
#define CSC_SIM_SPEED_KPH_MAX 35
/* Noticication status */
static bool notify_state = false;
/* Connection handle */
static uint16_t conn_handle;
static uint8_t blecsc_addr_type;
/* Advertised device name */
static const char *device_name = "blecsc_sensor";
/* Measurement and notification timer */
static struct os_callout blecsc_measure_timer;
/* Variable holds current CSC measurement state */
static struct ble_csc_measurement_state csc_measurement_state;
/* Variable holds simulted speed (kilometers per hour) */
static uint16_t csc_sim_speed_kph = CSC_SIM_SPEED_KPH_MIN;
/* Variable holds simulated cadence (RPM) */
static uint8_t csc_sim_crank_rpm = CSC_SIM_CRANK_RPM_MIN;
static int blecsc_gap_event(struct ble_gap_event *event, void *arg);
/*
* Enables advertising with parameters:
* o General discoverable mode
* o Undirected connectable mode
*/
static void
blecsc_advertise(void)
{
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
int rc;
/*
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info)
* o Advertising tx power
* o Device name
*/
memset(&fields, 0, sizeof(fields));
/*
* Advertise two flags:
* o Discoverability in forthcoming advertisement (general)
* o BLE-only (BR/EDR unsupported)
*/
fields.flags = BLE_HS_ADV_F_DISC_GEN |
BLE_HS_ADV_F_BREDR_UNSUP;
/*
* Indicate that the TX power level field should be included; have the
* stack fill this value automatically. This is done by assigning the
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
*/
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
/*
* Set appearance.
*/
fields.appearance = ble_svc_gap_device_appearance();
fields.appearance_is_present = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
return;
}
/* Begin advertising */
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(blecsc_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, blecsc_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
return;
}
}
/* Update simulated CSC measurements.
* Each call increments wheel and crank revolution counters by one and
* computes last event time in order to match simulated candence and speed.
* Last event time is expressedd in 1/1024th of second units.
*
* 60 * 1024
* crank_dt = --------------
* cadence[RPM]
*
*
* circumference[mm] * 1024 * 60 * 60
* wheel_dt = -------------------------------------
* 10^6 * speed [kph]
*/
static void
blecsc_simulate_speed_and_cadence(void)
{
uint16_t wheel_rev_period;
uint16_t crank_rev_period;
/* Update simulated crank and wheel rotation speed */
csc_sim_speed_kph++;
if (csc_sim_speed_kph >= CSC_SIM_SPEED_KPH_MAX) {
csc_sim_speed_kph = CSC_SIM_SPEED_KPH_MIN;
}
csc_sim_crank_rpm++;
if (csc_sim_crank_rpm >= CSC_SIM_CRANK_RPM_MAX) {
csc_sim_crank_rpm = CSC_SIM_CRANK_RPM_MIN;
}
/* Calculate simulated measurement values */
if (csc_sim_speed_kph > 0){
wheel_rev_period = (36*64*CSC_SIM_WHEEL_CIRCUMFERENCE_MM) /
(625*csc_sim_speed_kph);
csc_measurement_state.cumulative_wheel_rev++;
csc_measurement_state.last_wheel_evt_time += wheel_rev_period;
}
if (csc_sim_crank_rpm > 0){
crank_rev_period = (60*1024) / csc_sim_crank_rpm;
csc_measurement_state.cumulative_crank_rev++;
csc_measurement_state.last_crank_evt_time += crank_rev_period;
}
MODLOG_DFLT(INFO, "CSC simulated values: speed = %d kph, cadence = %d \n",
csc_sim_speed_kph, csc_sim_crank_rpm);
}
/* Run CSC measurement simulation and notify it to the client */
static void
blecsc_measurement(struct os_event *ev)
{
int rc;
rc = os_callout_reset(&blecsc_measure_timer, OS_TICKS_PER_SEC);
assert(rc == 0);
blecsc_simulate_speed_and_cadence();
if (notify_state) {
rc = gatt_svr_chr_notify_csc_measurement(conn_handle);
assert(rc == 0);
}
}
static int
blecsc_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed */
MODLOG_DFLT(INFO, "connection %s; status=%d\n",
event->connect.status == 0 ? "established" : "failed",
event->connect.status);
if (event->connect.status != 0) {
/* Connection failed; resume advertising */
blecsc_advertise();
conn_handle = 0;
}
else {
conn_handle = event->connect.conn_handle;
}
break;
case BLE_GAP_EVENT_DISCONNECT:
MODLOG_DFLT(INFO, "disconnect; reason=%d\n", event->disconnect.reason);
conn_handle = 0;
/* Connection terminated; resume advertising */
blecsc_advertise();
break;
case BLE_GAP_EVENT_ADV_COMPLETE:
MODLOG_DFLT(INFO, "adv complete\n");
break;
case BLE_GAP_EVENT_SUBSCRIBE:
MODLOG_DFLT(INFO, "subscribe event attr_handle=%d\n",
event->subscribe.attr_handle);
if (event->subscribe.attr_handle == csc_measurement_handle) {
notify_state = event->subscribe.cur_notify;
MODLOG_DFLT(INFO, "csc measurement notify state = %d\n",
notify_state);
}
else if (event->subscribe.attr_handle == csc_control_point_handle) {
gatt_svr_set_cp_indicate(event->subscribe.cur_indicate);
MODLOG_DFLT(INFO, "csc control point indicate state = %d\n",
event->subscribe.cur_indicate);
}
break;
case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.value);
break;
}
return 0;
}
static void
blecsc_on_sync(void)
{
int rc;
/* Figure out address to use while advertising (no privacy) */
rc = ble_hs_id_infer_auto(0, &blecsc_addr_type);
assert(rc == 0);
/* Begin advertising */
blecsc_advertise();
}
/*
* main
*
* The main task for the project. This function initializes the packages,
* then starts serving events from default event queue.
*
* @return int NOTE: this function should never return!
*/
int
main(void)
{
int rc;
/* Initialize OS */
sysinit();
/* Initialize the NimBLE host configuration */
ble_hs_cfg.sync_cb = blecsc_on_sync;
/* Initialize measurement and notification timer */
os_callout_init(&blecsc_measure_timer, os_eventq_dflt_get(),
blecsc_measurement, NULL);
rc = os_callout_reset(&blecsc_measure_timer, OS_TICKS_PER_SEC);
assert(rc == 0);
rc = gatt_svr_init(&csc_measurement_state);
assert(rc == 0);
/* Set the default device name */
rc = ble_svc_gap_device_name_set(device_name);
assert(rc == 0);
/* As the last thing, process events from default event queue */
while (1) {
os_eventq_run(os_eventq_dflt_get());
}
return 0;
}

View file

@ -0,0 +1,38 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
syscfg.vals:
# Disable central and observer roles.
BLE_ROLE_BROADCASTER: 1
BLE_ROLE_CENTRAL: 0
BLE_ROLE_OBSERVER: 0
BLE_ROLE_PERIPHERAL: 1
# Disable unused eddystone feature.
BLE_EDDYSTONE: 0
# Log reboot messages to a flash circular buffer.
REBOOT_LOG_FCB: 1
LOG_FCB: 1
CONFIG_FCB: 1
# Set public device address.
BLE_PUBLIC_DEV_ADDR: ((uint8_t[6]){0xcc, 0xbb, 0xaa, 0x33, 0x22, 0x11})
# Set device appearance to Cycling Speed and Cadence Sensor
BLE_SVC_GAP_APPEARANCE: BLE_SVC_GAP_APPEARANCE_CYC_SPEED_AND_CADENCE_SENSOR

View file

@ -0,0 +1,34 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
pkg.name: apps/blehci
pkg.type: app
pkg.description: BLE controller application exposing HCI over external interface
pkg.author: "Johan Hedberg <johan.hedberg@intel.com>"
pkg.homepage: "http://mynewt.apache.org/"
pkg.keywords:
pkg.deps:
- "@apache-mynewt-core/sys/console/stub"
- "@apache-mynewt-core/sys/log/stub"
- "@apache-mynewt-core/sys/stats/full"
- "@apache-mynewt-core/kernel/os"
- nimble/controller
- nimble/transport
pkg.req_apis:
- ble_transport

View file

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include "os/mynewt.h"
int
main(void)
{
/* Initialize OS */
sysinit();
while (1) {
os_eventq_run(os_eventq_dflt_get());
}
return 0;
}

View file

@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
syscfg.vals:
# Default task settings
OS_MAIN_STACK_SIZE: 64
# Use UART transport by default
BLE_HCI_TRANSPORT: uart

View file

@ -0,0 +1,9 @@
# BLE Heart Rate peripheral app.
The source files are located in the src/ directory.
pkg.yml contains the base definition of the app.
syscfg.yml contains setting definitions and overrides.

View file

@ -0,0 +1,40 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
pkg.name: apps/blehr
pkg.type: app
pkg.description: BLE peripheral heartrate sensor.
pkg.author: "Szymon Czapracki"
pkg.email: "szymon.czapracki@codecoup.pl"
pkg.homepage: "http://mynewt.apache.org/"
pkg.keywords:
pkg.deps:
- "@apache-mynewt-core/kernel/os"
- "@apache-mynewt-core/sys/console/full"
- "@apache-mynewt-core/sys/log/full"
- "@apache-mynewt-core/sys/log/modlog"
- "@apache-mynewt-core/sys/stats/full"
- "@apache-mynewt-core/sys/sysinit"
- "@apache-mynewt-core/sys/id"
- nimble/controller
- nimble/host
- nimble/host/services/gap
- nimble/host/services/gatt
- nimble/host/store/config
- nimble/transport/ram

View file

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef H_BLEHR_SENSOR_
#define H_BLEHR_SENSOR_
#include "nimble/ble.h"
#include "modlog/modlog.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Heart-rate configuration */
#define GATT_HRS_UUID 0x180D
#define GATT_HRS_MEASUREMENT_UUID 0x2A37
#define GATT_HRS_BODY_SENSOR_LOC_UUID 0x2A38
#define GATT_DEVICE_INFO_UUID 0x180A
#define GATT_MANUFACTURER_NAME_UUID 0x2A29
#define GATT_MODEL_NUMBER_UUID 0x2A24
extern uint16_t hrs_hrm_handle;
int gatt_svr_init(void);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,177 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "blehr_sens.h"
static const char *manuf_name = "Apache Mynewt";
static const char *model_num = "Mynewt HR Sensor";
uint16_t hrs_hrm_handle;
static int
gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
static int
gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
{
/* Service: Heart-rate */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(GATT_HRS_UUID),
.characteristics = (struct ble_gatt_chr_def[]) { {
/* Characteristic: Heart-rate measurement */
.uuid = BLE_UUID16_DECLARE(GATT_HRS_MEASUREMENT_UUID),
.access_cb = gatt_svr_chr_access_heart_rate,
.val_handle = &hrs_hrm_handle,
.flags = BLE_GATT_CHR_F_NOTIFY,
}, {
/* Characteristic: Body sensor location */
.uuid = BLE_UUID16_DECLARE(GATT_HRS_BODY_SENSOR_LOC_UUID),
.access_cb = gatt_svr_chr_access_heart_rate,
.flags = BLE_GATT_CHR_F_READ,
}, {
0, /* No more characteristics in this service */
}, }
},
{
/* Service: Device Information */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(GATT_DEVICE_INFO_UUID),
.characteristics = (struct ble_gatt_chr_def[]) { {
/* Characteristic: * Manufacturer name */
.uuid = BLE_UUID16_DECLARE(GATT_MANUFACTURER_NAME_UUID),
.access_cb = gatt_svr_chr_access_device_info,
.flags = BLE_GATT_CHR_F_READ,
}, {
/* Characteristic: Model number string */
.uuid = BLE_UUID16_DECLARE(GATT_MODEL_NUMBER_UUID),
.access_cb = gatt_svr_chr_access_device_info,
.flags = BLE_GATT_CHR_F_READ,
}, {
0, /* No more characteristics in this service */
}, }
},
{
0, /* No more services */
},
};
static int
gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
/* Sensor location, set to "Chest" */
static uint8_t body_sens_loc = 0x01;
uint16_t uuid;
int rc;
uuid = ble_uuid_u16(ctxt->chr->uuid);
if (uuid == GATT_HRS_BODY_SENSOR_LOC_UUID) {
rc = os_mbuf_append(ctxt->om, &body_sens_loc, sizeof(body_sens_loc));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
static int
gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
uint16_t uuid;
int rc;
uuid = ble_uuid_u16(ctxt->chr->uuid);
if (uuid == GATT_MODEL_NUMBER_UUID) {
rc = os_mbuf_append(ctxt->om, model_num, strlen(model_num));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
if (uuid == GATT_MANUFACTURER_NAME_UUID) {
rc = os_mbuf_append(ctxt->om, manuf_name, strlen(manuf_name));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
void
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
char buf[BLE_UUID_STR_LEN];
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
ctxt->svc.handle);
break;
case BLE_GATT_REGISTER_OP_CHR:
MODLOG_DFLT(DEBUG, "registering characteristic %s with "
"def_handle=%d val_handle=%d\n",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle,
ctxt->chr.val_handle);
break;
case BLE_GATT_REGISTER_OP_DSC:
MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
ctxt->dsc.handle);
break;
default:
assert(0);
break;
}
}
int
gatt_svr_init(void)
{
int rc;
rc = ble_gatts_count_cfg(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
rc = ble_gatts_add_svcs(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
return 0;
}

View file

@ -0,0 +1,261 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "os/mynewt.h"
#include "console/console.h"
#include "config/config.h"
#include "nimble/ble.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "blehr_sens.h"
static bool notify_state;
/* Connection handle */
static uint16_t conn_handle;
static const char *device_name = "blehr_sensor";
static int blehr_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t blehr_addr_type;
/* Sending notify data timer */
static struct os_callout blehr_tx_timer;
/* Variable to simulate heart beats */
static uint8_t heartrate = 90;
/*
* Enables advertising with parameters:
* o General discoverable mode
* o Undirected connectable mode
*/
static void
blehr_advertise(void)
{
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
int rc;
/*
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info)
* o Advertising tx power
* o Device name
*/
memset(&fields, 0, sizeof(fields));
/*
* Advertise two flags:
* o Discoverability in forthcoming advertisement (general)
* o BLE-only (BR/EDR unsupported)
*/
fields.flags = BLE_HS_ADV_F_DISC_GEN |
BLE_HS_ADV_F_BREDR_UNSUP;
/*
* Indicate that the TX power level field should be included; have the
* stack fill this value automatically. This is done by assigning the
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
*/
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
return;
}
/* Begin advertising */
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(blehr_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, blehr_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
return;
}
}
static void
blehr_tx_hrate_stop(void)
{
os_callout_stop(&blehr_tx_timer);
}
/* Reset heartrate measurment */
static void
blehr_tx_hrate_reset(void)
{
int rc;
rc = os_callout_reset(&blehr_tx_timer, OS_TICKS_PER_SEC);
assert(rc == 0);
}
/* This functions simulates heart beat and notifies it to the client */
static void
blehr_tx_hrate(struct os_event *ev)
{
static uint8_t hrm[2];
int rc;
struct os_mbuf *om;
if (!notify_state) {
blehr_tx_hrate_stop();
heartrate = 90;
return;
}
hrm[0] = 0x06; /* contact of a sensor */
hrm[1] = heartrate; /* storing dummy data */
/* Simulation of heart beats */
heartrate++;
if (heartrate == 160) {
heartrate = 90;
}
om = ble_hs_mbuf_from_flat(hrm, sizeof(hrm));
rc = ble_gattc_notify_custom(conn_handle, hrs_hrm_handle, om);
assert(rc == 0);
blehr_tx_hrate_reset();
}
static int
blehr_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed */
MODLOG_DFLT(INFO, "connection %s; status=%d\n",
event->connect.status == 0 ? "established" : "failed",
event->connect.status);
if (event->connect.status != 0) {
/* Connection failed; resume advertising */
blehr_advertise();
conn_handle = 0;
}
else {
conn_handle = event->connect.conn_handle;
}
break;
case BLE_GAP_EVENT_DISCONNECT:
MODLOG_DFLT(INFO, "disconnect; reason=%d\n", event->disconnect.reason);
conn_handle = BLE_HS_CONN_HANDLE_NONE; /* reset conn_handle */
/* Connection terminated; resume advertising */
blehr_advertise();
break;
case BLE_GAP_EVENT_ADV_COMPLETE:
MODLOG_DFLT(INFO, "adv complete\n");
blehr_advertise();
break;
case BLE_GAP_EVENT_SUBSCRIBE:
MODLOG_DFLT(INFO, "subscribe event; cur_notify=%d\n value handle; "
"val_handle=%d\n",
event->subscribe.cur_notify, hrs_hrm_handle);
if (event->subscribe.attr_handle == hrs_hrm_handle) {
notify_state = event->subscribe.cur_notify;
blehr_tx_hrate_reset();
} else if (event->subscribe.attr_handle != hrs_hrm_handle) {
notify_state = event->subscribe.cur_notify;
blehr_tx_hrate_stop();
}
break;
case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.value);
break;
}
return 0;
}
static void
blehr_on_sync(void)
{
int rc;
/* Use privacy */
rc = ble_hs_id_infer_auto(0, &blehr_addr_type);
assert(rc == 0);
/* Begin advertising */
blehr_advertise();
}
/*
* main
*
* The main task for the project. This function initializes the packages,
* then starts serving events from default event queue.
*
* @return int NOTE: this function should never return!
*/
int
main(void)
{
int rc;
/* Initialize OS */
sysinit();
/* Initialize the NimBLE host configuration */
ble_hs_cfg.sync_cb = blehr_on_sync;
os_callout_init(&blehr_tx_timer, os_eventq_dflt_get(),
blehr_tx_hrate, NULL);
rc = gatt_svr_init();
assert(rc == 0);
/* Set the default device name */
rc = ble_svc_gap_device_name_set(device_name);
assert(rc == 0);
/* As the last thing, process events from default event queue */
while (1) {
os_eventq_run(os_eventq_dflt_get());
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show more