diff --git a/README.md b/README.md index f43a7339a..6d945a599 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ These networking examples are only available if Wi-Fi is supported by the board. App|Description ---|--- [picow_access_point](pico_w/wifi/access_point) | Starts a WiFi access point, and fields DHCP requests. +[picow_access_point_wifi_provisioning](pico_w/wifi/access_point_wifi_provisioning) | Starts a WiFi access point, and allows WiFi credentials to be provisioned through a web page. [picow_blink](pico_w/wifi/blink) | Blinks the on-board LED (which is connected via the WiFi chip). [picow_blink_slow_clock](pico_w/wifi/blink) | Blinks the on-board LED (which is connected via the WiFi chip) with a slower system clock to show how to reconfigure communication with the WiFi chip at run time under those circumstances. [picow_blink_fast_clock](pico_w/wifi/blink) | Blinks the on-board LED (which is connected via the WiFi chip) with a faster system clock to show how to reconfigure communication with the WiFi chip at build time under those circumstances. @@ -296,8 +297,11 @@ Some standalone Bluetooth examples (without all the common example build infrast App|Description ---|--- [picow_ble_temp_sensor](pico_w/bt/standalone) | Reads from the on board temperature sensor and sends notifications via BLE. -[picow_ble_temp_sensor_with_wifi](pico_w/bt/standalone) | Same as above but also connects to Wi-Fi and starts an "iperf" server. -[picow_ble_temp_reader](pico_w/bt/standalone) | Connects to one of the above "sensors" and reads the temperature. +[picow_ble_temp_reader](pico_w/bt/standalone) | Connects to the above sensor and reads the temperature. +[picow_ble_pointer](pico_w/bt/standalone/ble_pointer) | Bluetooth HID mouse using mpu6050 to detect angle and move cursor. +[picow_ble_doorbell](pico_w/bt/standalone/doorbell) | Detects button press on transmitter Pico and illuminates LED on reciever Pico. +[picow_ble_secure_temp_sensor](pico_w/bt/standalone/secure_temp_sensor) | Variant of picow_ble_temp_sensor which allows exploration of LE_secure configurations. +[picow_ble_wifi_provisioner](pico_w/bt/standalone/wifi_provisioner) | Allows WiFi credentials to be provisioned over BLE, either using a mobile app or with the included python script. ### PIO diff --git a/pico_w/bt/standalone/CMakeLists.txt b/pico_w/bt/standalone/CMakeLists.txt index ef03fe482..19eea4404 100644 --- a/pico_w/bt/standalone/CMakeLists.txt +++ b/pico_w/bt/standalone/CMakeLists.txt @@ -1,2 +1,6 @@ -add_subdirectory(client) -add_subdirectory(server) +add_subdirectory(secure_temp_sensor) +add_subdirectory(doorbell) +add_subdirectory(wifi_provisioner) +add_subdirectory(ble_pointer) +add_subdirectory(temp_sensor) + diff --git a/pico_w/bt/standalone/ble_pointer/CMakeLists.txt b/pico_w/bt/standalone/ble_pointer/CMakeLists.txt new file mode 100644 index 000000000..9230bb4da --- /dev/null +++ b/pico_w/bt/standalone/ble_pointer/CMakeLists.txt @@ -0,0 +1,37 @@ +# Add mpu6050 libary +add_library(mpu6050_i2c_lib INTERFACE) +target_sources(mpu6050_i2c_lib INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/mpu6050_i2c_lib.c + ) +target_include_directories(mpu6050_i2c_lib INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ) +target_link_libraries(mpu6050_i2c_lib INTERFACE + pico_stdlib + hardware_i2c + ) + +# Add executable. Default name is the project name, version 0.1 +add_executable(ble_pointer + ble_pointer.c + ) + +# Add the standard library to the build +target_link_libraries(ble_pointer + pico_stdlib + pico_btstack_ble + pico_btstack_cyw43 + pico_cyw43_arch_none + mpu6050_i2c_lib + ) + +# Add the standard include files to the build +target_include_directories(ble_pointer PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. + ) + +pico_btstack_make_gatt_header(ble_pointer PRIVATE "${CMAKE_CURRENT_LIST_DIR}/ble_pointer.gatt") + +pico_add_extra_outputs(ble_pointer) + diff --git a/pico_w/bt/standalone/ble_pointer/README.md b/pico_w/bt/standalone/ble_pointer/README.md new file mode 100644 index 000000000..f1c4157e9 --- /dev/null +++ b/pico_w/bt/standalone/ble_pointer/README.md @@ -0,0 +1,7 @@ +### BLE pointer + +This example is based on BTstack's 'hog_mouse_demo' and demonstrates a Bluetooth HID mouse. Cursor position is controlled by mpu6050 angle measurements, allowing you to point at the screen and move the mouse cursor. + +To use this example connect a mpu6050 (which can be found at https://thepihut.com/products/6-dof-sensor-mpu6050) to the pico, with SDA connected to pin 4 and SCL connected to pin 5. Also, connect 2 buttons to pins 15 and 16 for left and right click. + +Once powered, you should be able to pair your computer with 'HID Mouse' and use the pointer. diff --git a/pico_w/bt/standalone/ble_pointer/ble_pointer.c b/pico_w/bt/standalone/ble_pointer/ble_pointer.c new file mode 100644 index 000000000..cdcfc3e20 --- /dev/null +++ b/pico_w/bt/standalone/ble_pointer/ble_pointer.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2017 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +//#define BTSTACK_FILE__ "hog_mouse_demo.c" + +// ***************************************************************************** +/* EXAMPLE_START(hog_mouse_demo): HID Mouse LE + */ +// ***************************************************************************** + +#include +#include +#include +#include +#include + +#include "ble_pointer.h" + +#include "btstack.h" + +#include "ble/gatt-service/battery_service_server.h" +#include "ble/gatt-service/device_information_service_server.h" +#include "ble/gatt-service/hids_device.h" + +#include "pico/cyw43_arch.h" +#include "pico/btstack_cyw43.h" +#include "pico/stdlib.h" +#include "hardware/i2c.h" + +#include "pico/binary_info.h" + +// for mpu6050 +#include +#include + +#define ALPHA 0.05 // for complimentary filter, big alpha gives faster reponse but more noise + +// FS values are 0, 1, 2, or 3 +// For gyro, correspond to += 250, 500, 1000, 2000 deg/s +// For accel, correspond to += 2, 4, 8, 16g +#define GYRO_FS 0 +#define ACCEL_FS 0 + +// how many readings taken to find gyro offsets +#define OFFSET_NUM 10000 + +//pins for buttons +#define LEFT_BUTTON_GPIO_NUM 15 +#define RIGHT_BUTTON_GPIO_NUM 16 + +float roll; +float pitch; +float yaw; + +float roll_offset; +float pitch_offset; +float yaw_offset; + +// start in top left corner +int abs_x = 0; +int abs_y = 0; + +// from USB HID Specification 1.1, Appendix B.2 +const uint8_t hid_descriptor_mouse_boot_mode[] = { + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xa1, 0x01, // COLLECTION (Application) + + 0x85, 0x01, // Report ID 1 + + 0x09, 0x01, // USAGE (Pointer) + + 0xa1, 0x00, // COLLECTION (Physical) + +#if 1 + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) +#endif + +#if 1 + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x06, // INPUT (Data,Var,Rel) +#endif + + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION +}; + +static btstack_packet_callback_registration_t hci_event_callback_registration; +static btstack_packet_callback_registration_t l2cap_event_callback_registration; +static btstack_packet_callback_registration_t sm_event_callback_registration; +static uint8_t battery = 100; +static hci_con_handle_t con_handle = HCI_CON_HANDLE_INVALID; +static uint8_t protocol_mode = 1; + +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); + +const uint8_t adv_data[] = { + // Flags general discoverable, BR/EDR not supported + 0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06, + // Name + 0x0a, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'H', 'I', 'D', ' ', 'M', 'o', 'u', 's', 'e', + // 16-bit Service UUIDs + 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE & 0xff, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE >> 8, + // Appearance HID - Mouse (Category 15, Sub-Category 2) + 0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC2, 0x03, +}; +const uint8_t adv_data_len = sizeof(adv_data); + +static void hog_mouse_setup(void){ + + // initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1) + if (cyw43_arch_init()) { + printf("failed to initialise cyw43_arch\n"); + } + + // setup l2cap + l2cap_init(); + + // setup SM: Display only + sm_init(); + sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING); + + // setup ATT server + att_server_init(profile_data, NULL, NULL); + + // setup battery service + battery_service_server_init(battery); + + // setup device information service + device_information_service_server_init(); + + // setup HID Device service + hids_device_init(0, hid_descriptor_mouse_boot_mode, sizeof(hid_descriptor_mouse_boot_mode)); + + // setup advertisements + uint16_t adv_int_min = 0x0030; + uint16_t adv_int_max = 0x0030; + uint8_t adv_type = 0; + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); + + // register for events + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + // register for connection parameter updates + l2cap_event_callback_registration.callback = &packet_handler; + l2cap_add_event_handler(&l2cap_event_callback_registration); + + sm_event_callback_registration.callback = &packet_handler; + sm_add_event_handler(&sm_event_callback_registration); + + hids_device_register_packet_handler(packet_handler); +} + +// HID Report sending +static void send_report(uint8_t buttons, int8_t dx, int8_t dy){ + uint8_t report[] = { buttons, (uint8_t) dx, (uint8_t) dy }; + switch (protocol_mode){ + case 0: + hids_device_send_boot_mouse_input_report(con_handle, report, sizeof(report)); + break; + case 1: + hids_device_send_input_report(con_handle, report, sizeof(report)); + break; + default: + break; + } +} + +static int dx; +static int dy; +static uint8_t buttons; + +#define MOUSE_PERIOD_MS 15 +#define SCREEN_WIDTH 1920 +#define SCREEN_HEIGHT 1080 + +static btstack_timer_source_t mousing_timer; +static int mousing_active = 0; + +static void mousing_timer_handler(btstack_timer_source_t * ts){ + + if (con_handle == HCI_CON_HANDLE_INVALID) { + mousing_active = 0; + return; + } + + mpu6050_fusion_output(roll_offset, pitch_offset, yaw_offset, ALPHA, MOUSE_PERIOD_MS, &roll, &pitch, &yaw, GYRO_FS); + + // move proportional to roll/yaw and pitch (if within boundary, otherwise dont move) + // int desired_x = SCREEN_WIDTH/2 + (SCREEN_WIDTH/2) * roll/90; // fully right at 90 degrees, fully left at -90 degrees + int desired_x = SCREEN_WIDTH/2 + (SCREEN_WIDTH/2) * - yaw/45; // alternative x based on yaw rather than roll (prone to drift) + int desired_y = SCREEN_HEIGHT/2 + (SCREEN_HEIGHT/2) * pitch/45; // fully top at 45 degrees, fully bottom at -45 degrees + + dx = desired_x - abs_x; + dy = desired_y - abs_y; + + if (abs_x + dx < 0 || abs_x + dx > SCREEN_WIDTH) { // about to move off side of screen + dx = 0; + } + + if (abs_y + dy < 0 || abs_y + dy > SCREEN_HEIGHT) { // about to move off top/bottom of screen + dy = 0; + } + + abs_x += dx; + abs_y += dy; + + // trigger send + hids_device_request_can_send_now_event(con_handle); + + // set next timer + btstack_run_loop_set_timer(ts, MOUSE_PERIOD_MS); + btstack_run_loop_add_timer(ts); +} + +// IRQ handler for mouse buttons +void button_irq_handler(uint gpio, uint32_t events) { + if (gpio == LEFT_BUTTON_GPIO_NUM) { + printf("left click!\n"); + if (!gpio_get(gpio)) { + buttons = 1; + } else { + buttons = 0; + } + } else if (gpio == RIGHT_BUTTON_GPIO_NUM) { + printf("right click!\n"); + if (!gpio_get(gpio)) { + buttons = 2; + } else { + buttons = 0; + } + } +} + +static void hid_embedded_start_mousing(void){ + if (mousing_active) return; + mousing_active = 1; + + printf("Start mousing..\n"); + + // set one-shot timer + mousing_timer.process = &mousing_timer_handler; + btstack_run_loop_set_timer(&mousing_timer, MOUSE_PERIOD_MS); + btstack_run_loop_add_timer(&mousing_timer); +} + +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + uint16_t conn_interval; + + if (packet_type != HCI_EVENT_PACKET) return; + + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_DISCONNECTION_COMPLETE: + con_handle = HCI_CON_HANDLE_INVALID; + printf("Disconnected\n"); + break; + case SM_EVENT_JUST_WORKS_REQUEST: + printf("Just Works requested\n"); + sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); + break; + case SM_EVENT_NUMERIC_COMPARISON_REQUEST: + printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); + sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); + break; + case SM_EVENT_PASSKEY_DISPLAY_NUMBER: + printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); + break; + case L2CAP_EVENT_CONNECTION_PARAMETER_UPDATE_RESPONSE: + printf("L2CAP Connection Parameter Update Complete, response: %x\n", l2cap_event_connection_parameter_update_response_get_result(packet)); + break; + case HCI_EVENT_META_GAP: + switch (hci_event_gap_meta_get_subevent_code(packet)) { + case GAP_SUBEVENT_LE_CONNECTION_COMPLETE: + // print connection parameters (without using float operations) + conn_interval = gap_subevent_le_connection_complete_get_conn_interval(packet); + printf("LE Connection Complete:\n"); + printf("- Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3)); + printf("- Connection Latency: %u\n", gap_subevent_le_connection_complete_get_conn_latency(packet)); + break; + default: + break; + } + break; + case HCI_EVENT_LE_META: + switch (hci_event_le_meta_get_subevent_code(packet)) { + case HCI_SUBEVENT_LE_CONNECTION_UPDATE_COMPLETE: + // print connection parameters (without using float operations) + conn_interval = hci_subevent_le_connection_update_complete_get_conn_interval(packet); + printf("LE Connection Update:\n"); + printf("- Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3)); + printf("- Connection Latency: %u\n", hci_subevent_le_connection_update_complete_get_conn_latency(packet)); + break; + default: + break; + } + break; + case HCI_EVENT_HIDS_META: + switch (hci_event_hids_meta_get_subevent_code(packet)){ + case HIDS_SUBEVENT_INPUT_REPORT_ENABLE: + con_handle = hids_subevent_input_report_enable_get_con_handle(packet); + printf("Report Characteristic Subscribed %u\n", hids_subevent_input_report_enable_get_enable(packet)); +#ifndef HAVE_BTSTACK_STDIN + hid_embedded_start_mousing(); +#endif + // request connection param update via L2CAP following Apple Bluetooth Design Guidelines + // gap_request_connection_parameter_update(con_handle, 12, 12, 4, 100); // 15 ms, 4, 1s + + // directly update connection params via HCI following Apple Bluetooth Design Guidelines + // gap_update_connection_parameters(con_handle, 12, 12, 4, 100); // 60-75 ms, 4, 1s + + break; + case HIDS_SUBEVENT_BOOT_KEYBOARD_INPUT_REPORT_ENABLE: + con_handle = hids_subevent_boot_keyboard_input_report_enable_get_con_handle(packet); + printf("Boot Keyboard Characteristic Subscribed %u\n", hids_subevent_boot_keyboard_input_report_enable_get_enable(packet)); + break; + case HIDS_SUBEVENT_BOOT_MOUSE_INPUT_REPORT_ENABLE: + con_handle = hids_subevent_boot_mouse_input_report_enable_get_con_handle(packet); + printf("Boot Mouse Characteristic Subscribed %u\n", hids_subevent_boot_mouse_input_report_enable_get_enable(packet)); + break; + case HIDS_SUBEVENT_PROTOCOL_MODE: + protocol_mode = hids_subevent_protocol_mode_get_protocol_mode(packet); + printf("Protocol Mode: %s mode\n", hids_subevent_protocol_mode_get_protocol_mode(packet) ? "Report" : "Boot"); + break; + case HIDS_SUBEVENT_CAN_SEND_NOW: + send_report(buttons, dx, dy); + break; + default: + break; + } + break; + + default: + break; + } +} + +int main(void) { + stdio_init_all(); + +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) + #warning Example requires a board with I2C pins + puts("Default I2C pins were not defined"); + return 1; +#endif + + // Make the I2C pins available to picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + // mouse buttons + gpio_init(LEFT_BUTTON_GPIO_NUM); + gpio_init(RIGHT_BUTTON_GPIO_NUM); + + gpio_set_dir(LEFT_BUTTON_GPIO_NUM, GPIO_IN); + gpio_set_dir(RIGHT_BUTTON_GPIO_NUM, GPIO_IN); + + gpio_pull_up(LEFT_BUTTON_GPIO_NUM); + gpio_pull_up(RIGHT_BUTTON_GPIO_NUM); + + gpio_set_irq_enabled_with_callback(LEFT_BUTTON_GPIO_NUM, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true, &button_irq_handler); + gpio_set_irq_enabled(RIGHT_BUTTON_GPIO_NUM, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true); + + // First initialise mpu6050 + // initialise to min full scale(+=250deg/s and +=2g) + mpu6050_initialise(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GYRO_FS, ACCEL_FS); + mpu6050_reset(); + + // get gyro offsets + mpu6050_get_gyro_offset(OFFSET_NUM, &roll_offset, &pitch_offset, &yaw_offset, GYRO_FS); + + // get initial roll and pitch values based on accelerometer + int16_t acceleration[3], gyro[3], temp; + mpu6050_read_raw(acceleration, gyro, &temp); + roll = atan2(acceleration[1] , acceleration[2]) * 57.3; + pitch = atan2((- acceleration[0]) , sqrt(acceleration[1] * acceleration[1] + acceleration[2] * acceleration[2])) * 57.3; + + // assume yaw starts at 0 degrees (no absolute reference) + yaw = 0; + + // now setup BLE + hog_mouse_setup(); + +#ifdef HAVE_BTSTACK_STDIN + btstack_stdin_setup(stdin_process); +#endif + + // turn on! + hci_power_control(HCI_POWER_ON); + + while (true) { + sleep_ms(1000); + } + return 0; +} diff --git a/pico_w/bt/standalone/ble_pointer/ble_pointer.fzz b/pico_w/bt/standalone/ble_pointer/ble_pointer.fzz new file mode 100644 index 000000000..347d8a82d Binary files /dev/null and b/pico_w/bt/standalone/ble_pointer/ble_pointer.fzz differ diff --git a/pico_w/bt/standalone/ble_pointer/ble_pointer.gatt b/pico_w/bt/standalone/ble_pointer/ble_pointer.gatt new file mode 100644 index 000000000..b2459f553 --- /dev/null +++ b/pico_w/bt/standalone/ble_pointer/ble_pointer.gatt @@ -0,0 +1,48 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "HID Mouse" + +PRIMARY_SERVICE, GATT_SERVICE +CHARACTERISTIC, GATT_DATABASE_HASH, READ, + +// add Battery Service +// Battery Service 180F +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL, DYNAMIC | READ | NOTIFY, + +// add Device ID Service +// Device Information 180A +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_DEVICE_INFORMATION +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_MANUFACTURER_NAME_STRING, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_MODEL_NUMBER_STRING, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_SERIAL_NUMBER_STRING, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HARDWARE_REVISION_STRING, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_FIRMWARE_REVISION_STRING, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_SOFTWARE_REVISION_STRING, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_SYSTEM_ID, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_PNP_ID, DYNAMIC | READ, + +// add HID Service +// Human Interface Device 1812 +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE, DYNAMIC | READ | WRITE_WITHOUT_RESPONSE, + +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16, +// fixed report id = 1, type = Input (1) +REPORT_REFERENCE, READ, 1, 1 + +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE | ENCRYPTION_KEY_SIZE_16, +// fixed report id = 2, type = Output (2) +REPORT_REFERENCE, READ, 2, 2 + +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16, +// fixed report id = 3, type = Feature (3) +REPORT_REFERENCE, READ, 3, 3 + +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT, DYNAMIC | READ | WRITE | NOTIFY, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT, DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT, DYNAMIC | READ | WRITE | NOTIFY, +// bcdHID = 0x101 (v1.0.1), bCountryCode 0, remote wakeable = 0 | normally connectable 2 +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION, READ, 01 01 00 02 +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT, DYNAMIC | WRITE_WITHOUT_RESPONSE, diff --git a/pico_w/bt/standalone/ble_pointer/ble_pointer_bb.png b/pico_w/bt/standalone/ble_pointer/ble_pointer_bb.png new file mode 100644 index 000000000..14acd872e Binary files /dev/null and b/pico_w/bt/standalone/ble_pointer/ble_pointer_bb.png differ diff --git a/pico_w/bt/standalone/client/btstack_config.h b/pico_w/bt/standalone/ble_pointer/btstack_config.h similarity index 100% rename from pico_w/bt/standalone/client/btstack_config.h rename to pico_w/bt/standalone/ble_pointer/btstack_config.h diff --git a/pico_w/bt/standalone/ble_pointer/mpu6050_i2c_lib.c b/pico_w/bt/standalone/ble_pointer/mpu6050_i2c_lib.c new file mode 100644 index 000000000..cd767454d --- /dev/null +++ b/pico_w/bt/standalone/ble_pointer/mpu6050_i2c_lib.c @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/stdlib.h" +#include "pico/binary_info.h" +#include "hardware/i2c.h" +#include "mpu6050_i2c_lib.h" +#include + +#define RAD_TO_DEG(r) (r) * 57.3 + +// Defualt address +static int addr = 0x68; + +void mpu6050_reset(void) { + // Two byte reset. First byte register, second byte data + // There are a load more options to set up the device in different ways that could be added here + uint8_t buf[] = {0x6B, 0x80}; + i2c_write_blocking(i2c_default, addr, buf, 2, false); + sleep_ms(100); // Allow device to reset and stabilize + + // Clear sleep mode (0x6B register, 0x00 value) + buf[1] = 0x00; // Clear sleep mode by writing 0x00 to the 0x6B register + i2c_write_blocking(i2c_default, addr, buf, 2, false); + sleep_ms(10); // Allow stabilization after waking up +} + +void mpu6050_read_raw(int16_t accel[3], int16_t gyro[3], int16_t *temp) { + // For this particular device, we send the device the register we want to read + // first, then subsequently read from the device. The register is auto incrementing + // so we don't need to keep sending the register we want, just the first. + + uint8_t buffer[6]; + + // Start reading acceleration registers from register 0x3B for 6 bytes + // accel measurments are from 0x3B to 0x40 + uint8_t val = 0x3B; + i2c_write_blocking(i2c_default, addr, &val, 1, true); // true to keep master control of bus + i2c_read_blocking(i2c_default, addr, buffer, 6, false); + + for (int i = 0; i < 3; i++) { + // register 3B contains accel_xout[15:8] + // next register contains accel_xout[7:0] + // so we bit shift 3B contents by 8 and concatinate with contents of resister 3C + // repeat for each pair of registers + accel[i] = (buffer[i * 2] << 8 | buffer[(i * 2) + 1]); + } + + // Now gyro data from reg 0x43 for 6 bytes + // The register is auto incrementing on each read + val = 0x43; + i2c_write_blocking(i2c_default, addr, &val, 1, true); + i2c_read_blocking(i2c_default, addr, buffer, 6, false); // False - finished with bus + + for (int i = 0; i < 3; i++) { + gyro[i] = (buffer[i * 2] << 8 | buffer[(i * 2) + 1]);; + } + + // Now temperature from reg 0x41 for 2 bytes + // The register is auto incrementing on each read + val = 0x41; + i2c_write_blocking(i2c_default, addr, &val, 1, true); + i2c_read_blocking(i2c_default, addr, buffer, 2, false); // False - finished with bus + + *temp = buffer[0] << 8 | buffer[1]; +} + +// FS values are 0, 1, 2, or 3 +// For gyro, correspond to += 250, 500, 1000, 2000 deg/s +// For accel, correspond to += 2, 4, 8, 16g +int mpu6050_initialise(int sda_pin, int scl_pin, int gyro_fs, int accel_fs) { + // Set the i2c pins + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(sda_pin, GPIO_FUNC_I2C); + gpio_set_function(scl_pin, GPIO_FUNC_I2C); + gpio_pull_up(sda_pin); + gpio_pull_up(scl_pin); + + // set full scale ranges for gyro and accel + // register 1B is gyro config - bit 4 and bit 3 are used for full scale select + uint8_t buffer[1]; + const uint8_t gyro_config = 0x1B; + + // first read + i2c_write_blocking(i2c_default, addr, &gyro_config, 1, true); + i2c_read_blocking(i2c_default, addr, buffer, 1, false); + + uint8_t to_write = buffer[0] | ((gyro_fs & 3) << 3); // Set gyro bits + + uint8_t write_buf[2] = {gyro_config, to_write}; + i2c_write_blocking(i2c_default, addr, write_buf, 1, false); + + // register 1C is for accel config + const uint8_t accel_config = 0x1C; + + // first read + i2c_write_blocking(i2c_default, addr, &accel_config, 1, true); + i2c_read_blocking(i2c_default, addr, buffer, 1, false); + + to_write = buffer[0] | ((accel_fs & 3) << 3); // Set accel bits + + write_buf[0] = accel_config; + write_buf[1] = to_write; + i2c_write_blocking(i2c_default, addr, write_buf, 1, false); + + return 0; +} + +// function takes num sensor readings to get gyro offsets +// sets roll_offset, pitch_offset and yaw_offset +void mpu6050_get_gyro_offset(int num, float *roll_offset, float *pitch_offset, float *yaw_offset, int gyro_fs) { + float r_offset = 0; + float p_offset = 0; + float y_offset = 0; + int16_t acceleration[3], gyro[3], temp; + + // can calculate gyro full scale range from the setting using 250 * 2^setting + int range = 250 * pow(2, gyro_fs); + + const float scale_factor = 1 / 32768.0 * range; + for (int i = 0; i < num; i++) { + mpu6050_read_raw(acceleration, gyro, &temp); + r_offset += gyro[0] * scale_factor; + p_offset += gyro[1] * scale_factor; + y_offset += gyro[2] * scale_factor; + } + r_offset /= num; + p_offset /= num; + y_offset /= num; + + printf("roll offset %f\n", r_offset); + printf("pitch offset %f\n", p_offset); + printf("yaw offset is %f\n", y_offset); + + *roll_offset = r_offset; + *pitch_offset = p_offset; + *yaw_offset = y_offset; +} + +// function outputs roll, pitch using sensor fusion +void mpu6050_fusion_output(float roll_offset, float pitch_offset, float yaw_offset, float alpha, int delta_ms, float *roll, float *pitch, float *yaw, int gyro_fs) { + int16_t acceleration[3], gyro[3], temp; + mpu6050_read_raw(acceleration, gyro, &temp); + // calculate pitch and roll from accelerometer output + // this calculation is independent of full scale + float accel_roll = RAD_TO_DEG(atan2(acceleration[1] , acceleration[2])); + float accel_pitch = RAD_TO_DEG(atan2((- acceleration[0]) , hypot(acceleration[1], acceleration[2]))); + + // can calculate gyro full scale range from the setting using 250 * 2^setting + int range = 250 * pow(2, gyro_fs); + float roll_rate = gyro[0] / 32768.0 * range - roll_offset; + float pitch_rate = gyro[1] / 32768.0 * range - pitch_offset; + float yaw_rate = gyro[2] / 32768.0 * range - yaw_offset; + + // calculate pitch and roll using sensor fusion (complimentary filter) + *roll = (1 - alpha) * (*roll + roll_rate * delta_ms/1000) + alpha * accel_roll; + *pitch = (1 - alpha) * (*pitch + pitch_rate * delta_ms/1000) + alpha * accel_pitch; + *yaw = *yaw + yaw_rate * delta_ms/1000; +} diff --git a/pico_w/bt/standalone/ble_pointer/mpu6050_i2c_lib.h b/pico_w/bt/standalone/ble_pointer/mpu6050_i2c_lib.h new file mode 100644 index 000000000..e46ccb716 --- /dev/null +++ b/pico_w/bt/standalone/ble_pointer/mpu6050_i2c_lib.h @@ -0,0 +1,12 @@ +#include + +#ifndef MPU6050_I2C_LIB +#define MPU6050_I2C_LIB + +void mpu6050_reset(void); +void mpu6050_read_raw(int16_t accel[3], int16_t gyro[3], int16_t *temp); +int mpu6050_initialise(int SDA_pin, int SCL_pin, int GYRO_FS, int ACCEL_FS); +void mpu6050_get_gyro_offset(int num, float *roll_offset, float *pitch_offset, float *yaw_offset, int gyro_fs); +void mpu6050_fusion_output(float roll_offset, float pitch_offset, float yaw_offset, float alpha, int delta_ms, float *roll, float *pitch, float *yaw, int gyro_fs); + +#endif \ No newline at end of file diff --git a/pico_w/bt/standalone/doorbell/CMakeLists.txt b/pico_w/bt/standalone/doorbell/CMakeLists.txt new file mode 100644 index 000000000..1242efdfb --- /dev/null +++ b/pico_w/bt/standalone/doorbell/CMakeLists.txt @@ -0,0 +1,42 @@ +# Standalone example that connects to the server Pico and flashes an external LED when the button is pressed +# Flashes the on-board LED once quickly each second when it's running but not connected to another device +# Flashes the on-board LED twice quickly each second when connected to another device +add_executable(doorbell_client + client.c + ) +target_link_libraries(doorbell_client + pico_stdlib + pico_btstack_ble + pico_btstack_cyw43 + pico_cyw43_arch_none + ) +target_include_directories(doorbell_client PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # For our common btstack config + ) +target_compile_definitions(doorbell_client PRIVATE + RUNNING_AS_CLIENT=1 +) + +pico_add_extra_outputs(doorbell_client) + + +# Standalone example that connects to the client Pico and detects button inputs +# Flashes slowly each second to show it's running +add_executable(doorbell_server + server.c + ) +target_link_libraries(doorbell_server + pico_stdlib + pico_btstack_ble + pico_btstack_cyw43 + pico_cyw43_arch_none + ) +target_include_directories(doorbell_server PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # For our common btstack config + ) +pico_btstack_make_gatt_header(doorbell_server PRIVATE "${CMAKE_CURRENT_LIST_DIR}/doorbell.gatt") + +pico_add_extra_outputs(doorbell_server) + diff --git a/pico_w/bt/standalone/doorbell/README.md b/pico_w/bt/standalone/doorbell/README.md new file mode 100644 index 000000000..72f5c2ec9 --- /dev/null +++ b/pico_w/bt/standalone/doorbell/README.md @@ -0,0 +1,5 @@ +### Doorbell + +This example uses BLE to communicate between 2 Pico Ws. When the button on one Pico is pressed, the LED connected to the second Pico will illuminate. + +To use this example, connect a LED in series with a 330 ohm resistor between pin 15 and ground on the client Pico. On the server Pico, connect a button between pin 15 and ground. \ No newline at end of file diff --git a/pico_w/bt/standalone/server/btstack_config.h b/pico_w/bt/standalone/doorbell/btstack_config.h similarity index 100% rename from pico_w/bt/standalone/server/btstack_config.h rename to pico_w/bt/standalone/doorbell/btstack_config.h diff --git a/pico_w/bt/standalone/doorbell/client.c b/pico_w/bt/standalone/doorbell/client.c new file mode 100644 index 000000000..89cece27c --- /dev/null +++ b/pico_w/bt/standalone/doorbell/client.c @@ -0,0 +1,303 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "btstack.h" +#include "pico/cyw43_arch.h" +#include "pico/stdlib.h" + +#ifndef NDEBG +#define DEBUG_LOG(...) printf(__VA_ARGS__) +#else +#define DEBUG_LOG(...) +#endif + +#define LED_QUICK_FLASH_DELAY_MS 100 +#define LED_SLOW_FLASH_DELAY_MS 1000 + +#define EXT_LED_GPIO_NUM 15 + +typedef enum { + TC_OFF, + TC_IDLE, + TC_W4_SCAN_RESULT, + TC_W4_CONNECT, + TC_W4_SERVICE_RESULT, + TC_W4_CHARACTERISTIC_RESULT, + TC_W4_ENABLE_NOTIFICATIONS_COMPLETE, + TC_W4_READY +} gc_state_t; + +static btstack_packet_callback_registration_t hci_event_callback_registration; +static gc_state_t state = TC_OFF; +static bd_addr_t server_addr; +static bd_addr_type_t server_addr_type; +static hci_con_handle_t connection_handle; +static gatt_client_service_t server_service; +static gatt_client_characteristic_t server_characteristic; +static bool listener_registered; +static gatt_client_notification_t notification_listener; +static btstack_timer_source_t heartbeat; + +static void client_start(void){ + DEBUG_LOG("Start scanning!\n"); + state = TC_W4_SCAN_RESULT; + gap_set_scan_parameters(0,0x0030, 0x0030); + gap_start_scan(); +} + +static bool advertisement_report_contains_service(uint16_t service, uint8_t *advertisement_report){ + // get advertisement from report event + const uint8_t * adv_data = gap_event_advertising_report_get_data(advertisement_report); + uint8_t adv_len = gap_event_advertising_report_get_data_length(advertisement_report); + + // iterate over advertisement data + ad_context_t context; + for (ad_iterator_init(&context, adv_len, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){ + uint8_t data_type = ad_iterator_get_data_type(&context); + uint8_t data_size = ad_iterator_get_data_len(&context); + const uint8_t * data = ad_iterator_get_data(&context); + switch (data_type){ + case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: + for (int i = 0; i < data_size; i += 2) { + uint16_t type = little_endian_read_16(data, i); + if (type == service) return true; + } + default: + break; + } + } + return false; +} + +static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(packet_type); + UNUSED(channel); + UNUSED(size); + + uint8_t att_status; + switch(state){ + case TC_W4_SERVICE_RESULT: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_SERVICE_QUERY_RESULT: + // store service (we expect only one) + DEBUG_LOG("Storing service\n"); + gatt_event_service_query_result_get_service(packet, &server_service); + break; + case GATT_EVENT_QUERY_COMPLETE: + att_status = gatt_event_query_complete_get_att_status(packet); + if (att_status != ATT_ERROR_SUCCESS){ + printf("SERVICE_QUERY_RESULT, ATT Error 0x%02x.\n", att_status); + gap_disconnect(connection_handle); + break; + } + // service query complete, look for characteristic + state = TC_W4_CHARACTERISTIC_RESULT; + DEBUG_LOG("Search for binary sensing characteristic.\n"); + gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &server_service, ORG_BLUETOOTH_CHARACTERISTIC_DIGITAL_OUTPUT); + break; + default: + break; + } + break; + case TC_W4_CHARACTERISTIC_RESULT: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: + DEBUG_LOG("Storing characteristic\n"); + gatt_event_characteristic_query_result_get_characteristic(packet, &server_characteristic); + break; + case GATT_EVENT_QUERY_COMPLETE: + att_status = gatt_event_query_complete_get_att_status(packet); + if (att_status != ATT_ERROR_SUCCESS){ + printf("CHARACTERISTIC_QUERY_RESULT, ATT Error 0x%02x.\n", att_status); + gap_disconnect(connection_handle); + break; + } + // register handler for notifications + listener_registered = true; + gatt_client_listen_for_characteristic_value_updates(¬ification_listener, handle_gatt_client_event, connection_handle, &server_characteristic); + // enable notifications + DEBUG_LOG("Enable notify on characteristic.\n"); + state = TC_W4_ENABLE_NOTIFICATIONS_COMPLETE; + gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handle, + &server_characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); + break; + default: + break; + } + break; + case TC_W4_ENABLE_NOTIFICATIONS_COMPLETE: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_QUERY_COMPLETE: + DEBUG_LOG("Notifications enabled, ATT status 0x%02x\n", gatt_event_query_complete_get_att_status(packet)); + if (gatt_event_query_complete_get_att_status(packet) != ATT_ERROR_SUCCESS) break; + state = TC_W4_READY; + break; + default: + break; + } + break; + case TC_W4_READY: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_NOTIFICATION: { + uint16_t value_length = gatt_event_notification_get_value_length(packet); + const uint8_t *value = gatt_event_notification_get_value(packet); + + DEBUG_LOG("Indication value len %d\n", value_length); + if (value_length == 2) { + uint state = little_endian_read_16(value, 0); + printf("recieved state %d \n", state); + // flash LED + for (int i = 1; i < 11; i++) { + gpio_put(EXT_LED_GPIO_NUM, i%2); + busy_wait_ms(100); + } + } else { + printf("Unexpected length %d\n", value_length); + } + break; + } + default: + printf("Unknown packet type 0x%02x\n", hci_event_packet_get_type(packet)); + break; + } + break; + default: + printf("Unknown state %d\n", state); + break; + } +} + +static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(size); + UNUSED(channel); + bd_addr_t local_addr; + if (packet_type != HCI_EVENT_PACKET) return; + + uint8_t event_type = hci_event_packet_get_type(packet); + switch(event_type){ + case BTSTACK_EVENT_STATE: + if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) { + gap_local_bd_addr(local_addr); + printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); + client_start(); + } else { + state = TC_OFF; + } + break; + case GAP_EVENT_ADVERTISING_REPORT: + if (state != TC_W4_SCAN_RESULT) return; + // check name in advertisement + if (!advertisement_report_contains_service(ORG_BLUETOOTH_SERVICE_BINARY_SENSOR, packet)) return; + // store address and type + gap_event_advertising_report_get_address(packet, server_addr); + server_addr_type = gap_event_advertising_report_get_address_type(packet); + // stop scanning, and connect to the device + state = TC_W4_CONNECT; + gap_stop_scan(); + printf("Connecting to device with addr %s.\n", bd_addr_to_str(server_addr)); + gap_connect(server_addr, server_addr_type); + break; + case HCI_EVENT_LE_META: + // wait for connection complete + switch (hci_event_le_meta_get_subevent_code(packet)) { + case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: + if (state != TC_W4_CONNECT) return; + connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + // initialize gatt client context with handle, and add it to the list of active clients + // query primary services + DEBUG_LOG("Search for binary sensing service.\n"); + state = TC_W4_SERVICE_RESULT; + gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, connection_handle, ORG_BLUETOOTH_SERVICE_BINARY_SENSOR); + break; + default: + break; + } + break; + case HCI_EVENT_DISCONNECTION_COMPLETE: + // unregister listener + connection_handle = HCI_CON_HANDLE_INVALID; + if (listener_registered){ + listener_registered = false; + gatt_client_stop_listening_for_characteristic_value_updates(¬ification_listener); + } + printf("Disconnected %s\n", bd_addr_to_str(server_addr)); + if (state == TC_OFF) break; + client_start(); + break; + default: + break; + } +} + +static void heartbeat_handler(struct btstack_timer_source *ts) { + // Invert the led + static bool quick_flash; + static bool led_on = true; + + led_on = !led_on; + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on); + if (listener_registered && led_on) { + quick_flash = !quick_flash; + } else if (!listener_registered) { + quick_flash = false; + } + + // Restart timer + btstack_run_loop_set_timer(ts, (led_on || quick_flash) ? LED_QUICK_FLASH_DELAY_MS : LED_SLOW_FLASH_DELAY_MS); + btstack_run_loop_add_timer(ts); +} + +int main() { + stdio_init_all(); + + // initialize external LED + gpio_init(EXT_LED_GPIO_NUM); + gpio_set_dir(EXT_LED_GPIO_NUM, GPIO_OUT); + + // initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1) + if (cyw43_arch_init()) { + printf("failed to initialise cyw43_arch\n"); + return -1; + } + + l2cap_init(); + sm_init(); + sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); + + // setup empty ATT server - only needed if LE Peripheral does ATT queries on its own, e.g. Android and iOS + att_server_init(NULL, NULL, NULL); + + gatt_client_init(); + + hci_event_callback_registration.callback = &hci_event_handler; + hci_add_event_handler(&hci_event_callback_registration); + + // set one-shot btstack timer + heartbeat.process = &heartbeat_handler; + btstack_run_loop_set_timer(&heartbeat, LED_SLOW_FLASH_DELAY_MS); + btstack_run_loop_add_timer(&heartbeat); + + // turn on! + hci_power_control(HCI_POWER_ON); + + // btstack_run_loop_execute is only required when using the 'polling' method (e.g. using pico_cyw43_arch_poll library). + // This example uses the 'threadsafe background` method, where BT work is handled in a low priority IRQ, so it + // is fine to call bt_stack_run_loop_execute() but equally you can continue executing user code. + +#if 1 // this is only necessary when using polling (which we aren't, but we're showing it is still safe to call in this case) + btstack_run_loop_execute(); +#else + // this core is free to do it's own stuff except when using 'polling' method (in which case you should use + // btstacK_run_loop_ methods to add work to the run loop. + + // this is a forever loop in place of where user code would go. + while(true) { + sleep_ms(1000); + } +#endif + return 0; +} diff --git a/pico_w/bt/standalone/doorbell/doorbell.fzz b/pico_w/bt/standalone/doorbell/doorbell.fzz new file mode 100644 index 000000000..378241ec8 Binary files /dev/null and b/pico_w/bt/standalone/doorbell/doorbell.fzz differ diff --git a/pico_w/bt/standalone/doorbell/doorbell.gatt b/pico_w/bt/standalone/doorbell/doorbell.gatt new file mode 100644 index 000000000..be0c10968 --- /dev/null +++ b/pico_w/bt/standalone/doorbell/doorbell.gatt @@ -0,0 +1,11 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "picow_doorbell" + +PRIMARY_SERVICE, GATT_SERVICE +CHARACTERISTIC, GATT_DATABASE_HASH, READ, + +// physical activity service +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_BINARY_SENSOR + +// +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_DIGITAL_OUTPUT, READ | NOTIFY | INDICATE | DYNAMIC, diff --git a/pico_w/bt/standalone/doorbell/doorbell_bb.png b/pico_w/bt/standalone/doorbell/doorbell_bb.png new file mode 100644 index 000000000..868e7e8b2 Binary files /dev/null and b/pico_w/bt/standalone/doorbell/doorbell_bb.png differ diff --git a/pico_w/bt/standalone/doorbell/server.c b/pico_w/bt/standalone/doorbell/server.c new file mode 100644 index 000000000..f06955d29 --- /dev/null +++ b/pico_w/bt/standalone/doorbell/server.c @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "btstack.h" +#include "pico/cyw43_arch.h" +#include "pico/btstack_cyw43.h" +#include "pico/stdlib.h" +#include "doorbell.h" // generated by pico_btstack_make_gatt_header in CMakeLists.txt +#include "hardware/gpio.h" + + +#define HEARTBEAT_PERIOD_MS 1000 +#define APP_AD_FLAGS 0x06 + +#define BUTTON_IRQ_DEBOUNCE_MS 500 +#define BUTTON_GPIO_NUM 15 + +// initialise time for button debouncing +static int last_time = 0; + +static uint8_t adv_data[] = { + // Flags general discoverable + 0x02, BLUETOOTH_DATA_TYPE_FLAGS, APP_AD_FLAGS, + // Name + 0x17, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', ' ', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', + 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x3b, 0x18, +}; + +static const uint8_t adv_data_len = sizeof(adv_data); + +static int le_notification_enabled; +static hci_con_handle_t con_handle; +static uint16_t current_state; +extern uint8_t const profile_data[]; + +static btstack_timer_source_t heartbeat; +static btstack_packet_callback_registration_t hci_event_callback_registration; + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(size); + UNUSED(channel); + bd_addr_t local_addr; + if (packet_type != HCI_EVENT_PACKET) return; + + uint8_t event_type = hci_event_packet_get_type(packet); + switch(event_type){ + case BTSTACK_EVENT_STATE: + if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return; + gap_local_bd_addr(local_addr); + printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); + + // setup advertisements + uint16_t adv_int_min = 800; + uint16_t adv_int_max = 800; + uint8_t adv_type = 0; + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + assert(adv_data_len <= 31); // ble limitation + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); + + + break; + case HCI_EVENT_DISCONNECTION_COMPLETE: + le_notification_enabled = 0; + break; + case ATT_EVENT_CAN_SEND_NOW: + att_server_notify(con_handle, ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_DIGITAL_OUTPUT_01_VALUE_HANDLE, (uint8_t*)¤t_state, sizeof(current_state)); + break; + default: + break; + } +} + +static uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + UNUSED(connection_handle); + + if (att_handle == ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_DIGITAL_OUTPUT_01_VALUE_HANDLE){ + return att_read_callback_handle_blob((const uint8_t *)¤t_state, sizeof(current_state), offset, buffer, buffer_size); + } + return 0; +} + +static int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + UNUSED(transaction_mode); + UNUSED(offset); + UNUSED(buffer_size); + + if (att_handle != ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_DIGITAL_OUTPUT_01_CLIENT_CONFIGURATION_HANDLE) return 0; + le_notification_enabled = little_endian_read_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION; + con_handle = connection_handle; + if (le_notification_enabled) { + att_server_request_can_send_now_event(con_handle); + } + return 0; +} + + +static void button_irq_handler(uint gpio, uint32_t events) { + // button debounce handling - ignore any additional presses for BUTTON_IRQ_DEBOUNCE_MS after first press + int time_now = to_ms_since_boot(get_absolute_time()); + if ((time_now - last_time) > BUTTON_IRQ_DEBOUNCE_MS && le_notification_enabled) { + last_time = time_now; + att_server_request_can_send_now_event(con_handle); + } +} + + +static void heartbeat_handler(struct btstack_timer_source *ts) { + static uint32_t counter = 0; + counter++; + + // Invert the led + static int led_on = true; + led_on = !led_on; + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on); + + // Restart timer + btstack_run_loop_set_timer(ts, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(ts); +} + +int main() { + stdio_init_all(); + + gpio_init(BUTTON_GPIO_NUM); + gpio_set_dir(BUTTON_GPIO_NUM, GPIO_IN); + gpio_pull_up(BUTTON_GPIO_NUM); + gpio_set_irq_enabled_with_callback(BUTTON_GPIO_NUM, GPIO_IRQ_EDGE_FALL, true, &button_irq_handler); + + // initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1) + if (cyw43_arch_init()) { + printf("failed to initialise cyw43_arch\n"); + return -1; + } + + l2cap_init(); + sm_init(); + + att_server_init(profile_data, att_read_callback, att_write_callback); + + // inform about BTstack state + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + // register for ATT event + att_server_register_packet_handler(packet_handler); + + // set one-shot btstack timer + heartbeat.process = &heartbeat_handler; + btstack_run_loop_set_timer(&heartbeat, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(&heartbeat); + + // turn on bluetooth! + hci_power_control(HCI_POWER_ON); + + // btstack_run_loop_execute is only required when using the 'polling' method (e.g. using pico_cyw43_arch_poll library). + // This example uses the 'threadsafe background` method, where BT work is handled in a low priority IRQ, so it + // is fine to call bt_stack_run_loop_execute() but equally you can continue executing user code. + +#if 0 // btstack_run_loop_execute() is not required, so lets not use it + btstack_run_loop_execute(); +#else + // this core is free to do it's own stuff except when using 'polling' method (in which case you should use + // btstacK_run_loop_ methods to add work to the run loop. + + // this is a forever loop in place of where user code would go. + while(true) { + sleep_ms(1000); + } +#endif + return 0; +} diff --git a/pico_w/bt/standalone/secure_temp_sensor/CMakeLists.txt b/pico_w/bt/standalone/secure_temp_sensor/CMakeLists.txt new file mode 100644 index 000000000..5f62c4745 --- /dev/null +++ b/pico_w/bt/standalone/secure_temp_sensor/CMakeLists.txt @@ -0,0 +1,42 @@ +# Standalone example that reads from the on board temperature sensor and sends notifications via BLE +# Flashes slowly each second to show it's running +add_executable(secure_temp_sensor + server.c + ) +target_link_libraries(secure_temp_sensor + pico_stdlib + pico_btstack_ble + pico_btstack_cyw43 + pico_cyw43_arch_none + hardware_adc + ) +target_include_directories(secure_temp_sensor PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # For our common btstack config + ) +pico_btstack_make_gatt_header(secure_temp_sensor PRIVATE "${CMAKE_CURRENT_LIST_DIR}/temp_sensor.gatt") + +pico_add_extra_outputs(secure_temp_sensor) + +# Standalone example that connects to secure_temp_sensor and reads the temperature +# Flahes once quickly each second when it's running but not connected to another device +# Flashes twice quickly each second when connected to another device and reading it's temperature +add_executable(secure_temp_reader + client.c + ) +target_link_libraries(secure_temp_reader + pico_stdlib + pico_btstack_ble + pico_btstack_cyw43 + pico_cyw43_arch_none + hardware_adc + ) +target_include_directories(secure_temp_reader PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # For our common btstack config + ) +target_compile_definitions(secure_temp_reader PRIVATE + RUNNING_AS_CLIENT=1 +) + +pico_add_extra_outputs(secure_temp_reader) \ No newline at end of file diff --git a/pico_w/bt/standalone/secure_temp_sensor/README.md b/pico_w/bt/standalone/secure_temp_sensor/README.md new file mode 100644 index 000000000..a2c13f59c --- /dev/null +++ b/pico_w/bt/standalone/secure_temp_sensor/README.md @@ -0,0 +1,10 @@ +### Secure temp sensor + +This example uses BLE to communicate temperature between a pair of pico Ws. This example is a variant of temp sensor, using LE secure to provide a secure connection. + +In server.c and client.c there is a variable security_setting which you can change to explore different security options: + +Security setting 0: Just works (pairing), no MITM protection +Security setting 1: Numeric comparison +Security setting 2: Peripheral displays passkey, client enters passkey +Security setting 3: Client displays passkey, peripheral enters passkey \ No newline at end of file diff --git a/pico_w/bt/standalone/secure_temp_sensor/btstack_config.h b/pico_w/bt/standalone/secure_temp_sensor/btstack_config.h new file mode 100644 index 000000000..b628edf30 --- /dev/null +++ b/pico_w/bt/standalone/secure_temp_sensor/btstack_config.h @@ -0,0 +1,9 @@ +#ifndef _PICO_BTSTACK_CONFIG_H +#define _PICO_BTSTACK_CONFIG_H + +#include "btstack_config_common.h" + +// security +#define ENABLE_LE_SECURE_CONNECTIONS + +#endif diff --git a/pico_w/bt/standalone/secure_temp_sensor/client.c b/pico_w/bt/standalone/secure_temp_sensor/client.c new file mode 100644 index 000000000..6f328641d --- /dev/null +++ b/pico_w/bt/standalone/secure_temp_sensor/client.c @@ -0,0 +1,464 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "btstack.h" +#include "pico/cyw43_arch.h" +#include "pico/stdlib.h" +#include "inttypes.h" +#include "string.h" + +#if 0 +#define DEBUG_LOG(...) printf(__VA_ARGS__) +#else +#define DEBUG_LOG(...) +#endif + +#define LED_QUICK_FLASH_DELAY_MS 100 +#define LED_SLOW_FLASH_DELAY_MS 1000 + +typedef enum { + TC_OFF, + TC_IDLE, + TC_W4_SCAN_RESULT, + TC_W4_CONNECT, + TC_W4_SERVICE_RESULT, + TC_W4_CHARACTERISTIC_RESULT, + TC_W4_ENABLE_NOTIFICATIONS_COMPLETE, + TC_W4_READY +} gc_state_t; + +static btstack_packet_callback_registration_t hci_event_callback_registration; +static btstack_packet_callback_registration_t sm_event_callback_registration; +static gc_state_t state = TC_OFF; +static bd_addr_t server_addr; +static bd_addr_type_t server_addr_type; +static hci_con_handle_t connection_handle; +static gatt_client_service_t server_service; +static gatt_client_characteristic_t server_characteristic; +static bool listener_registered; +static gatt_client_notification_t notification_listener; +static btstack_timer_source_t heartbeat; + +// Select a security setting to explore the BLE security +// security setting 0: Just works (pairing), no MITM protection +// security setting 1: Numeric comparison +// security setting 2: Peripheral displays passkey, client enters passkey +// security setting 3: Client displays passkey, peripheral enters passkey +static int security_setting = 1; + +static void configure_security(int security_setting) { + + sm_set_secure_connections_only_mode(true); + + switch (security_setting) { + case 0: + printf("Security setting 0 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION); + break; + case 1: + printf("Security setting 1 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_YES_NO); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION); + break; + case 2: + printf("Security setting 2 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_KEYBOARD_ONLY); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION); + break; + case 3: + printf("Security setting 3 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION); + break; + default: + break; + } +} + +static void client_start(void){ + DEBUG_LOG("Start scanning!\n"); + state = TC_W4_SCAN_RESULT; + gap_set_scan_parameters(0,0x0030, 0x0030); + gap_start_scan(); +} + +static bool advertisement_report_contains_service(uint16_t service, uint8_t *advertisement_report){ + // get advertisement from report event + const uint8_t * adv_data = gap_event_advertising_report_get_data(advertisement_report); + uint8_t adv_len = gap_event_advertising_report_get_data_length(advertisement_report); + + // iterate over advertisement data + ad_context_t context; + for (ad_iterator_init(&context, adv_len, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){ + uint8_t data_type = ad_iterator_get_data_type(&context); + uint8_t data_size = ad_iterator_get_data_len(&context); + const uint8_t * data = ad_iterator_get_data(&context); + switch (data_type){ + case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: + for (int i = 0; i < data_size; i += 2) { + uint16_t type = little_endian_read_16(data, i); + if (type == service) return true; + } + default: + break; + } + } + return false; +} + +static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(packet_type); + UNUSED(channel); + UNUSED(size); + + uint8_t att_status; + switch(state){ + case TC_W4_SERVICE_RESULT: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_SERVICE_QUERY_RESULT: + // store service (we expect only one) + DEBUG_LOG("Storing service\n"); + gatt_event_service_query_result_get_service(packet, &server_service); + break; + case GATT_EVENT_QUERY_COMPLETE: + att_status = gatt_event_query_complete_get_att_status(packet); + if (att_status != ATT_ERROR_SUCCESS){ + printf("SERVICE_QUERY_RESULT, ATT Error 0x%02x.\n", att_status); + gap_disconnect(connection_handle); + break; + } + // service query complete, look for characteristic + state = TC_W4_CHARACTERISTIC_RESULT; + DEBUG_LOG("Search for env sensing characteristic.\n"); + gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &server_service, ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE); + break; + default: + break; + } + break; + case TC_W4_CHARACTERISTIC_RESULT: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: + DEBUG_LOG("Storing characteristic\n"); + gatt_event_characteristic_query_result_get_characteristic(packet, &server_characteristic); + break; + case GATT_EVENT_QUERY_COMPLETE: + att_status = gatt_event_query_complete_get_att_status(packet); + if (att_status != ATT_ERROR_SUCCESS){ + printf("CHARACTERISTIC_QUERY_RESULT, ATT Error 0x%02x.\n", att_status); + gap_disconnect(connection_handle); + break; + } + // register handler for notifications + listener_registered = true; + gatt_client_listen_for_characteristic_value_updates(¬ification_listener, handle_gatt_client_event, connection_handle, &server_characteristic); + // enable notifications + DEBUG_LOG("Enable notify on characteristic.\n"); + state = TC_W4_ENABLE_NOTIFICATIONS_COMPLETE; + gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handle, + &server_characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); + break; + default: + break; + } + break; + case TC_W4_ENABLE_NOTIFICATIONS_COMPLETE: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_QUERY_COMPLETE: + DEBUG_LOG("Notifications enabled, ATT status 0x%02x\n", gatt_event_query_complete_get_att_status(packet)); + if (gatt_event_query_complete_get_att_status(packet) != ATT_ERROR_SUCCESS) break; + state = TC_W4_READY; + break; + default: + break; + } + break; + case TC_W4_READY: + switch(hci_event_packet_get_type(packet)) { + case GATT_EVENT_NOTIFICATION: { + uint16_t value_length = gatt_event_notification_get_value_length(packet); + const uint8_t *value = gatt_event_notification_get_value(packet); + DEBUG_LOG("Indication value len %d\n", value_length); + if (value_length == 2) { + float temp = little_endian_read_16(value, 0); + printf("read temp %.2f degc\n", temp / 100); + } else { + printf("Unexpected length %d\n", value_length); + } + break; + } + default: + printf("Unknown packet type 0x%02x\n", hci_event_packet_get_type(packet)); + break; + } + break; + default: + printf("error\n"); + break; + } +} + +static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(size); + UNUSED(channel); + bd_addr_t local_addr; + if (packet_type != HCI_EVENT_PACKET) return; + hci_con_handle_t con_handle; + uint8_t status; + + uint8_t event_type = hci_event_packet_get_type(packet); + switch(event_type){ + case BTSTACK_EVENT_STATE: + if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) { + gap_local_bd_addr(local_addr); + printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); + client_start(); + } else { + state = TC_OFF; + } + break; + case GAP_EVENT_ADVERTISING_REPORT: + if (state != TC_W4_SCAN_RESULT) return; + // check name in advertisement + if (!advertisement_report_contains_service(ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING, packet)) return; + // store address and type + gap_event_advertising_report_get_address(packet, server_addr); + server_addr_type = gap_event_advertising_report_get_address_type(packet); + // stop scanning, and connect to the device + state = TC_W4_CONNECT; + gap_stop_scan(); + printf("Connecting to device with addr %s.\n", bd_addr_to_str(server_addr)); + gap_connect(server_addr, server_addr_type); + break; + case HCI_EVENT_LE_META: + // wait for connection complete + switch (hci_event_le_meta_get_subevent_code(packet)) { + case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: + if (state != TC_W4_CONNECT) return; + connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + // initialize gatt client context with handle, and add it to the list of active clients + // query primary services + DEBUG_LOG("Search for env sensing service.\n"); + state = TC_W4_SERVICE_RESULT; + gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, connection_handle, ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING); + break; + default: + break; + } + break; + case HCI_EVENT_META_GAP: + // wait for connection complete + if (hci_event_gap_meta_get_subevent_code(packet) != GAP_SUBEVENT_LE_CONNECTION_COMPLETE) break; + con_handle = gap_subevent_le_connection_complete_get_connection_handle(packet); + printf("Connection complete\n"); + sm_request_pairing(con_handle); + break; + case GATT_EVENT_QUERY_COMPLETE: + status = gatt_event_query_complete_get_att_status(packet); + switch (status){ + case ATT_ERROR_INSUFFICIENT_ENCRYPTION: + printf("GATT Query result: Insufficient Encryption\n"); + break; + case ATT_ERROR_INSUFFICIENT_AUTHENTICATION: + printf("GATT Query result: Insufficient Authentication\n"); + break; + case ATT_ERROR_BONDING_INFORMATION_MISSING: + printf("GATT Query result: Bonding Information Missing\n"); + break; + case ATT_ERROR_SUCCESS: + printf("GATT Query result: OK\n"); + break; + default: + printf("GATT Query result: 0x%02x\n", gatt_event_query_complete_get_att_status(packet)); + break; + } + break; + case HCI_EVENT_DISCONNECTION_COMPLETE: + // unregister listener + connection_handle = HCI_CON_HANDLE_INVALID; + if (listener_registered){ + listener_registered = false; + gatt_client_stop_listening_for_characteristic_value_updates(¬ification_listener); + } + printf("Disconnected %s\n", bd_addr_to_str(server_addr)); + if (state == TC_OFF) break; + client_start(); + break; + default: + break; + } +} + +static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + if (packet_type != HCI_EVENT_PACKET) return; + + bd_addr_t addr; + bd_addr_type_t addr_type; + + switch (hci_event_packet_get_type(packet)) { + case SM_EVENT_JUST_WORKS_REQUEST: + printf("Just works requested\n"); + sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); + break; + case SM_EVENT_NUMERIC_COMPARISON_REQUEST: + printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); + printf("Do the numbers match? (y/n)\n"); + + char result[2]; + scanf(" %c", &result); + if (result[0] == 'y' || result[0] == 'Y') { + sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); + } else { + sm_bonding_decline(sm_event_passkey_display_number_get_handle(packet)); + } + break; + case SM_EVENT_PASSKEY_DISPLAY_NUMBER: + printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); + break; + case SM_EVENT_PASSKEY_INPUT_NUMBER: + char passkey[7]; + + printf("Passkey Input requested \n"); + scanf("%6[^\n]", passkey); + int to_send = atoi(passkey); // convert passkey to int + + printf("Sending passkey %"PRIu32"\n", to_send); + sm_passkey_input(sm_event_passkey_input_number_get_handle(packet), to_send); + break; + case SM_EVENT_PAIRING_STARTED: + printf("Pairing started\n"); + break; + case SM_EVENT_PAIRING_COMPLETE: + switch (sm_event_pairing_complete_get_status(packet)){ + case ERROR_CODE_SUCCESS: + printf("Pairing complete, success\n"); + break; + case ERROR_CODE_CONNECTION_TIMEOUT: + printf("Pairing failed, timeout\n"); + break; + case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: + printf("Pairing failed, disconnected\n"); + break; + case ERROR_CODE_AUTHENTICATION_FAILURE: + printf("Pairing failed, authentication failure with reason = %u\n", sm_event_pairing_complete_get_reason(packet)); + break; + default: + break; + } + break; + case SM_EVENT_REENCRYPTION_STARTED: + sm_event_reencryption_complete_get_address(packet, addr); + printf("Bonding information exists for addr type %u, identity addr %s -> start re-encryption\n", + sm_event_reencryption_started_get_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_REENCRYPTION_COMPLETE: + switch (sm_event_reencryption_complete_get_status(packet)){ + case ERROR_CODE_SUCCESS: + printf("Re-encryption complete, success\n"); + break; + case ERROR_CODE_CONNECTION_TIMEOUT: + printf("Re-encryption failed, timeout\n"); + break; + case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: + printf("Re-encryption failed, disconnected\n"); + break; + case ERROR_CODE_PIN_OR_KEY_MISSING: + printf("Re-encryption failed, bonding information missing\n\n"); + printf("Assuming remote lost bonding information\n"); + printf("Deleting local bonding information and start new pairing...\n"); + sm_event_reencryption_complete_get_address(packet, addr); + addr_type = sm_event_reencryption_started_get_addr_type(packet); + gap_delete_bonding(addr_type, addr); + sm_request_pairing(sm_event_reencryption_complete_get_handle(packet)); + break; + default: + break; + } + break; + default: + break; + } +} + +static void heartbeat_handler(struct btstack_timer_source *ts) { + // Invert the led + static bool quick_flash; + static bool led_on = true; + + led_on = !led_on; + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on); + if (listener_registered && led_on) { + quick_flash = !quick_flash; + } else if (!listener_registered) { + quick_flash = false; + } + + // Restart timer + btstack_run_loop_set_timer(ts, (led_on || quick_flash) ? LED_QUICK_FLASH_DELAY_MS : LED_SLOW_FLASH_DELAY_MS); + btstack_run_loop_add_timer(ts); +} + +int main() { + stdio_init_all(); + + // initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1) + if (cyw43_arch_init()) { + printf("failed to initialise cyw43_arch\n"); + return -1; + } + + l2cap_init(); + sm_init(); + sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); + + // setup empty ATT server - only needed if LE Peripheral does ATT queries on its own, e.g. Android and iOS + att_server_init(NULL, NULL, NULL); + + gatt_client_init(); + + hci_event_callback_registration.callback = &hci_packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + sm_event_callback_registration.callback = &sm_packet_handler; + sm_add_event_handler(&sm_event_callback_registration); + + // apply security configuration settings + configure_security(security_setting); + + // set one-shot btstack timer + heartbeat.process = &heartbeat_handler; + btstack_run_loop_set_timer(&heartbeat, LED_SLOW_FLASH_DELAY_MS); + btstack_run_loop_add_timer(&heartbeat); + + // turn on! + hci_power_control(HCI_POWER_ON); + + // btstack_run_loop_execute is only required when using the 'polling' method (e.g. using pico_cyw43_arch_poll library). + // This example uses the 'threadsafe background` method, where BT work is handled in a low priority IRQ, so it + // is fine to call bt_stack_run_loop_execute() but equally you can continue executing user code. + +#if 1 // this is only necessary when using polling (which we aren't, but we're showing it is still safe to call in this case) + btstack_run_loop_execute(); +#else + // this core is free to do it's own stuff except when using 'polling' method (in which case you should use + // btstacK_run_loop_ methods to add work to the run loop. + + // this is a forever loop in place of where user code would go. + while(true) { + sleep_ms(1000); + } +#endif + return 0; +} \ No newline at end of file diff --git a/pico_w/bt/standalone/secure_temp_sensor/server.c b/pico_w/bt/standalone/secure_temp_sensor/server.c new file mode 100644 index 000000000..c85d1269f --- /dev/null +++ b/pico_w/bt/standalone/secure_temp_sensor/server.c @@ -0,0 +1,372 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "btstack.h" +#include "pico/cyw43_arch.h" +#include "pico/btstack_cyw43.h" +#include "hardware/adc.h" +#include "pico/stdlib.h" +#include "temp_sensor.h" +#include +#include + +#define HEARTBEAT_PERIOD_MS 1000 +#define ADC_CHANNEL_TEMPSENSOR 4 + +#define APP_AD_FLAGS 0x06 +static uint8_t adv_data[] = { + // Flags general discoverable + 0x02, BLUETOOTH_DATA_TYPE_FLAGS, APP_AD_FLAGS, + // Name + 0x17, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', ' ', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', + 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x1a, 0x18, +}; +static const uint8_t adv_data_len = sizeof(adv_data); + +int le_notification_enabled; +static hci_con_handle_t con_handle; +static uint16_t current_temp; + +extern uint8_t const profile_data[]; +static void poll_temp(void); + +static btstack_timer_source_t heartbeat; +static btstack_packet_callback_registration_t hci_event_callback_registration; +static btstack_packet_callback_registration_t sm_event_callback_registration; + +// Select a security setting to explore the BLE security +// security setting 0: Just works (pairing), no MITM protection +// security setting 1: Numeric comparison +// security setting 2: Peripheral displays passkey, client enters passkey +// security setting 3: Client displays passkey, peripheral enters passkey +static int security_setting = 1; + +static void configure_security(int security_setting) { + + sm_set_secure_connections_only_mode(true); + + switch (security_setting) { + case 0: + printf("Security setting 0 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION); + break; + case 1: + printf("Security setting 1 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_YES_NO); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION); + break; + case 2: + printf("Security setting 2 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION); + break; + case 3: + printf("Security setting 3 selected. \n"); + + sm_set_io_capabilities(IO_CAPABILITY_KEYBOARD_ONLY); + sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION); + break; + default: + break; + } +} + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(size); + UNUSED(channel); + bd_addr_t local_addr; + if (packet_type != HCI_EVENT_PACKET) return; + + uint8_t event_type = hci_event_packet_get_type(packet); + switch(event_type){ + case BTSTACK_EVENT_STATE: + if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return; + gap_local_bd_addr(local_addr); + printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); + + // setup advertisements + uint16_t adv_int_min = 800; + uint16_t adv_int_max = 800; + uint8_t adv_type = 0; + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + assert(adv_data_len <= 31); // ble limitation + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); + + poll_temp(); + + break; + case HCI_EVENT_DISCONNECTION_COMPLETE: + le_notification_enabled = 0; + break; + case ATT_EVENT_CAN_SEND_NOW: + att_server_notify(con_handle, ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE_01_VALUE_HANDLE, (uint8_t*)¤t_temp, sizeof(current_temp)); + break; + default: + break; + } +} + +static void sm_packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + if (packet_type != HCI_EVENT_PACKET) return; + + hci_con_handle_t con_handle; + bd_addr_t addr; + bd_addr_type_t addr_type; + uint8_t status; + + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_META_GAP: + switch (hci_event_gap_meta_get_subevent_code(packet)) { + case GAP_SUBEVENT_LE_CONNECTION_COMPLETE: + printf("Connection complete\n"); + con_handle = gap_subevent_le_connection_complete_get_connection_handle(packet); + sm_request_pairing(con_handle); + break; + default: + break; + } + break; + case SM_EVENT_JUST_WORKS_REQUEST: + printf("Just Works requested\n"); + sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); + break; + case SM_EVENT_NUMERIC_COMPARISON_REQUEST: + printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); + sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); + break; + case SM_EVENT_PASSKEY_DISPLAY_NUMBER: + printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); + break; + case SM_EVENT_PASSKEY_INPUT_NUMBER: + char passkey[7]; + + printf("Passkey Input requested \n"); + scanf("%6[^\n]", passkey); + int to_send = atoi(passkey); // convert passkey to int + + printf("Sending passkey %"PRIu32"\n", to_send); + sm_passkey_input(sm_event_passkey_input_number_get_handle(packet), to_send); + break; + case SM_EVENT_IDENTITY_CREATED: + sm_event_identity_created_get_identity_address(packet, addr); + printf("Identity created: type %u address %s\n", sm_event_identity_created_get_identity_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_IDENTITY_RESOLVING_SUCCEEDED: + sm_event_identity_resolving_succeeded_get_identity_address(packet, addr); + printf("Identity resolved: type %u address %s\n", sm_event_identity_resolving_succeeded_get_identity_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_IDENTITY_RESOLVING_FAILED: + sm_event_identity_created_get_address(packet, addr); + printf("Identity resolving failed\n"); + break; + case SM_EVENT_PAIRING_STARTED: + printf("Pairing started\n"); + break; + case SM_EVENT_PAIRING_COMPLETE: + switch (sm_event_pairing_complete_get_status(packet)){ + case ERROR_CODE_SUCCESS: + printf("Pairing complete, success\n"); + break; + case ERROR_CODE_CONNECTION_TIMEOUT: + printf("Pairing failed, timeout\n"); + break; + case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: + printf("Pairing failed, disconnected\n"); + break; + case ERROR_CODE_AUTHENTICATION_FAILURE: + printf("Pairing failed, authentication failure with reason = %u\n", sm_event_pairing_complete_get_reason(packet)); + break; + default: + break; + } + break; + case SM_EVENT_REENCRYPTION_STARTED: + sm_event_reencryption_complete_get_address(packet, addr); + printf("Bonding information exists for addr type %u, identity addr %s -> re-encryption started\n", + sm_event_reencryption_started_get_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_REENCRYPTION_COMPLETE: + switch (sm_event_reencryption_complete_get_status(packet)){ + case ERROR_CODE_SUCCESS: + printf("Re-encryption complete, success\n"); + break; + case ERROR_CODE_CONNECTION_TIMEOUT: + printf("Re-encryption failed, timeout\n"); + break; + case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: + printf("Re-encryption failed, disconnected\n"); + break; + case ERROR_CODE_PIN_OR_KEY_MISSING: + printf("Re-encryption failed, bonding information missing\n\n"); + printf("Assuming remote lost bonding information\n"); + printf("Deleting local bonding information to allow for new pairing...\n"); + sm_event_reencryption_complete_get_address(packet, addr); + addr_type = sm_event_reencryption_started_get_addr_type(packet); + gap_delete_bonding(addr_type, addr); + break; + default: + break; + } + break; + case GATT_EVENT_QUERY_COMPLETE: + status = gatt_event_query_complete_get_att_status(packet); + switch (status){ + case ATT_ERROR_INSUFFICIENT_ENCRYPTION: + printf("GATT Query failed, Insufficient Encryption\n"); + break; + case ATT_ERROR_INSUFFICIENT_AUTHENTICATION: + printf("GATT Query failed, Insufficient Authentication\n"); + break; + case ATT_ERROR_BONDING_INFORMATION_MISSING: + printf("GATT Query failed, Bonding Information Missing\n"); + break; + case ATT_ERROR_SUCCESS: + printf("GATT Query successful\n"); + break; + default: + printf("GATT Query failed, status 0x%02x\n", gatt_event_query_complete_get_att_status(packet)); + break; + } + break; + default: + break; + } +} + + +static uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + UNUSED(connection_handle); + + if (att_handle == ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE_01_VALUE_HANDLE){ + return att_read_callback_handle_blob((const uint8_t *)¤t_temp, sizeof(current_temp), offset, buffer, buffer_size); + } + return 0; +} + +static int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + UNUSED(transaction_mode); + UNUSED(offset); + UNUSED(buffer_size); + + if (att_handle != ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE_01_CLIENT_CONFIGURATION_HANDLE) return 0; + le_notification_enabled = little_endian_read_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION; + con_handle = connection_handle; + if (le_notification_enabled) { + att_server_request_can_send_now_event(con_handle); + } + return 0; +} + +static void poll_temp(void) { + adc_select_input(ADC_CHANNEL_TEMPSENSOR); + uint32_t raw32 = adc_read(); + const uint32_t bits = 12; + + // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) + uint16_t raw16 = raw32 << (16 - bits) | raw32 >> (2 * bits - 16); + + // ref https://github.com/raspberrypi/pico-micropython-examples/blob/master/adc/temperature.py + const float conversion_factor = 3.3 / (65535); + float reading = raw16 * conversion_factor; + + // The temperature sensor measures the Vbe voltage of a biased bipolar diode, connected to the fifth ADC channel + // Typically, Vbe = 0.706V at 27 degrees C, with a slope of -1.721mV (0.001721) per degree. + float deg_c = 27 - (reading - 0.706) / 0.001721; + current_temp = deg_c * 100; + printf("Write temp %.2f degc\n", deg_c); + } + +static void heartbeat_handler(struct btstack_timer_source *ts) { + static uint32_t counter = 0; + counter++; + + // Update the temp every 10s + if (counter % 10 == 0) { + poll_temp(); + if (le_notification_enabled) { + att_server_request_can_send_now_event(con_handle); + } + } + + // Invert the led + static int led_on = true; + led_on = !led_on; + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on); + + // Restart timer + btstack_run_loop_set_timer(ts, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(ts); +} + +int main() { + stdio_init_all(); + + // initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1) + if (cyw43_arch_init()) { + printf("failed to initialise cyw43_arch\n"); + return -1; + } + + // Initialise adc for the temp sensor + adc_init(); + adc_select_input(ADC_CHANNEL_TEMPSENSOR); + adc_set_temp_sensor_enabled(true); + + l2cap_init(); + sm_init(); + + att_server_init(profile_data, att_read_callback, att_write_callback); + + // inform about BTstack state + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + // sm packet handler + sm_event_callback_registration.callback = &sm_packet_handler; + sm_add_event_handler(&sm_event_callback_registration); + + // apply security configuration settings + configure_security(security_setting); + + // register for ATT event + att_server_register_packet_handler(packet_handler); + + // set one-shot btstack timer + heartbeat.process = &heartbeat_handler; + btstack_run_loop_set_timer(&heartbeat, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(&heartbeat); + + // turn on bluetooth! + hci_power_control(HCI_POWER_ON); + + // btstack_run_loop_execute is only required when using the 'polling' method (e.g. using pico_cyw43_arch_poll library). + // This example uses the 'threadsafe background` method, where BT work is handled in a low priority IRQ, so it + // is fine to call bt_stack_run_loop_execute() but equally you can continue executing user code. + +#if 0 // btstack_run_loop_execute() is not required, so lets not use it + btstack_run_loop_execute(); +#else + // this core is free to do it's own stuff except when using 'polling' method (in which case you should use + // btstacK_run_loop_ methods to add work to the run loop. + + // this is a forever loop in place of where user code would go. + while(true) { + sleep_ms(1000); + } +#endif + return 0; +} diff --git a/pico_w/bt/standalone/secure_temp_sensor/temp_sensor.gatt b/pico_w/bt/standalone/secure_temp_sensor/temp_sensor.gatt new file mode 100644 index 000000000..e0219483e --- /dev/null +++ b/pico_w/bt/standalone/secure_temp_sensor/temp_sensor.gatt @@ -0,0 +1,8 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "secure_picow_temp" + +PRIMARY_SERVICE, GATT_SERVICE +CHARACTERISTIC, GATT_DATABASE_HASH, READ, + +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE, READ | NOTIFY | INDICATE | DYNAMIC | ENCRYPTION_KEY_SIZE_16, diff --git a/pico_w/bt/standalone/temp_sensor/CMakeLists.txt b/pico_w/bt/standalone/temp_sensor/CMakeLists.txt new file mode 100644 index 000000000..ef03fe482 --- /dev/null +++ b/pico_w/bt/standalone/temp_sensor/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(client) +add_subdirectory(server) diff --git a/pico_w/bt/standalone/temp_sensor/README.md b/pico_w/bt/standalone/temp_sensor/README.md new file mode 100644 index 000000000..86a68cf2d --- /dev/null +++ b/pico_w/bt/standalone/temp_sensor/README.md @@ -0,0 +1,3 @@ +### Secure temp sensor + +This example uses BLE to communicate temperature between a pair of pico Ws. It reads temperature by measuring some onboard voltage and applying a conversion factor. \ No newline at end of file diff --git a/pico_w/bt/standalone/client/CMakeLists.txt b/pico_w/bt/standalone/temp_sensor/client/CMakeLists.txt similarity index 91% rename from pico_w/bt/standalone/client/CMakeLists.txt rename to pico_w/bt/standalone/temp_sensor/client/CMakeLists.txt index 808d2fdf9..67ab5d041 100644 --- a/pico_w/bt/standalone/client/CMakeLists.txt +++ b/pico_w/bt/standalone/temp_sensor/client/CMakeLists.txt @@ -13,7 +13,7 @@ target_link_libraries(picow_ble_temp_reader ) target_include_directories(picow_ble_temp_reader PRIVATE ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # For our common btstack config + ${CMAKE_CURRENT_LIST_DIR}/../.. # For our common btstack config ) target_compile_definitions(picow_ble_temp_reader PRIVATE RUNNING_AS_CLIENT=1 diff --git a/pico_w/bt/standalone/temp_sensor/client/btstack_config.h b/pico_w/bt/standalone/temp_sensor/client/btstack_config.h new file mode 100644 index 000000000..ce1919916 --- /dev/null +++ b/pico_w/bt/standalone/temp_sensor/client/btstack_config.h @@ -0,0 +1,6 @@ +#ifndef _PICO_BTSTACK_CONFIG_H +#define _PICO_BTSTACK_CONFIG_H + +#include "btstack_config_common.h" + +#endif diff --git a/pico_w/bt/standalone/client/client.c b/pico_w/bt/standalone/temp_sensor/client/client.c similarity index 100% rename from pico_w/bt/standalone/client/client.c rename to pico_w/bt/standalone/temp_sensor/client/client.c diff --git a/pico_w/bt/standalone/server/CMakeLists.txt b/pico_w/bt/standalone/temp_sensor/server/CMakeLists.txt similarity index 90% rename from pico_w/bt/standalone/server/CMakeLists.txt rename to pico_w/bt/standalone/temp_sensor/server/CMakeLists.txt index b18ac013a..30cb69c26 100644 --- a/pico_w/bt/standalone/server/CMakeLists.txt +++ b/pico_w/bt/standalone/temp_sensor/server/CMakeLists.txt @@ -12,7 +12,7 @@ target_link_libraries(picow_ble_temp_sensor ) target_include_directories(picow_ble_temp_sensor PRIVATE ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # For our common btstack config + ${CMAKE_CURRENT_LIST_DIR}/../.. # For our common btstack config ) pico_btstack_make_gatt_header(picow_ble_temp_sensor PRIVATE "${CMAKE_CURRENT_LIST_DIR}/temp_sensor.gatt") diff --git a/pico_w/bt/standalone/temp_sensor/server/btstack_config.h b/pico_w/bt/standalone/temp_sensor/server/btstack_config.h new file mode 100644 index 000000000..ce1919916 --- /dev/null +++ b/pico_w/bt/standalone/temp_sensor/server/btstack_config.h @@ -0,0 +1,6 @@ +#ifndef _PICO_BTSTACK_CONFIG_H +#define _PICO_BTSTACK_CONFIG_H + +#include "btstack_config_common.h" + +#endif diff --git a/pico_w/bt/standalone/server/server.c b/pico_w/bt/standalone/temp_sensor/server/server.c similarity index 100% rename from pico_w/bt/standalone/server/server.c rename to pico_w/bt/standalone/temp_sensor/server/server.c diff --git a/pico_w/bt/standalone/server/temp_sensor.gatt b/pico_w/bt/standalone/temp_sensor/server/temp_sensor.gatt similarity index 100% rename from pico_w/bt/standalone/server/temp_sensor.gatt rename to pico_w/bt/standalone/temp_sensor/server/temp_sensor.gatt diff --git a/pico_w/bt/standalone/wifi_provisioner/CMakeLists.txt b/pico_w/bt/standalone/wifi_provisioner/CMakeLists.txt new file mode 100644 index 000000000..9a08e7106 --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/CMakeLists.txt @@ -0,0 +1,32 @@ +# Add provisioning libary +add_library(wifi_prov_lib INTERFACE) +target_sources(wifi_prov_lib INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/wifi_prov_lib.c + ) +target_include_directories(wifi_prov_lib INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ) +target_link_libraries(wifi_prov_lib INTERFACE + pico_stdlib + pico_btstack_ble + pico_btstack_cyw43 + pico_cyw43_arch_lwip_threadsafe_background + hardware_flash + ) +pico_btstack_make_gatt_header(wifi_prov_lib INTERFACE "${CMAKE_CURRENT_LIST_DIR}/provisioning.gatt") + +# Standalone example which uses the provisioning libary to allow credentials to be set over BLE +add_executable(wifi_provisioning_new + example.c + ) +target_link_libraries(wifi_provisioning_new + pico_stdlib + wifi_prov_lib + ) +target_include_directories(wifi_provisioning_new PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # For our common btstack config + ) + +pico_add_extra_outputs(wifi_provisioning_new) + diff --git a/pico_w/bt/standalone/wifi_provisioner/README.md b/pico_w/bt/standalone/wifi_provisioner/README.md new file mode 100644 index 000000000..c65abed94 --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/README.md @@ -0,0 +1,3 @@ +### BLE wifi provisioning + +This example demonstrates provisioning wifi credentials using bluetooth low energy. The pico saves the most recent set of succesful credentials in flash for future use. Upon powering, the pico attemps to connect using the saved credentials. If this fails, the pico sets up a GATT server which you can connect to using a mobile BLE scanner app or the attached python script. The GATT server has 2 custom characteritics - one for ssid and one for password. To write to these characteristics you can run 'python3 set_credentials.py ssid password address'. diff --git a/pico_w/bt/standalone/wifi_provisioner/btstack_config.h b/pico_w/bt/standalone/wifi_provisioner/btstack_config.h new file mode 100644 index 000000000..ce1919916 --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/btstack_config.h @@ -0,0 +1,6 @@ +#ifndef _PICO_BTSTACK_CONFIG_H +#define _PICO_BTSTACK_CONFIG_H + +#include "btstack_config_common.h" + +#endif diff --git a/pico_w/bt/standalone/wifi_provisioner/example.c b/pico_w/bt/standalone/wifi_provisioner/example.c new file mode 100644 index 000000000..90c36a23e --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/example.c @@ -0,0 +1,10 @@ +#include +#include + + +int main(void) { + // if unable to connect with saved ssid and password, waits 120 seconds + // for new credentials to be provisioned over BLE + start_ble_wifi_provisioning(120000); + printf("finished provisioning\n"); +} \ No newline at end of file diff --git a/pico_w/bt/standalone/lwipopts.h b/pico_w/bt/standalone/wifi_provisioner/lwipopts.h similarity index 99% rename from pico_w/bt/standalone/lwipopts.h rename to pico_w/bt/standalone/wifi_provisioner/lwipopts.h index 32bb24c51..7ae8d527c 100644 --- a/pico_w/bt/standalone/lwipopts.h +++ b/pico_w/bt/standalone/wifi_provisioner/lwipopts.h @@ -73,4 +73,4 @@ #define SLIP_DEBUG LWIP_DBG_OFF #define DHCP_DEBUG LWIP_DBG_OFF -#endif /* __LWIPOPTS_H__ */ +#endif /* __LWIPOPTS_H__ */ \ No newline at end of file diff --git a/pico_w/bt/standalone/wifi_provisioner/provisioning.gatt b/pico_w/bt/standalone/wifi_provisioner/provisioning.gatt new file mode 100644 index 000000000..d298585ad --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/provisioning.gatt @@ -0,0 +1,16 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "pico_wifi_provisioning" + +PRIMARY_SERVICE, GATT_SERVICE +CHARACTERISTIC, GATT_DATABASE_HASH, READ, + +PRIMARY_SERVICE, 6bfacb8f-6b40-49f8-906e-53bd113c5cfb + +// SSID characteristic +CHARACTERISTIC, b1829813-e8ec-4621-b9b5-6c1be43fe223, READ | WRITE | NOTIFY | DYNAMIC, +CHARACTERISTIC_USER_DESCRIPTION, READ, + + +// Password characteristic +CHARACTERISTIC, 410f5077-9e81-4f3b-b888-bf435174fa58, READ | WRITE | NOTIFY | DYNAMIC, +CHARACTERISTIC_USER_DESCRIPTION, READ, \ No newline at end of file diff --git a/pico_w/bt/standalone/wifi_provisioner/set_credentials.py b/pico_w/bt/standalone/wifi_provisioner/set_credentials.py new file mode 100644 index 000000000..d42a8610c --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/set_credentials.py @@ -0,0 +1,40 @@ +import asyncio +import sys +import argparse + +from bleak import BleakClient + +SSID_CHARACTERISTIC = "b1829813-e8ec-4621-b9b5-6c1be43fe223" +PASSWORD_CHARACTERISTIC = "410f5077-9e81-4f3b-b888-bf435174fa58" + +#Add arguments from terminal with python3 set_credentials.py ssid password address +parser=argparse.ArgumentParser(description="ssid, password and address parser") +parser.add_argument("ssid") +parser.add_argument("password") +parser.add_argument("address") +args = parser.parse_args() + +ssid = args.ssid +password = args.password +address = args.address + +print("submitted ssid: ", ssid) +print("submitted password: ", password) +print("submitted address: ", address) + +async def main(ssid, password): + async with BleakClient(address) as client: + print(f"Connected: {client.is_connected}") + + await client.pair() + + print("Writing SSID...") + await client.write_gatt_char(SSID_CHARACTERISTIC, ssid.encode("utf-8"), response=True) + await asyncio.sleep(1.0) + + print("Writing password...") + await client.write_gatt_char(PASSWORD_CHARACTERISTIC, password.encode("utf-8"), response=True) + await asyncio.sleep(1.0) + +if __name__ == "__main__": + asyncio.run(main(ssid, password)) \ No newline at end of file diff --git a/pico_w/bt/standalone/wifi_provisioner/wifi_prov_lib.c b/pico_w/bt/standalone/wifi_provisioner/wifi_prov_lib.c new file mode 100644 index 000000000..b3d28eb4e --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/wifi_prov_lib.c @@ -0,0 +1,455 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "btstack.h" +#include "pico/cyw43_arch.h" +#include "pico/btstack_cyw43.h" +#include "pico/stdlib.h" +#include "provisioning.h" +#include "wifi_prov_lib.h" +#include "hardware/gpio.h" +#include "pico/flash.h" +#include "hardware/flash.h" + +#include + +#define HEARTBEAT_PERIOD_MS 1000 +#define APP_AD_FLAGS 0x06 + +int le_notification_enabled; +hci_con_handle_t con_handle; + +// max lengths of credentials + 1 to ensure null termination +char ssid[33] = ""; +char password[64] = ""; + +bool connection_status = false; + +static btstack_timer_source_t heartbeat; + +static btstack_packet_callback_registration_t hci_event_callback_registration; +static btstack_packet_callback_registration_t sm_event_callback_registration; + +static uint8_t adv_data[] = { + // Flags general discoverable + 0x02, BLUETOOTH_DATA_TYPE_FLAGS, APP_AD_FLAGS, + // Name + 0x17, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', ' ', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', + 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x10, 0xFF, +}; + +static const uint8_t adv_data_len = sizeof(adv_data); + +// Define flash offset towards end of flash +#ifndef PICO_FLASH_BANK_TOTAL_SIZE +#define PICO_FLASH_BANK_TOTAL_SIZE (FLASH_SECTOR_SIZE * 2u) +#endif + +#ifndef PICO_FLASH_BANK_STORAGE_OFFSET +#if PICO_RP2350 && PICO_RP2350_A2_SUPPORTED +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE - PICO_FLASH_BANK_TOTAL_SIZE) +#else +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE - PICO_FLASH_BANK_TOTAL_SIZE) +#endif +#endif + +const uint8_t *flash_target_contents = (const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET); + +// This function will be called when it's safe to call flash_range_erase +static void call_flash_range_erase(void *param) { + uint32_t offset = (uint32_t)param; + flash_range_erase(offset, FLASH_SECTOR_SIZE); +} + +// This function will be called when it's safe to call flash_range_program +static void call_flash_range_program(void *param) { + uint32_t offset = ((uintptr_t*)param)[0]; + const uint8_t *data = (const uint8_t *)((uintptr_t*)param)[1]; + flash_range_program(offset, data, FLASH_PAGE_SIZE); +} + +// Security Manager Packet Handler +void sm_packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + if (packet_type != HCI_EVENT_PACKET) return; + + hci_con_handle_t con_handle; + bd_addr_t addr; + bd_addr_type_t addr_type; + uint8_t status; + + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_META_GAP: + switch (hci_event_gap_meta_get_subevent_code(packet)) { + case GAP_SUBEVENT_LE_CONNECTION_COMPLETE: + printf("Connection complete\n"); + con_handle = gap_subevent_le_connection_complete_get_connection_handle(packet); + UNUSED(con_handle); + sm_request_pairing(con_handle); + break; + default: + break; + } + break; + case SM_EVENT_JUST_WORKS_REQUEST: + printf("Just Works requested\n"); + sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); + break; + case SM_EVENT_IDENTITY_CREATED: + sm_event_identity_created_get_identity_address(packet, addr); + printf("Identity created: type %u address %s\n", sm_event_identity_created_get_identity_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_IDENTITY_RESOLVING_SUCCEEDED: + sm_event_identity_resolving_succeeded_get_identity_address(packet, addr); + printf("Identity resolved: type %u address %s\n", sm_event_identity_resolving_succeeded_get_identity_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_IDENTITY_RESOLVING_FAILED: + sm_event_identity_created_get_address(packet, addr); + printf("Identity resolving failed\n"); + break; + case SM_EVENT_PAIRING_STARTED: + printf("Pairing started\n"); + break; + case SM_EVENT_PAIRING_COMPLETE: + switch (sm_event_pairing_complete_get_status(packet)){ + case ERROR_CODE_SUCCESS: + printf("Pairing complete, success\n"); + break; + case ERROR_CODE_CONNECTION_TIMEOUT: + printf("Pairing failed, timeout\n"); + break; + case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: + printf("Pairing failed, disconnected\n"); + break; + case ERROR_CODE_AUTHENTICATION_FAILURE: + printf("Pairing failed, authentication failure with reason = %u\n", sm_event_pairing_complete_get_reason(packet)); + break; + default: + break; + } + break; + case SM_EVENT_REENCRYPTION_STARTED: + sm_event_reencryption_complete_get_address(packet, addr); + printf("Bonding information exists for addr type %u, identity addr %s -> re-encryption started\n", + sm_event_reencryption_started_get_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_REENCRYPTION_COMPLETE: + switch (sm_event_reencryption_complete_get_status(packet)){ + case ERROR_CODE_SUCCESS: + printf("Re-encryption complete, success\n"); + break; + case ERROR_CODE_CONNECTION_TIMEOUT: + printf("Re-encryption failed, timeout\n"); + break; + case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: + printf("Re-encryption failed, disconnected\n"); + break; + case ERROR_CODE_PIN_OR_KEY_MISSING: + printf("Re-encryption failed, bonding information missing\n\n"); + printf("Assuming remote lost bonding information\n"); + printf("Deleting local bonding information to allow for new pairing...\n"); + sm_event_reencryption_complete_get_address(packet, addr); + addr_type = sm_event_reencryption_started_get_addr_type(packet); + gap_delete_bonding(addr_type, addr); + break; + default: + break; + } + break; + case GATT_EVENT_QUERY_COMPLETE: + status = gatt_event_query_complete_get_att_status(packet); + switch (status){ + case ATT_ERROR_INSUFFICIENT_ENCRYPTION: + printf("GATT Query failed, Insufficient Encryption\n"); + break; + case ATT_ERROR_INSUFFICIENT_AUTHENTICATION: + printf("GATT Query failed, Insufficient Authentication\n"); + break; + case ATT_ERROR_BONDING_INFORMATION_MISSING: + printf("GATT Query failed, Bonding Information Missing\n"); + break; + case ATT_ERROR_SUCCESS: + printf("GATT Query successful\n"); + break; + default: + printf("GATT Query failed, status 0x%02x\n", gatt_event_query_complete_get_att_status(packet)); + break; + } + break; + default: + break; + } +} + +void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(size); + UNUSED(channel); + bd_addr_t local_addr; + if (packet_type != HCI_EVENT_PACKET) return; + + uint8_t event_type = hci_event_packet_get_type(packet); + switch(event_type){ + case HCI_EVENT_META_GAP: + switch (hci_event_gap_meta_get_subevent_code(packet)) { + case GAP_SUBEVENT_LE_CONNECTION_COMPLETE: + printf("Connection complete\n"); + con_handle = gap_subevent_le_connection_complete_get_connection_handle(packet); + UNUSED(con_handle); + sm_request_pairing(con_handle); + break; + default: + break; + } + break; + + case BTSTACK_EVENT_STATE: + if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return; + gap_local_bd_addr(local_addr); + printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); + + // setup advertisements + uint16_t adv_int_min = 800; + uint16_t adv_int_max = 800; + uint8_t adv_type = 0; + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + assert(adv_data_len <= 31); // ble limitation + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); + + break; + case HCI_EVENT_DISCONNECTION_COMPLETE: + le_notification_enabled = 0; + break; + case ATT_EVENT_CAN_SEND_NOW: + att_server_notify(con_handle, ATT_CHARACTERISTIC_b1829813_e8ec_4621_b9b5_6c1be43fe223_01_VALUE_HANDLE, (uint8_t*)ssid, sizeof(ssid)); + att_server_notify(con_handle, ATT_CHARACTERISTIC_410f5077_9e81_4f3b_b888_bf435174fa58_01_VALUE_HANDLE, (uint8_t*)password, sizeof(password)); + break; + + default: + break; + } +} + +uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + UNUSED(connection_handle); + + // SSID read callbaclk + if (att_handle == ATT_CHARACTERISTIC_b1829813_e8ec_4621_b9b5_6c1be43fe223_01_VALUE_HANDLE){ + return att_read_callback_handle_blob((const uint8_t *)&ssid, sizeof(ssid), offset, buffer, buffer_size); + } + + // Password read callback + if (att_handle == ATT_CHARACTERISTIC_410f5077_9e81_4f3b_b888_bf435174fa58_01_VALUE_HANDLE){ + return att_read_callback_handle_blob((const uint8_t *)&password, sizeof(password), offset, buffer, buffer_size); + } + + return 0; +} + +int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + UNUSED(transaction_mode); + UNUSED(offset); + UNUSED(buffer_size); + + le_notification_enabled = little_endian_read_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION; + con_handle = connection_handle; + if (le_notification_enabled) { + att_server_request_can_send_now_event(con_handle); + //This occurs when the client enables notification (the download button on nrf scanner) + } + + // First characteristic (SSID) + if (att_handle == ATT_CHARACTERISTIC_b1829813_e8ec_4621_b9b5_6c1be43fe223_01_VALUE_HANDLE){ + att_server_request_can_send_now_event(con_handle); + memset(ssid, 0, sizeof(ssid)); + memcpy(ssid, buffer, buffer_size); + //This occurs when the client sends a write request to the ssid characteristic (up arrow on nrf scanner) + printf("Current saved SSID: %s\n", ssid); + printf("Current saved password: %s\n", password); + } + + // Second characteristic (Password) + if (att_handle == ATT_CHARACTERISTIC_410f5077_9e81_4f3b_b888_bf435174fa58_01_VALUE_HANDLE){ + att_server_request_can_send_now_event(con_handle); + memset(password, 0, sizeof(password)); + memcpy(password, buffer, buffer_size); + //This occurs when the client sends a write request to the password characteristic (up arrow on nrf scanner) + printf("Current saved SSID: %s\n", ssid); + printf("Current saved password: %s\n", password); + } + + return 0; +} + +static void heartbeat_handler(struct btstack_timer_source *ts) { + static uint32_t counter = 0; + counter++; + + // Invert the led + static int led_on = true; + led_on = !led_on; + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on); + + // Restart timer + btstack_run_loop_set_timer(ts, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(ts); +} + +void save_credentials(char ssid[], char password[]) { + // create empty 256 byte list + uint8_t flash_data[FLASH_PAGE_SIZE] = {0}; + + uint ssid_len = strlen(ssid); + uint password_len = strlen(password); + + // no character has ascii value 0, so we can seperate our ssid and password with a single 0 + // first add ssid + for (uint i = 0; i < ssid_len; i++) { + int ascii = (int) ssid[i]; + flash_data[i] = ascii; + } + + //next add password + for (uint i = 0; i < password_len; i++) { + int ascii = (int) password[i]; + flash_data[i + ssid_len + 1] = ascii; + } + + //now erase and then write flash + int rc = flash_safe_execute(call_flash_range_erase, (void*)FLASH_TARGET_OFFSET, UINT32_MAX); + hard_assert(rc == PICO_OK); + + uintptr_t params[] = { FLASH_TARGET_OFFSET, (uintptr_t)flash_data}; + rc = flash_safe_execute(call_flash_range_program, params, UINT32_MAX); + hard_assert(rc == PICO_OK); +} + +void read_credentials(void) { + uint counter = 0; + uint ssid_len = 0; + + // first check if the flash page begins with FF - this indicates the flash has not yet been written to + // so must initialise with empty write (otherwise crashes) + if (flash_target_contents[0] == 255) { + save_credentials("", ""); + } + + //initialise temporary ssid and password as 1 bigger than max to ensure null termination + char t_ssid[33] = {0}; + char t_password[64] = {0}; + + // itterate through the flash and seperate ssid and password + for (uint i = 0; i < FLASH_PAGE_SIZE; i++) { + // when detect first zero, increment counter and continue. update ssid_len so we can index password + if (flash_target_contents[i] == 0 && counter == 0) { + counter++; + ssid_len = i; + continue; + } + // when detect second zero, have extracted both ssid and password so stop + else if (flash_target_contents[i] == 0 && counter == 1) + { + break; + } + // otherwise just write ssid and password + else if (counter == 0) { + t_ssid[i] = (char) flash_target_contents[i]; + } + else if (counter == 1) { + t_password[i - ssid_len - 1] = (char) flash_target_contents[i]; + } + } + // update global ssid and password + memset(ssid, 0, sizeof(ssid)); + memcpy(ssid, t_ssid, sizeof(t_ssid)); + + memset(password, 0, sizeof(password)); + memcpy(password, t_password, sizeof(t_password)); +} + +// this function carries out the BLE credential provisioning and also wifi connection +int start_ble_wifi_provisioning(int ble_timeout_ms) { + stdio_init_all(); + + // initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1) + if (cyw43_arch_init()) { + printf("failed to initialise cyw43_arch\n"); + return -1; + } + + l2cap_init(); + sm_init(); + + att_server_init(profile_data, att_read_callback, att_write_callback); + + // inform about BTstack state + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + // secure manager register handler + sm_event_callback_registration.callback = &sm_packet_handler; + sm_add_event_handler(&sm_event_callback_registration); + + // configure secure BLE (Just works) (legacy pairing) + gatt_client_set_required_security_level(LEVEL_2); + sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); + sm_set_authentication_requirements(0); + + // register for ATT event + att_server_register_packet_handler(packet_handler); + + // set one-shot btstack timer + heartbeat.process = &heartbeat_handler; + btstack_run_loop_set_timer(&heartbeat, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(&heartbeat); + + // turn on bluetooth! + hci_power_control(HCI_POWER_ON); + + read_credentials(); + printf("Current saved SSID: %s\n", ssid); + printf("Current saved password: %s\n", password); + + // first attempt to connect using saved credentials + cyw43_arch_enable_sta_mode(); + if (cyw43_arch_wifi_connect_timeout_ms(ssid, password, CYW43_AUTH_WPA2_AES_PSK, 5000)) { + printf("failed to connect with saved credentials \n"); + } else { + printf("Connected.\n"); + connection_status = true; + } + + // If this fails, wait for user to provision credentials over BLE until timeout + // cyw43_arch_wifi_connect_timeout_ms returns -2 for timeout and -7 for incorrect password + // wish to keep trying if password incorrect + int result; + if (connection_status == false) { + while (true) { + result = cyw43_arch_wifi_connect_timeout_ms(ssid, password, CYW43_AUTH_WPA2_AES_PSK, ble_timeout_ms); + if (result == -2) { + panic("Timed out - failed provisioning! \n"); + } else if (result == 0) { + connection_status = true; + printf("Succesfully provisioned credentials through BLE! \n"); + // since connected, save credentiald for future use + save_credentials(ssid, password); + break; + } else if (result == -7) { + printf("Incorrect password - retrying \n"); + } else { + panic("Connection error - failed provisioning! \n"); + } + } + } + // once finished, turn off bluetooth + hci_power_control(HCI_POWER_OFF); + return 0; +} diff --git a/pico_w/bt/standalone/wifi_provisioner/wifi_prov_lib.h b/pico_w/bt/standalone/wifi_provisioner/wifi_prov_lib.h new file mode 100644 index 000000000..53bd1f2b0 --- /dev/null +++ b/pico_w/bt/standalone/wifi_provisioner/wifi_prov_lib.h @@ -0,0 +1,18 @@ +#include +#include "btstack.h" + +#ifndef WIFI_PROV_LIB +#define WIFI_PROV_LIB + +extern int le_notification_enabled; +extern hci_con_handle_t con_handle; +extern uint8_t const profile_data[]; + +void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); +void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); +uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); +int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size); + +int start_ble_wifi_provisioning(int ble_timeout_ms); + +#endif \ No newline at end of file diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index b096d2583..36d73f43f 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -4,6 +4,7 @@ set(WIFI_PASSWORD "${WIFI_PASSWORD}" CACHE INTERNAL "WiFi password for examples" add_subdirectory_exclude_platforms(blink) add_subdirectory_exclude_platforms(wifi_scan) add_subdirectory_exclude_platforms(access_point) +add_subdirectory_exclude_platforms(access_point_wifi_provisioning) if ("${WIFI_SSID}" STREQUAL "") message("Skipping some Pico W examples as WIFI_SSID is not defined") diff --git a/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c b/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c new file mode 100644 index 000000000..ec89a383a --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c @@ -0,0 +1,355 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/cyw43_arch.h" +#include "pico/stdlib.h" + +#include "lwip/ip4_addr.h" +#include "lwip/init.h" +#include "lwip/apps/httpd.h" + +#include "dhcpserver.h" +#include "dnsserver.h" + +#include "pico/flash.h" +#include "hardware/flash.h" // for saving succesful credentials + +static absolute_time_t wifi_connected_time; +static bool led_on = false; + +// max lengths + 1 +static char ssid[33]; +static char password[64]; + +static int num_credentials; + +static bool connection_status = false; + +// how many sectors would you like to reserve +// each sector is 4096 bytes, so can hold 40 pairs of max length credentials +#define DESIRED_FLASH_SECTORS 1 +static char ssid_list[40 * DESIRED_FLASH_SECTORS][33]; +static char password_list[40 * DESIRED_FLASH_SECTORS][64]; + +// Define flash offset towards end of flash +#ifndef PICO_FLASH_BANK_TOTAL_SIZE +#define PICO_FLASH_BANK_TOTAL_SIZE (FLASH_SECTOR_SIZE * 2u) +#endif + +#ifndef PICO_FLASH_BANK_STORAGE_OFFSET +#if PICO_RP2350 && PICO_RP2350_A2_SUPPORTED +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE - PICO_FLASH_BANK_TOTAL_SIZE - FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS) +#else +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - PICO_FLASH_BANK_TOTAL_SIZE - FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS) +#endif +#endif + +static const uint8_t *flash_target_contents = (const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET); + +// Function prototypes +static void call_flash_range_erase(void *param); +static void call_flash_range_program(void *param); + +static void save_credentials(char ssid[], char password[]); +static void read_credentials(void); + +static void attempt_wifi_connection(void); + +static const char *credential_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); +static const char *connect_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); +static const char *connect_from_saved_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); +static const char *clear_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); + +static u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen +#if LWIP_HTTPD_SSI_MULTIPART + , uint16_t current_tag_part, uint16_t *next_tag_part +#endif +); + +static tCGI cgi_handlers[] = { + { "/credentials.cgi", credential_cgi_handler }, + { "/connect.cgi", connect_cgi_handler }, + { "/connect_from_saved.cgi", connect_from_saved_cgi_handler}, + {"/clear.cgi", clear_cgi_handler} +}; + +// Be aware of LWIP_HTTPD_MAX_TAG_NAME_LEN +static const char *ssi_tags[] = { + "wifilist", + "ssid", + "password" +}; + +int main() { + stdio_init_all(); + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + printf("intitialised\n"); + + // for testing, erase memory first + // might need to erase memory first time you use provisioning in case there are garbage values in flash + //int rc = flash_safe_execute(call_flash_range_erase, (void*)FLASH_TARGET_OFFSET, UINT32_MAX); + //hard_assert(rc == PICO_OK); + + // First, try to connect to network using saved credentials + read_credentials(); + + cyw43_arch_enable_sta_mode(); + for (int i = 0; i < num_credentials; i++) { + if (cyw43_arch_wifi_connect_timeout_ms(ssid_list[i], password_list[i], CYW43_AUTH_WPA2_AES_PSK, 5000)) { + printf("failed to connect with saved credentials %i \n", i); + } else { + printf("Connected.\n"); + connection_status = true; + break; + } + } + + // If this fails, enable access point + if (connection_status == false) { + cyw43_arch_disable_sta_mode(); + cyw43_arch_enable_ap_mode("picow_test", "12345678", CYW43_AUTH_WPA2_AES_PSK); + printf("\nReady, running server at %s\n", ip4addr_ntoa(netif_ip4_addr(netif_list))); + + #if LWIP_IPV6 + #define IP(x) ((x).u_addr.ip4) + #else + #define IP(x) (x) + #endif + + ip4_addr_t mask; + ip4_addr_t gw; + IP(gw).addr = PP_HTONL(CYW43_DEFAULT_IP_AP_ADDRESS); + IP(mask).addr = PP_HTONL(CYW43_DEFAULT_IP_MASK); + + #undef IP + dhcp_server_t dhcp_server; + dhcp_server_init(&dhcp_server, &gw, &mask); + + dns_server_t dns_server; + dns_server_init(&dns_server, &gw); + + char hostname[sizeof(CYW43_HOST_NAME) + 4]; + memcpy(&hostname[0], CYW43_HOST_NAME, sizeof(CYW43_HOST_NAME) - 1); + hostname[sizeof(hostname) - 1] = '\0'; + netif_set_hostname(&cyw43_state.netif[CYW43_ITF_STA], hostname); + + // start http server + wifi_connected_time = get_absolute_time(); + + // setup http server + cyw43_arch_lwip_begin(); + httpd_init(); + http_set_cgi_handlers(cgi_handlers, LWIP_ARRAYSIZE(cgi_handlers)); + http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags)); + cyw43_arch_lwip_end(); + } + + //wait for connection + while(connection_status == false) { + cyw43_arch_poll(); + cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000)); + } + + printf("Finished provisioning credentials. \n"); + cyw43_arch_deinit(); + return 0; +} + +// This function will be called when it's safe to call flash_range_erase +static void call_flash_range_erase(void *param) { + uint32_t offset = (uint32_t)param; + flash_range_erase(offset, FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS); +} + +// This function will be called when it's safe to call flash_range_program +static void call_flash_range_program(void *param) { + uint32_t offset = ((uintptr_t*)param)[0]; + const uint8_t *data = (const uint8_t *)((uintptr_t*)param)[1]; + flash_range_program(offset, data, FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS); +} + +// Functions for saving and reading credentials from flash +static void save_credentials(char ssid[], char password[]) { + // create empty 256 byte list + uint8_t flash_data[FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS] = {0}; + + uint ssid_len = strlen(ssid); + uint password_len = strlen(password); + uint credential_count; + + //first check how many credentials are already saved + if (flash_target_contents[1] != 255) { + credential_count = flash_target_contents[1]; + //incriment this count since we are about to add a credential + credential_count++; + flash_data[1] = credential_count; + } else { + // first (empty) save, so dont want to incriment + credential_count = 0; + } + + //now need to find how far through the flash to start writing, and also add previous stuff to flash data + uint write_start_location = 2; + if (credential_count != 0) { + uint count = 0; + while (count < 2 * credential_count - 2) { + flash_data[write_start_location] = flash_target_contents[write_start_location]; + if (flash_target_contents[write_start_location] == 0) { + count++; + } + write_start_location++; + } + } + + // no character has ascii value 0, so we can seperate our ssid and password with a single 0 + // first add ssid + for (uint i = 0; i < ssid_len; i++) { + int ascii = (int) ssid[i]; + //printf("%i\n", ascii); + flash_data[i + write_start_location] = ascii; + } + + //next add password + for (uint i = 0; i < password_len; i++) { + int ascii = (int) password[i]; + flash_data[i + ssid_len + write_start_location + 1] = ascii; + } + + // must always erase flash before write + int rc = flash_safe_execute(call_flash_range_erase, (void*)FLASH_TARGET_OFFSET, UINT32_MAX); + hard_assert(rc == PICO_OK); + + // write flash + uintptr_t params[] = { FLASH_TARGET_OFFSET, (uintptr_t)flash_data}; + rc = flash_safe_execute(call_flash_range_program, params, UINT32_MAX); + hard_assert(rc == PICO_OK); +} + +static void read_credentials(void) { + uint credential_count; + + // first check if the flash page begins with FF - this indicates the flash has not yet been written to + // so must initialise with empty write + if (flash_target_contents[0] == 255) { + save_credentials("", ""); + } + + //second byte saves credential count (allows 255 sets of credentials, should be enough) + credential_count = flash_target_contents[1]; + num_credentials = credential_count; + //initialise temporary ssid and password as 1 bigger than max to ensure null termination + char t_ssid_list[20][33] = {0}; + char t_password_list[20][64] = {0}; + + uint space_count = 0; + uint start_index = 1; + + for (uint i = 2; i < FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS; i++) { + if (space_count >= 2*credential_count) { + break; + } else if (flash_target_contents[i] == 0) { + space_count++; + start_index = i; + printf("\n"); + //printf("space count %i\n", space_count); + } else if (flash_target_contents[i] != 0 && space_count % 2 == 0) { + // there is a char, and even space count. So we are reading a ssid + t_ssid_list[(int) space_count / 2][i - start_index - 1] = flash_target_contents[i]; + //printf("%c", flash_target_contents[i]); + } else if (flash_target_contents[i] != 0 && space_count % 2 == 1) { + // there is a char and odd space count, so reading password + t_password_list[(int) space_count / 2][i - start_index - 1] = flash_target_contents[i]; + //printf("%c", flash_target_contents[i]); + } + } + + // update global ssid and password lists + memset(ssid_list, 0, sizeof(ssid_list)); + memcpy(ssid_list, t_ssid_list, sizeof(t_ssid_list)); + + memset(password_list, 0, sizeof(password_list)); + memcpy(password_list, t_password_list, sizeof(t_password_list)); +} + +static void attempt_wifi_connection(void) { + cyw43_arch_disable_ap_mode(); + cyw43_arch_enable_sta_mode(); + + if (cyw43_arch_wifi_connect_timeout_ms(ssid, password, CYW43_AUTH_WPA2_AES_PSK, 15000)) { + panic("Failed to connect!"); + } else { + printf("Connected.\n"); + connection_status = true; + // success, so save credentials for future use + save_credentials(ssid, password); + } +} + +static const char *credential_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("credential_cgi_handler called\n"); + strncpy(ssid, pcValue[0], sizeof(ssid) - 1); + strncpy(password, pcValue[1], sizeof(password) - 1); + printf("SSID AND PASSWORD: %s %s \n", ssid, password); + return "/index.shtml"; +} + +static const char *connect_from_saved_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("load_from_saved_cgi_handler called\n"); + strncpy(ssid, ssid_list[atoi(pcValue[0])], sizeof(ssid) - 1); + strncpy(password, password_list[atoi(pcValue[0])], sizeof(password) - 1); + attempt_wifi_connection(); + return "/index.shtml"; +} + +static const char *connect_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("connect_cgi_handler called\n"); + attempt_wifi_connection(); + return "/index.shtml"; +} + +static const char *clear_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("clear_cgi_handler called\n"); + int rc = flash_safe_execute(call_flash_range_erase, (void*)FLASH_TARGET_OFFSET, UINT32_MAX); + hard_assert(rc == PICO_OK); + save_credentials("", ""); + read_credentials(); + return "/index.shtml"; +} + +// Note that the buffer size is limited by LWIP_HTTPD_MAX_TAG_INSERT_LEN, so use LWIP_HTTPD_SSI_MULTIPART to return larger amounts of data +static u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen +#if LWIP_HTTPD_SSI_MULTIPART + , uint16_t current_tag_part, uint16_t *next_tag_part +#endif +) { + int printed = 0; + switch (iIndex) { + case 0: { // wifilist + for (int i = 0; i < num_credentials; i++) { + printed += snprintf(pcInsert + printed, iInsertLen - printed, + "
  • SSID: %s, Password: %s, Index: %i
  • ", + ssid_list[i], password_list[i], i); + } + break; + } + case 1: { // ssid + printed = snprintf(pcInsert, iInsertLen, ssid); + break; + } + case 2: { // password + printed = snprintf(pcInsert, iInsertLen, password); + break; + } + default: { // unknown tag + printed = 0; + break; + } + } + return printed; +} \ No newline at end of file diff --git a/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt b/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt new file mode 100644 index 000000000..c22c25a5d --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt @@ -0,0 +1,25 @@ +add_executable(AP_provisioning + AP_provisioning.c + dhcpserver/dhcpserver.c + dnsserver/dnsserver.c + ) +target_include_directories(AP_provisioning PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ${PICO_LWIP_CONTRIB_PATH}/apps/httpd + ${CMAKE_CURRENT_LIST_DIR}/dhcpserver + ${CMAKE_CURRENT_LIST_DIR}/dnsserver + ) +target_link_libraries(AP_provisioning PRIVATE + pico_cyw43_arch_lwip_poll + pico_lwip_http + ap_provisioning_content + pico_stdlib + hardware_flash + ) +pico_add_extra_outputs(AP_provisioning) + +pico_add_library(ap_provisioning_content NOFLAG) +pico_set_lwip_httpd_content(ap_provisioning_content INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/content/index.shtml + ) \ No newline at end of file diff --git a/pico_w/wifi/access_point_wifi_provisioning/README.md b/pico_w/wifi/access_point_wifi_provisioning/README.md new file mode 100644 index 000000000..b264909ff --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/README.md @@ -0,0 +1,4 @@ +### Access point wifi provisioning + +This example demonstrates provisioning wifi credentials using an access point. The pico saves succesful credentials in flash, which it first uses when attempting to join wifi. If this fails, the pico starts an access point which you can connect to and input credentials. The DNS server means that any address you enter when connected to the access point will take you to the provisioning web page. + diff --git a/pico_w/wifi/access_point_wifi_provisioning/content/index.shtml b/pico_w/wifi/access_point_wifi_provisioning/content/index.shtml new file mode 100644 index 000000000..5f552d417 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/content/index.shtml @@ -0,0 +1,57 @@ + + + + + + + +

    AP provisioning

    + +

    Enter new credentials, submit, then connect, or select from previously saved.

    + +
    + +
    +

    Current SSID:

    +

    Current password:

    + +
    +
    +
    +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +

    List of saved credentials:

    +
    +
    +
    + +
    +
    + + +
    + + + \ No newline at end of file diff --git a/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/LICENSE b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/LICENSE new file mode 100644 index 000000000..8f9b52cbb --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2022 Damien P. George + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.c b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.c new file mode 100644 index 000000000..2061d047e --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.c @@ -0,0 +1,309 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// For DHCP specs see: +// https://www.ietf.org/rfc/rfc2131.txt +// https://tools.ietf.org/html/rfc2132 -- DHCP Options and BOOTP Vendor Extensions + +#include +#include +#include + +#include "cyw43_config.h" +#include "dhcpserver.h" +#include "lwip/udp.h" + +#define DHCPDISCOVER (1) +#define DHCPOFFER (2) +#define DHCPREQUEST (3) +#define DHCPDECLINE (4) +#define DHCPACK (5) +#define DHCPNACK (6) +#define DHCPRELEASE (7) +#define DHCPINFORM (8) + +#define DHCP_OPT_PAD (0) +#define DHCP_OPT_SUBNET_MASK (1) +#define DHCP_OPT_ROUTER (3) +#define DHCP_OPT_DNS (6) +#define DHCP_OPT_HOST_NAME (12) +#define DHCP_OPT_REQUESTED_IP (50) +#define DHCP_OPT_IP_LEASE_TIME (51) +#define DHCP_OPT_MSG_TYPE (53) +#define DHCP_OPT_SERVER_ID (54) +#define DHCP_OPT_PARAM_REQUEST_LIST (55) +#define DHCP_OPT_MAX_MSG_SIZE (57) +#define DHCP_OPT_VENDOR_CLASS_ID (60) +#define DHCP_OPT_CLIENT_ID (61) +#define DHCP_OPT_END (255) + +#define PORT_DHCP_SERVER (67) +#define PORT_DHCP_CLIENT (68) + +#define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds + +#define MAC_LEN (6) +#define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + +typedef struct { + uint8_t op; // message opcode + uint8_t htype; // hardware address type + uint8_t hlen; // hardware address length + uint8_t hops; + uint32_t xid; // transaction id, chosen by client + uint16_t secs; // client seconds elapsed + uint16_t flags; + uint8_t ciaddr[4]; // client IP address + uint8_t yiaddr[4]; // your IP address + uint8_t siaddr[4]; // next server IP address + uint8_t giaddr[4]; // relay agent IP address + uint8_t chaddr[16]; // client hardware address + uint8_t sname[64]; // server host name + uint8_t file[128]; // boot file name + uint8_t options[312]; // optional parameters, variable, starts with magic +} dhcp_msg_t; + +static int dhcp_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { + // family is AF_INET + // type is SOCK_DGRAM + + *udp = udp_new(); + if (*udp == NULL) { + return -ENOMEM; + } + + // Register callback + udp_recv(*udp, cb_udp_recv, (void *)cb_data); + + return 0; // success +} + +static void dhcp_socket_free(struct udp_pcb **udp) { + if (*udp != NULL) { + udp_remove(*udp); + *udp = NULL; + } +} + +static int dhcp_socket_bind(struct udp_pcb **udp, uint16_t port) { + // TODO convert lwIP errors to errno + return udp_bind(*udp, IP_ANY_TYPE, port); +} + +static int dhcp_socket_sendto(struct udp_pcb **udp, struct netif *nif, const void *buf, size_t len, uint32_t ip, uint16_t port) { + if (len > 0xffff) { + len = 0xffff; + } + + struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (p == NULL) { + return -ENOMEM; + } + + memcpy(p->payload, buf, len); + + ip_addr_t dest; + IP4_ADDR(ip_2_ip4(&dest), ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); + err_t err; + if (nif != NULL) { + err = udp_sendto_if(*udp, p, &dest, port, nif); + } else { + err = udp_sendto(*udp, p, &dest, port); + } + + pbuf_free(p); + + if (err != ERR_OK) { + return err; + } + + return len; +} + +static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) { + for (int i = 0; i < 308 && opt[i] != DHCP_OPT_END;) { + if (opt[i] == cmd) { + return &opt[i]; + } + i += 2 + opt[i + 1]; + } + return NULL; +} + +static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, const void *data) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = n; + memcpy(o, data, n); + *opt = o + n; +} + +static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = 1; + *o++ = val; + *opt = o; +} + +static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = 4; + *o++ = val >> 24; + *o++ = val >> 16; + *o++ = val >> 8; + *o++ = val; + *opt = o; +} + +static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { + dhcp_server_t *d = arg; + (void)upcb; + (void)src_addr; + (void)src_port; + + // This is around 548 bytes + dhcp_msg_t dhcp_msg; + + #define DHCP_MIN_SIZE (240 + 3) + if (p->tot_len < DHCP_MIN_SIZE) { + goto ignore_request; + } + + size_t len = pbuf_copy_partial(p, &dhcp_msg, sizeof(dhcp_msg), 0); + if (len < DHCP_MIN_SIZE) { + goto ignore_request; + } + + dhcp_msg.op = DHCPOFFER; + memcpy(&dhcp_msg.yiaddr, &ip4_addr_get_u32(ip_2_ip4(&d->ip)), 4); + + uint8_t *opt = (uint8_t *)&dhcp_msg.options; + opt += 4; // assume magic cookie: 99, 130, 83, 99 + + uint8_t *msgtype = opt_find(opt, DHCP_OPT_MSG_TYPE); + if (msgtype == NULL) { + // A DHCP package without MSG_TYPE? + goto ignore_request; + } + + switch (msgtype[2]) { + case DHCPDISCOVER: { + int yi = DHCPS_MAX_IP; + for (int i = 0; i < DHCPS_MAX_IP; ++i) { + if (memcmp(d->lease[i].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { + // MAC match, use this IP address + yi = i; + break; + } + if (yi == DHCPS_MAX_IP) { + // Look for a free IP address + if (memcmp(d->lease[i].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { + // IP available + yi = i; + } + uint32_t expiry = d->lease[i].expiry << 16 | 0xffff; + if ((int32_t)(expiry - cyw43_hal_ticks_ms()) < 0) { + // IP expired, reuse it + memset(d->lease[i].mac, 0, MAC_LEN); + yi = i; + } + } + } + if (yi == DHCPS_MAX_IP) { + // No more IP addresses left + goto ignore_request; + } + dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; + opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPOFFER); + break; + } + + case DHCPREQUEST: { + uint8_t *o = opt_find(opt, DHCP_OPT_REQUESTED_IP); + if (o == NULL) { + // Should be NACK + goto ignore_request; + } + if (memcmp(o + 2, &ip4_addr_get_u32(ip_2_ip4(&d->ip)), 3) != 0) { + // Should be NACK + goto ignore_request; + } + uint8_t yi = o[5] - DHCPS_BASE_IP; + if (yi >= DHCPS_MAX_IP) { + // Should be NACK + goto ignore_request; + } + if (memcmp(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { + // MAC match, ok to use this IP address + } else if (memcmp(d->lease[yi].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { + // IP unused, ok to use this IP address + memcpy(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN); + } else { + // IP already in use + // Should be NACK + goto ignore_request; + } + d->lease[yi].expiry = (cyw43_hal_ticks_ms() + DEFAULT_LEASE_TIME_S * 1000) >> 16; + dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; + opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPACK); + printf("DHCPS: client connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x IP=%u.%u.%u.%u\n", + dhcp_msg.chaddr[0], dhcp_msg.chaddr[1], dhcp_msg.chaddr[2], dhcp_msg.chaddr[3], dhcp_msg.chaddr[4], dhcp_msg.chaddr[5], + dhcp_msg.yiaddr[0], dhcp_msg.yiaddr[1], dhcp_msg.yiaddr[2], dhcp_msg.yiaddr[3]); + break; + } + + default: + goto ignore_request; + } + + opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); + opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &ip4_addr_get_u32(ip_2_ip4(&d->nm))); + opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); // aka gateway; can have multiple addresses + opt_write_n(&opt, DHCP_OPT_DNS, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); // this server is the dns + opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S); + *opt++ = DHCP_OPT_END; + struct netif *nif = ip_current_input_netif(); + dhcp_socket_sendto(&d->udp, nif, &dhcp_msg, opt - (uint8_t *)&dhcp_msg, 0xffffffff, PORT_DHCP_CLIENT); + +ignore_request: + pbuf_free(p); +} + +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) { + ip_addr_copy(d->ip, *ip); + ip_addr_copy(d->nm, *nm); + memset(d->lease, 0, sizeof(d->lease)); + if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) { + return; + } + dhcp_socket_bind(&d->udp, PORT_DHCP_SERVER); +} + +void dhcp_server_deinit(dhcp_server_t *d) { + dhcp_socket_free(&d->udp); +} diff --git a/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.h b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.h new file mode 100644 index 000000000..2349d2ea4 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H +#define MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H + +#include "lwip/ip_addr.h" + +#define DHCPS_BASE_IP (16) +#define DHCPS_MAX_IP (8) + +typedef struct _dhcp_server_lease_t { + uint8_t mac[6]; + uint16_t expiry; +} dhcp_server_lease_t; + +typedef struct _dhcp_server_t { + ip_addr_t ip; + ip_addr_t nm; + dhcp_server_lease_t lease[DHCPS_MAX_IP]; + struct udp_pcb *udp; +} dhcp_server_t; + +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm); +void dhcp_server_deinit(dhcp_server_t *d); + +#endif // MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H diff --git a/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.c b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.c new file mode 100644 index 000000000..029870b56 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.c @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include "dnsserver.h" +#include "lwip/udp.h" + +#define PORT_DNS_SERVER 53 +#define DUMP_DATA 0 + +#define DEBUG_printf(...) +#define ERROR_printf printf + +typedef struct dns_header_t_ { + uint16_t id; + uint16_t flags; + uint16_t question_count; + uint16_t answer_record_count; + uint16_t authority_record_count; + uint16_t additional_record_count; +} dns_header_t; + +#define MAX_DNS_MSG_SIZE 300 + +static int dns_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { + *udp = udp_new(); + if (*udp == NULL) { + return -ENOMEM; + } + udp_recv(*udp, cb_udp_recv, (void *)cb_data); + return ERR_OK; +} + +static void dns_socket_free(struct udp_pcb **udp) { + if (*udp != NULL) { + udp_remove(*udp); + *udp = NULL; + } +} + +static int dns_socket_bind(struct udp_pcb **udp, uint32_t ip, uint16_t port) { + ip_addr_t addr; + IP4_ADDR(&addr, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); + err_t err = udp_bind(*udp, &addr, port); + if (err != ERR_OK) { + ERROR_printf("dns failed to bind to port %u: %d", port, err); + assert(false); + } + return err; +} + +#if DUMP_DATA +static void dump_bytes(const uint8_t *bptr, uint32_t len) { + unsigned int i = 0; + + for (i = 0; i < len;) { + if ((i & 0x0f) == 0) { + printf("\n"); + } else if ((i & 0x07) == 0) { + printf(" "); + } + printf("%02x ", bptr[i++]); + } + printf("\n"); +} +#endif + +static int dns_socket_sendto(struct udp_pcb **udp, const void *buf, size_t len, const ip_addr_t *dest, uint16_t port) { + if (len > 0xffff) { + len = 0xffff; + } + + struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (p == NULL) { + ERROR_printf("DNS: Failed to send message out of memory\n"); + return -ENOMEM; + } + + memcpy(p->payload, buf, len); + err_t err = udp_sendto(*udp, p, dest, port); + + pbuf_free(p); + + if (err != ERR_OK) { + ERROR_printf("DNS: Failed to send message %d\n", err); + return err; + } + +#if DUMP_DATA + dump_bytes(buf, len); +#endif + return len; +} + +static void dns_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { + dns_server_t *d = arg; + DEBUG_printf("dns_server_process %u\n", p->tot_len); + + uint8_t dns_msg[MAX_DNS_MSG_SIZE]; + dns_header_t *dns_hdr = (dns_header_t*)dns_msg; + + size_t msg_len = pbuf_copy_partial(p, dns_msg, sizeof(dns_msg), 0); + if (msg_len < sizeof(dns_header_t)) { + goto ignore_request; + } + +#if DUMP_DATA + dump_bytes(dns_msg, msg_len); +#endif + + uint16_t flags = lwip_ntohs(dns_hdr->flags); + uint16_t question_count = lwip_ntohs(dns_hdr->question_count); + + DEBUG_printf("len %d\n", msg_len); + DEBUG_printf("dns flags 0x%x\n", flags); + DEBUG_printf("dns question count 0x%x\n", question_count); + + // flags from rfc1035 + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + // Check QR indicates a query + if (((flags >> 15) & 0x1) != 0) { + DEBUG_printf("Ignoring non-query\n"); + goto ignore_request; + } + + // Check for standard query + if (((flags >> 11) & 0xf) != 0) { + DEBUG_printf("Ignoring non-standard query\n"); + goto ignore_request; + } + + // Check question count + if (question_count < 1) { + DEBUG_printf("Invalid question count\n"); + goto ignore_request; + } + + // Print the question + DEBUG_printf("question: "); + const uint8_t *question_ptr_start = dns_msg + sizeof(dns_header_t); + const uint8_t *question_ptr_end = dns_msg + msg_len; + const uint8_t *question_ptr = question_ptr_start; + while(question_ptr < question_ptr_end) { + if (*question_ptr == 0) { + question_ptr++; + break; + } else { + if (question_ptr > question_ptr_start) { + DEBUG_printf("."); + } + int label_len = *question_ptr++; + if (label_len > 63) { + DEBUG_printf("Invalid label\n"); + goto ignore_request; + } + DEBUG_printf("%.*s", label_len, question_ptr); + question_ptr += label_len; + } + } + DEBUG_printf("\n"); + + // Check question length + if (question_ptr - question_ptr_start > 255) { + DEBUG_printf("Invalid question length\n"); + goto ignore_request; + } + + // Skip QNAME and QTYPE + question_ptr += 4; + + // Generate answer + uint8_t *answer_ptr = dns_msg + (question_ptr - dns_msg); + *answer_ptr++ = 0xc0; // pointer + *answer_ptr++ = question_ptr_start - dns_msg; // pointer to question + + *answer_ptr++ = 0; + *answer_ptr++ = 1; // host address + + *answer_ptr++ = 0; + *answer_ptr++ = 1; // Internet class + + *answer_ptr++ = 0; + *answer_ptr++ = 0; + *answer_ptr++ = 0; + *answer_ptr++ = 60; // ttl 60s + + *answer_ptr++ = 0; + *answer_ptr++ = 4; // length + memcpy(answer_ptr, &d->ip.addr, 4); // use our address + answer_ptr += 4; + + dns_hdr->flags = lwip_htons( + 0x1 << 15 | // QR = response + 0x1 << 10 | // AA = authoritative + 0x1 << 7); // RA = authenticated + dns_hdr->question_count = lwip_htons(1); + dns_hdr->answer_record_count = lwip_htons(1); + dns_hdr->authority_record_count = 0; + dns_hdr->additional_record_count = 0; + + // Send the reply + DEBUG_printf("Sending %d byte reply to %s:%d\n", answer_ptr - dns_msg, ipaddr_ntoa(src_addr), src_port); + dns_socket_sendto(&d->udp, &dns_msg, answer_ptr - dns_msg, src_addr, src_port); + +ignore_request: + pbuf_free(p); +} + +void dns_server_init(dns_server_t *d, ip_addr_t *ip) { + if (dns_socket_new_dgram(&d->udp, d, dns_server_process) != ERR_OK) { + DEBUG_printf("dns server failed to start\n"); + return; + } + if (dns_socket_bind(&d->udp, 0, PORT_DNS_SERVER) != ERR_OK) { + DEBUG_printf("dns server failed to bind\n"); + return; + } + ip_addr_copy(d->ip, *ip); + DEBUG_printf("dns server listening on port %d\n", PORT_DNS_SERVER); +} + +void dns_server_deinit(dns_server_t *d) { + dns_socket_free(&d->udp); +} diff --git a/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.h b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.h new file mode 100644 index 000000000..d23534c03 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _DNSSERVER_H_ +#define _DNSSERVER_H_ + +#include "lwip/ip_addr.h" + +typedef struct dns_server_t_ { + struct udp_pcb *udp; + ip_addr_t ip; +} dns_server_t; + +void dns_server_init(dns_server_t *d, ip_addr_t *ip); +void dns_server_deinit(dns_server_t *d); + +#endif diff --git a/pico_w/wifi/access_point_wifi_provisioning/lwipopts.h b/pico_w/wifi/access_point_wifi_provisioning/lwipopts.h new file mode 100644 index 000000000..98170b8de --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/lwipopts.h @@ -0,0 +1,101 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Common settings used in most of the pico_w examples +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) + +// allow override in some examples +#ifndef NO_SYS +#define NO_SYS 1 +#endif +// allow override in some examples +#ifndef LWIP_SOCKET +#define LWIP_SOCKET 0 +#endif +#if PICO_CYW43_ARCH_POLL +#define MEM_LIBC_MALLOC 1 +#else +// MEM_LIBC_MALLOC is incompatible with non polling versions +#define MEM_LIBC_MALLOC 0 +#endif +#define MEM_ALIGNMENT 4 +#ifndef MEM_SIZE +#define MEM_SIZE 4000 +#endif +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + +// Enable some httpd features +#define LWIP_HTTPD_CGI 1 +#define LWIP_HTTPD_SSI 1 +#define LWIP_HTTPD_SSI_MULTIPART 1 +#define LWIP_HTTPD_SUPPORT_POST 0 +#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 + +// Generated file containing html data +#define HTTPD_FSDATA_FILE "pico_fsdata.inc" + +#endif \ No newline at end of file