diff --git a/.github/workflows/rpi_pico.yaml b/.github/workflows/rpi_pico.yaml new file mode 100644 index 000000000..bf67c7965 --- /dev/null +++ b/.github/workflows/rpi_pico.yaml @@ -0,0 +1,63 @@ +# +# Copyright (c) 2024 ZettaScale Technology +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +# +# Contributors: +# ZettaScale Zenoh Team, +# +name: rpi_pico + +on: + push: + branches: [ '**' ] + pull_request: + branches: [ '**' ] + +jobs: + build: + name: Build on ${{ matrix.os }} for ${{ matrix.pico_board }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + pico_board: ["pico", "pico_w", "pico2", "pico2_w"] + steps: + - uses: actions/checkout@v4 + - uses: jwlawson/actions-setup-cmake@v1.13 + - name: Install requirements + run: | + sudo apt update + sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential g++ libstdc++-arm-none-eabi-newlib + + - name: Install Raspberry Pico SDK + run: | + export PICO_SDK_PATH=$HOME/work/pico-sdk + mkdir -p $PICO_SDK_PATH + cd $PICO_SDK_PATH + git clone https://github.com/raspberrypi/pico-sdk.git --branch 2.1.0 . + git submodule update --init + + - name: Install FreeRTOS SDK + run: | + export FREERTOS_KERNEL_PATH=$HOME/work/FreeRTOS-Kernel/ + mkdir -p $FREERTOS_KERNEL_PATH + cd $FREERTOS_KERNEL_PATH + git clone https://github.com/FreeRTOS/FreeRTOS-Kernel.git . + git submodule update --init + + - name: Build examples + run: | + export PICO_SDK_PATH=$HOME/work/pico-sdk + export FREERTOS_KERNEL_PATH=$HOME/work/FreeRTOS-Kernel/ + cd $HOME/work/zenoh-pico/zenoh-pico/examples/rpi_pico + cmake -Bbuild -DWIFI_SSID=wifi_network_ssid -DWIFI_PASSWORD=wifi_network_password -DPICO_BOARD="$PICO_BOARD" + cmake --build ./build + env: + PICO_BOARD: ${{ matrix.pico_board}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a9fb6d10..02911e326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ include(GNUInstallDirs) option(BUILD_SHARED_LIBS "Build shared libraries if ON, otherwise build static libraries" ON) option(WITH_ZEPHYR "Build for Zephyr RTOS" OFF) option(WITH_FREERTOS_PLUS_TCP "Build for FreeRTOS RTOS and FreeRTOS-Plus-TCP network stack" OFF) +option(WITH_RPI_PICO "Build for Raspberry Pi Pico" OFF) set(ZENOH_DEBUG 0 CACHE STRING "Use this to set the ZENOH_DEBUG variable") set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") if(CMAKE_EXPORT_COMPILE_COMMANDS) @@ -90,7 +91,9 @@ if(CMAKE_BUILD_TYPE MATCHES "RELEASE" OR "Release") add_compile_options(-pipe -O3) endif() else() - if(UNIX) + if(CMAKE_SYSTEM_NAME MATCHES "PICO") + add_compile_options(-c -Wall -Wextra -Wno-unused -Wno-strict-prototypes -pipe -g -O0) + elseif(UNIX) add_compile_options(-c -Wall -Wextra -Werror -Wshadow -Wunused -Wstrict-prototypes -pipe -g -O0) # C99 pedantic doesn't like struct anonymous in unix header if (NOT CMAKE_C_STANDARD STREQUAL "99") @@ -182,6 +185,9 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Generic") elseif(WITH_FREERTOS_PLUS_TCP) pico_add_compile_definition(ZENOH_FREERTOS_PLUS_TCP) endif() +elseif(CMAKE_SYSTEM_NAME MATCHES "PICO") # Raspberry Pi Pico Series + pico_add_compile_definition(ZENOH_RPI_PICO) + set(CHECK_THREADS "OFF") else() message(FATAL_ERROR "zenoh-pico is not yet available on ${CMAKE_SYSTEM_NAME} platform") return() @@ -226,6 +232,7 @@ set(Z_FEATURE_LINK_TCP 1 CACHE STRING "Toggle TCP links") set(Z_FEATURE_LINK_BLUETOOTH 0 CACHE STRING "Toggle Bluetooth links") set(Z_FEATURE_LINK_WS 0 CACHE STRING "Toggle WebSocket links") set(Z_FEATURE_LINK_SERIAL 0 CACHE STRING "Toggle Serial links") +set(Z_FEATURE_LINK_SERIAL_USB 0 CACHE STRING "Toggle Serial USB links") set(Z_FEATURE_SCOUTING_UDP 1 CACHE STRING "Toggle UDP scouting") set(Z_FEATURE_LINK_UDP_MULTICAST 1 CACHE STRING "Toggle UDP multicast links") set(Z_FEATURE_LINK_UDP_UNICAST 1 CACHE STRING "Toggle UDP unicast links") @@ -240,6 +247,12 @@ if(Z_FEATURE_LIVELINESS AND NOT Z_FEATURE_UNSTABLE_API) set(Z_FEATURE_LIVELINESS 0 CACHE STRING "Toggle liveliness feature" FORCE) endif() +# Add a warning message if someone tries to enable Z_FEATURE_LINK_SERIAL_USB directly +if(Z_FEATURE_LINK_SERIAL_USB AND NOT Z_FEATURE_UNSTABLE_API) + message(WARNING "Z_FEATURE_LINK_SERIAL_USB can only be enabled when Z_FEATURE_UNSTABLE_API is also enabled. Disabling Z_FEATURE_LINK_SERIAL_USB.") + set(Z_FEATURE_LINK_SERIAL_USB 0 CACHE STRING "Toggle Serial USB links" FORCE) +endif() + add_compile_definitions("Z_BUILD_DEBUG=$") message(STATUS "Building with feature confing:\n\ * UNSTABLE_API: ${Z_FEATURE_UNSTABLE_API}\n\ @@ -273,6 +286,7 @@ message(STATUS "Unicast batch max size: ${BATCH_UNICAST_SIZE}") message(STATUS "Multicast batch max size: ${BATCH_MULTICAST_SIZE}") message(STATUS "Build for Zephyr RTOS: ${WITH_ZEPHYR}") message(STATUS "Build for FreeRTOS-Plus-TCP: ${WITH_FREERTOS_PLUS_TCP}") +message(STATUS "Build for Raspberry Pi Pico: ${WITH_RPI_PICO}") message(STATUS "Configuring for ${CMAKE_SYSTEM_NAME}") if(SKBUILD) @@ -334,6 +348,9 @@ if(WITH_ZEPHYR) elseif(WITH_FREERTOS_PLUS_TCP) file (GLOB Sources_Freertos_Plus_TCP "src/system/freertos_plus_tcp/*.c") list(APPEND Sources ${Sources_Freertos_Plus_TCP}) +elseif(WITH_RPI_PICO) + file (GLOB Sources_RPI_Pico "src/system/rpi_pico/*.c") + list(APPEND Sources ${Sources_RPI_Pico}) elseif(CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Darwin" OR CMAKE_SYSTEM_NAME MATCHES "BSD" OR POSIX_COMPATIBLE) file (GLOB Sources_Unix "src/system/unix/*.c" "src/system/unix/link/*.c") list(APPEND Sources ${Sources_Unix}) diff --git a/README.md b/README.md index fed957104..741fc8208 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Currently, zenoh-pico provides support for the following (RT)OSs and protocols: | **OpenCR** | UDP (unicast and multicast), TCP | IPv4 | WiFi | | **Emscripten** | Websocket | IPv4, IPv6 | WiFi, Ethernet | | **FreeRTOS-Plus-TCP** | UDP (unicast), TCP | IPv4 | Ethernet | +| **Raspberry Pi Pico** | UDP (unicast and multicast), TCP | IPv4 | WiFi (for "W" version), Serial, USB (CDC) | Check the website [zenoh.io](http://zenoh.io) and the [roadmap](https://github.com/eclipse-zenoh/roadmap) for more detailed information. @@ -329,6 +330,92 @@ To build and upload the code into the board, run the following command: platformio run -t upload ``` +#### 2.2.6. Raspberry Pi Pico +Note: tested with `Raspberry Pi Pico W` and `Raspberry Pi Pico 2 W` boards + +Ensure your system has the necessary tools and libraries installed. Run the following commands: + +```bash +sudo apt update +sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential g++ libstdc++-arm-none-eabi-newlib +``` + +Set up the Raspberry Pi Pico SDK by cloning the repository: + +```bash +export PICO_SDK_PATH=$HOME/src/pico-sdk +mkdir -p $PICO_SDK_PATH +cd $PICO_SDK_PATH +git clone https://github.com/raspberrypi/pico-sdk.git . +git submodule update --init +``` + +Clone the FreeRTOS Kernel repository for the project: + +```bash +export FREERTOS_KERNEL_PATH=$HOME/src/FreeRTOS-Kernel/ +mkdir -p $FREERTOS_KERNEL_PATH +cd $FREERTOS_KERNEL_PATH +git clone https://github.com/FreeRTOS/FreeRTOS-Kernel.git . +git submodule update --init +``` + +Setup and build the examples: +- `PICO_BOARD` - Pico board type: pico, pico_w, pico2, pico2_w (default: pico_w) +- `WIFI_SSID` - Wi-Fi network SSID +- `WIFI_PASSWORD` - Wi-Fi password +- `ZENOH_CONFIG_MODE` - client or peer mode (default: client) +- `ZENOH_CONFIG_CONNECT` - connect endpoint (only for client mode, optional) +- `ZENOH_CONFIG_LISTEN` - listen endpoint (only for peer mode, optional) + +```bash +cd examples/rpi_pico +cmake -Bbuild -DPICO_BOARD="pico" -DWIFI_SSID=wifi_network_ssid -DWIFI_PASSWORD=wifi_network_password -DZENOH_CONFIG_MODE=[client|peer] -DZENOH_CONFIG_CONNECT=connect -DZENOH_CONFIG_LISTEN=listen +cmake --build ./build +``` + +To flash the Raspberry Pi Pico board, connect it in bootloader mode (it will appear as a removable drive) and copy the generated .uf2 file onto it. + +**Serial connection**: + +To connect via UART specify pins or predefined device name and baud rate: + +e.g. +``` +-DZENOH_CONFIG_CONNECT="serial/0.1#baudrate=38400" +-DZENOH_CONFIG_CONNECT="serial/uart1_0#baudrate=38400" +``` + +Valid PIN combinations and associated device names: + +| **PINS** | **Device name** | +|---------:|:---------------:| +| 0.1 | uart0_0 | +| 4.5 | uart1_0 | +| 8.9 | uart1_1 | +| 12.13 | uart0_1 | +| 16.17 | uart0_2 | + + +**USB Serial connection (experemental)**: + + +To enable this feature, zenoh-pico should be compiled with `Z_FEATURE_LINK_SERIAL_USB` and `Z_FEATURE_UNSTABLE_API` enabled. + +To connect via USB CDC, specify `usb` device: + +e.g. +``` +-DZENOH_CONFIG_CONNECT="serial/usb#baudrate=112500" +``` + +On the host Zenoh, specify the USB CDC device: + +e.g. +``` +zenohd -l serial//dev/ttyACM1#baudrate=112500 +``` + ## 3. Running the Examples The simplest way to run some of the example is to get a Docker image of the **zenoh** router (see [http://zenoh.io/docs/getting-started/quick-test/](http://zenoh.io/docs/getting-started/quick-test/)) and then to run the examples on your machine. diff --git a/examples/rpi_pico/CMakeLists.txt b/examples/rpi_pico/CMakeLists.txt new file mode 100644 index 000000000..8cf1b42e1 --- /dev/null +++ b/examples/rpi_pico/CMakeLists.txt @@ -0,0 +1,163 @@ +cmake_minimum_required(VERSION 3.13) + +set(PICO_BOARD "pico_w" CACHE STRING "Raspberry Pi Pico board: pico, pico_w, pico2, pico2_w") + +set(WIFI_SUPPORT_ENABLED 0) +if(PICO_BOARD STREQUAL "pico_w" OR PICO_BOARD STREQUAL "pico2_w") + set(WIFI_SUPPORT_ENABLED 1) +endif() + +# Set options +set(WIFI_SSID "" CACHE STRING "WiFi SSID") +set(WIFI_PASSWORD "" CACHE STRING "WiFi Password") + +set(ZENOH_CONFIG_MODE "client" CACHE STRING "ZENOH_CONFIG_MODE") +set(ZENOH_CONFIG_CONNECT CACHE STRING "ZENOH_CONFIG_CONNECT") +set(ZENOH_CONFIG_LISTEN CACHE STRING "ZENOH_CONFIG_LISTEN") +option(ZENOH_USB_UART "Enable USB UART" OFF) + +message(STATUS "PICO_BOARD: ${PICO_BOARD}") +message(STATUS "WIFI_SSID: ${WIFI_SSID}") +if(WIFI_PASSWORD STREQUAL "") + message(STATUS "WIFI_PASSWORD is empty") +else() + message(STATUS "WIFI_PASSWORD is set") +endif() + +message(STATUS "ZENOH_CONFIG_MODE: ${ZENOH_CONFIG_MODE}") +message(STATUS "ZENOH_CONFIG_CONNECT: ${ZENOH_CONFIG_CONNECT}") +message(STATUS "ZENOH_CONFIG_LISTEN: ${ZENOH_CONFIG_LISTEN}") +message(STATUS "ZENOH_USB_UART: ${ZENOH_USB_UART}") + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in" + "${CMAKE_CURRENT_SOURCE_DIR}/include/config.h" +) + +set(CMAKE_C_STANDARD 11) + +# Include Raspberry Pi Pico SDK +if(NOT DEFINED ENV{PICO_SDK_PATH}) + message(FATAL_ERROR "PICO_SDK_PATH environment variable is not set. Please set it to the location of the Pico SDK.") +endif() +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) + +# Include FreeRTOS SDK +include(FreeRTOS_Kernel_import.cmake) + +# Configure project +project(zenohpico_rpi_pico_examples C CXX ASM) + +if(WIFI_SUPPORT_ENABLED) + set(WIFI_LIB pico_cyw43_arch_lwip_sys_freertos) +else() + set(WIFI_LIB "") +endif() + +if(ZENOH_USB_UART) + set(USB_LIBS + tinyusb_host + tinyusb_device + tinyusb_board + ) + set(USB_INCLUDE + ${CMAKE_CURRENT_LIST_DIR}/include/tusb + ) +else() + set(USB_LIBS "") + set(USB_INCLUDE "") +endif() + +add_compile_definitions(LWIP_TIMEVAL_PRIVATE=0) +pico_sdk_init() + +# Include Zenoh Pico library +include(../../cmake/helpers.cmake) +set_default_build_type(Release) + +if (NOT WIFI_SUPPORT_ENABLED) + declare_cache_var(Z_FEATURE_LINK_TCP 0 STRING "TCP support") + declare_cache_var(Z_FEATURE_LINK_UDP_MULTICAST 0 STRING "UDP multicast support") + declare_cache_var(Z_FEATURE_LINK_UDP_UNICAST 0 STRING "UDP unicast support") + declare_cache_var(Z_FEATURE_SCOUTING_UDP 0 STRING "Scouting support") +endif() + +declare_cache_var(Z_FEATURE_LINK_SERIAL 1 STRING "Serial support") +if(ZENOH_USB_UART) + declare_cache_var(Z_FEATURE_LINK_SERIAL_USB 1 STRING "Serial USB support") +else() + declare_cache_var(Z_FEATURE_LINK_SERIAL_USB 0 STRING "Serial USB support") +endif() + +set(BUILD_SHARED_LIBS OFF) +set(WITH_RPI_PICO ON) + +configure_include_project(ZENOHPICO zenohpico zenohpico::lib "../.." zenohpico "https://github.com/eclipse-zenoh/zenoh-pico" "") + +target_link_libraries(zenohpico_static + hardware_uart + pico_stdlib + pico_rand + FreeRTOS-Kernel-Heap4 + ${WIFI_LIB} + ${USB_LIBS} +) + +# Configure build +target_include_directories(zenohpico_static PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/include + ${USB_INCLUDE} +) + +add_library(main STATIC main.c) + +target_include_directories(main PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/include +) +target_link_libraries(main + pico_stdlib + pico_rand + FreeRTOS-Kernel-Heap4 + ${WIFI_LIB} +) + +function(add_example name) + add_executable(${name} ${name}.c) + target_link_libraries(${name} + main + hardware_uart + pico_stdlib + pico_rand + FreeRTOS-Kernel-Heap4 + zenohpico::lib + ${WIFI_LIB} + ${USB_LIBS} + ) + target_include_directories(${name} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/include + ${USB_INCLUDE} + ) + if(ZENOH_USB_UART) + pico_enable_stdio_uart(${name} 1) + pico_enable_stdio_usb(${name} 0) + else() + pico_enable_stdio_uart(${name} 0) + pico_enable_stdio_usb(${name} 1) + endif() + pico_add_extra_outputs(${name}) +endfunction() + +#add_example(z_get) +add_example(z_pub) +#add_example(z_pub_st) +#add_example(z_pub_thr) +#add_example(z_pull) +#add_example(z_put) +#add_example(z_queryable) +#add_example(z_scout) +#add_example(z_sub) +#add_example(z_sub_st) +#add_example(z_sub_thr) diff --git a/examples/rpi_pico/FreeRTOS_Kernel_import.cmake b/examples/rpi_pico/FreeRTOS_Kernel_import.cmake new file mode 100644 index 000000000..15d8e7724 --- /dev/null +++ b/examples/rpi_pico/FreeRTOS_Kernel_import.cmake @@ -0,0 +1,91 @@ +# This is a copy of /portable/ThirdParty/GCC/RP2040/FREERTOS_KERNEL_import.cmake + +# This can be dropped into an external project to help locate the FreeRTOS kernel +# It should be include()ed prior to project(). Alternatively this file may +# or the CMakeLists.txt in this directory may be included or added via add_subdirectory +# respectively. + +if (DEFINED ENV{FREERTOS_KERNEL_PATH} AND (NOT FREERTOS_KERNEL_PATH)) + set(FREERTOS_KERNEL_PATH $ENV{FREERTOS_KERNEL_PATH}) + message("Using FREERTOS_KERNEL_PATH from environment ('${FREERTOS_KERNEL_PATH}')") +endif () + +# first pass we look in old tree; second pass we look in new tree +foreach(SEARCH_PASS RANGE 0 1) + if (SEARCH_PASS) + # ports may be moving to submodule in the future + set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "portable/ThirdParty/Community-Supported-Ports/GCC") + set(FREERTOS_KERNEL_RP2040_BACK_PATH "../../../../..") + else() + set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "portable/ThirdParty/GCC") + set(FREERTOS_KERNEL_RP2040_BACK_PATH "../../../..") + endif() + + if(PICO_PLATFORM STREQUAL "rp2040") + set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2040") + else() + if (PICO_PLATFORM STREQUAL "rp2350-riscv") + set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2350_RISC-V") + else() + set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2350_ARM_NTZ") + endif() + endif() + + if (NOT FREERTOS_KERNEL_PATH) + # check if we are inside the FreeRTOS kernel tree (i.e. this file has been included directly) + get_filename_component(_ACTUAL_PATH ${CMAKE_CURRENT_LIST_DIR} REALPATH) + get_filename_component(_POSSIBLE_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH} REALPATH) + if (_ACTUAL_PATH STREQUAL _POSSIBLE_PATH) + get_filename_component(FREERTOS_KERNEL_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH} REALPATH) + endif() + if (_ACTUAL_PATH STREQUAL _POSSIBLE_PATH) + get_filename_component(FREERTOS_KERNEL_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH} REALPATH) + message("Setting FREERTOS_KERNEL_PATH to ${FREERTOS_KERNEL_PATH} based on location of FreeRTOS-Kernel-import.cmake") + break() + elseif (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../FreeRTOS-Kernel") + set(FREERTOS_KERNEL_PATH ${PICO_SDK_PATH}/../FreeRTOS-Kernel) + message("Defaulting FREERTOS_KERNEL_PATH as sibling of PICO_SDK_PATH: ${FREERTOS_KERNEL_PATH}") + break() + endif() + endif () + + if (NOT FREERTOS_KERNEL_PATH) + foreach(POSSIBLE_SUFFIX Source FreeRTOS-Kernel FreeRTOS/Source) + # check if FreeRTOS-Kernel exists under directory that included us + set(SEARCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) + get_filename_component(_POSSIBLE_PATH ${SEARCH_ROOT}/${POSSIBLE_SUFFIX} REALPATH) + if (EXISTS ${_POSSIBLE_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/CMakeLists.txt) + get_filename_component(FREERTOS_KERNEL_PATH ${_POSSIBLE_PATH} REALPATH) + message("Setting FREERTOS_KERNEL_PATH to '${FREERTOS_KERNEL_PATH}' found relative to enclosing project") + break() + endif() + endforeach() + if (FREERTOS_KERNEL_PATH) + break() + endif() + endif() + + # user must have specified + if (FREERTOS_KERNEL_PATH) + if (EXISTS "${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}") + break() + endif() + endif() +endforeach () + +if (NOT FREERTOS_KERNEL_PATH) + message(FATAL_ERROR "FreeRTOS location was not specified. Please set FREERTOS_KERNEL_PATH.") +endif() + +set(FREERTOS_KERNEL_PATH "${FREERTOS_KERNEL_PATH}" CACHE PATH "Path to the FreeRTOS Kernel") + +get_filename_component(FREERTOS_KERNEL_PATH "${FREERTOS_KERNEL_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${FREERTOS_KERNEL_PATH}) + message(FATAL_ERROR "Directory '${FREERTOS_KERNEL_PATH}' not found") +endif() +if (NOT EXISTS ${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/CMakeLists.txt) + message(FATAL_ERROR "Directory '${FREERTOS_KERNEL_PATH}' does not contain a '${PICO_PLATFORM}' port here: ${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}") +endif() +set(FREERTOS_KERNEL_PATH ${FREERTOS_KERNEL_PATH} CACHE PATH "Path to the FreeRTOS_KERNEL" FORCE) + +add_subdirectory(${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH} FREERTOS_KERNEL) diff --git a/examples/rpi_pico/include/FreeRTOSConfig.h b/examples/rpi_pico/include/FreeRTOSConfig.h new file mode 100644 index 000000000..8051ecb01 --- /dev/null +++ b/examples/rpi_pico/include/FreeRTOSConfig.h @@ -0,0 +1,155 @@ +/* + * FreeRTOS V202111.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * 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. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +#ifndef FREERTOS_CONFIG_EXAMPLES_COMMON_H +#define FREERTOS_CONFIG_EXAMPLES_COMMON_H + +/*----------------------------------------------------------- + * Application specific definitions. + * + * These definitions should be adjusted for your particular hardware and + * application requirements. + * + * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE + * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. + * + * See http://www.freertos.org/a00110.html + *----------------------------------------------------------*/ + +/* Scheduler Related */ +#define configUSE_PREEMPTION 1 +#define configUSE_TICKLESS_IDLE 0 +#define configUSE_IDLE_HOOK 0 +#define configUSE_TICK_HOOK 0 +#define configTICK_RATE_HZ ((TickType_t)1000) +#define configMAX_PRIORITIES 32 +#define configMINIMAL_STACK_SIZE (configSTACK_DEPTH_TYPE)512 +#define configUSE_16_BIT_TICKS 0 + +#define configIDLE_SHOULD_YIELD 1 + +/* Synchronization Related */ +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_APPLICATION_TASK_TAG 0 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configQUEUE_REGISTRY_SIZE 8 +#define configUSE_QUEUE_SETS 1 +#define configUSE_TIME_SLICING 1 +#define configUSE_NEWLIB_REENTRANT 0 +// todo need this for lwip FreeRTOS sys_arch to compile +#define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 + +/* System */ +#define configSTACK_DEPTH_TYPE uint32_t +#define configMESSAGE_BUFFER_LENGTH_TYPE size_t + +/* Memory allocation related definitions. */ +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configTOTAL_HEAP_SIZE (128 * 1024) +#define configAPPLICATION_ALLOCATED_HEAP 0 + +/* Hook function related definitions. */ +#define configCHECK_FOR_STACK_OVERFLOW 0 +#define configUSE_MALLOC_FAILED_HOOK 0 +#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 + +/* Run time and task stats gathering related definitions. */ +#define configGENERATE_RUN_TIME_STATS 0 +#define configUSE_TRACE_FACILITY 1 +#define configUSE_STATS_FORMATTING_FUNCTIONS 0 + +/* Co-routine related definitions. */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES 1 + +/* Software timer related definitions. */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH 1024 + +/* Interrupt nesting behaviour configuration. */ +/* +#define configKERNEL_INTERRUPT_PRIORITY [dependent of processor] +#define configMAX_SYSCALL_INTERRUPT_PRIORITY [dependent on processor and application] +#define configMAX_API_CALL_INTERRUPT_PRIORITY [dependent on processor and application] +*/ + +#define configNUMBER_OF_CORES 2 +/* SMP (configNUMBER_OF_CORES > 1) only */ +#define configTICK_CORE 0 +#define configRUN_MULTIPLE_PRIORITIES 1 +#if configNUMBER_OF_CORES > 1 +#define configUSE_CORE_AFFINITY 1 +#endif +#define configUSE_PASSIVE_IDLE_HOOK 0 + +/* Armv8-M */ + +/* Not currently supported */ +#define configENABLE_MPU 0 +// #define configSYSTEM_CALL_STACK_SIZE ( configSTACK_DEPTH_TYPE ) 512 +#define configENABLE_FPU 1 +/* Not currently supported */ +#define configENABLE_TRUSTZONE 0 +#define configRUN_FREERTOS_SECURE_ONLY 1 +// see https://www.freertos.org/RTOS-Cortex-M3-M4.html +#define configMAX_SYSCALL_INTERRUPT_PRIORITY 16 + +/* RP2xxx specific */ +#define configSUPPORT_PICO_SYNC_INTEROP 1 +#define configSUPPORT_PICO_TIME_INTEROP 1 + +#include +/* Define to trap errors during development. */ +#define configASSERT(x) assert(x) + +/* Set the following definitions to 1 to include the API function, or zero +to exclude the API function. */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskAbortDelay 1 +#define INCLUDE_xTaskGetHandle 1 +#define INCLUDE_xTaskResumeFromISR 1 +#define INCLUDE_xQueueGetMutexHolder 1 + +/* A header file that defines trace macro can be included here. */ + +#endif /* FREERTOS_CONFIG_H */ diff --git a/examples/rpi_pico/include/config.h.in b/examples/rpi_pico/include/config.h.in new file mode 100644 index 000000000..37c150d13 --- /dev/null +++ b/examples/rpi_pico/include/config.h.in @@ -0,0 +1,11 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define WIFI_SUPPORT_ENABLED @WIFI_SUPPORT_ENABLED@ +#define WIFI_SSID "@WIFI_SSID@" +#define WIFI_PASSWORD "@WIFI_PASSWORD@" +#define ZENOH_CONFIG_MODE "@ZENOH_CONFIG_MODE@" +#define ZENOH_CONFIG_CONNECT "@ZENOH_CONFIG_CONNECT@" +#define ZENOH_CONFIG_LISTEN "@ZENOH_CONFIG_LISTEN@" + +#endif // CONFIG_H diff --git a/examples/rpi_pico/include/lwipopts.h b/examples/rpi_pico/include/lwipopts.h new file mode 100644 index 000000000..7ff74f115 --- /dev/null +++ b/examples/rpi_pico/include/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) + +#define NO_SYS 0 +#define LWIP_SOCKET 1 +#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 +#define MEM_SIZE 4000 +#define MEMP_NUM_TCP_SEG 64 +#define MEMP_NUM_ARP_QUEUE 20 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (16 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (16 * 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_IGMP 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 +#define LWIP_SOCKET_POLL 0 +#define LWIP_MULTICAST_TX_OPTIONS 1 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#define LWIP_FREERTOS_CHECK_CORE_LOCKING 1 +#define LWIP_TCPIP_CORE_LOCKING 1 +#define LWIP_TCPIP_CORE_LOCKING_INPUT 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 + +#define TCPIP_THREAD_STACKSIZE 2048 +#define DEFAULT_THREAD_STACKSIZE 2048 +#define DEFAULT_RAW_RECVMBOX_SIZE 16 +#define DEFAULT_UDP_RECVMBOX_SIZE 16 +#define DEFAULT_TCP_RECVMBOX_SIZE 16 +#define TCPIP_MBOX_SIZE 16 +#define LWIP_TIMEVAL_PRIVATE 0 + +#define LWIP_SO_RCVTIMEO 1 +#define LWIP_SO_SNDTIMEO 1 +#define LWIP_SO_LINGER 1 + +#endif /* __LWIPOPTS_H__ */ diff --git a/examples/rpi_pico/include/tusb/tusb_config.h b/examples/rpi_pico/include/tusb/tusb_config.h new file mode 100644 index 000000000..7cfefab94 --- /dev/null +++ b/examples/rpi_pico/include/tusb/tusb_config.h @@ -0,0 +1,111 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 2 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// CDC Endpoint transfer buffer size, more is faster +#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/rpi_pico/main.c b/examples/rpi_pico/main.c new file mode 100644 index 000000000..5184f6b91 --- /dev/null +++ b/examples/rpi_pico/main.c @@ -0,0 +1,89 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include + +#include "FreeRTOS.h" +#include "config.h" + +#if WIFI_SUPPORT_ENABLED +#include "pico/cyw43_arch.h" +#endif + +#include "pico/stdlib.h" +#include "task.h" + +#define TASK_PRIORITY (tskIDLE_PRIORITY + 2UL) +#define WIFI_TIMEOUT 30000 + +int app_main(); + +#if WIFI_SUPPORT_ENABLED +void print_ip_address() { + struct netif *netif = &cyw43_state.netif[CYW43_ITF_STA]; + if (netif_is_up(netif)) { + printf("IP Address: %s\n", ip4addr_ntoa(netif_ip4_addr(netif))); + printf("Netmask: %s\n", ip4addr_ntoa(netif_ip4_netmask(netif))); + printf("Gateway: %s\n", ip4addr_ntoa(netif_ip4_gw(netif))); + } else { + printf("Network interface is down.\n"); + } +} +#endif + +void main_task(void *params) { + (void)params; + vTaskDelay(pdMS_TO_TICKS(3000)); +#if WIFI_SUPPORT_ENABLED + if (cyw43_arch_init()) { + printf("Failed to initialise\n"); + return; + } + + if (strlen(WIFI_SSID) != 0) { + cyw43_arch_enable_sta_mode(); + printf("Connecting to Wi-Fi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, WIFI_TIMEOUT) == 0) { + printf("Wi-Fi connected.\n"); + print_ip_address(); + app_main(); + } else { + printf("Failed to connect Wi-Fi\n"); + } + } else { + printf("Offline mode\n"); + app_main(); + } +#else + app_main(); +#endif + + printf("Terminate.\n"); + +#if WIFI_SUPPORT_ENABLED + cyw43_arch_deinit(); +#endif + + vTaskDelete(NULL); +} + +int main(void) { + stdio_init_all(); + + xTaskCreate(main_task, "MainThread", configMINIMAL_STACK_SIZE * 16, NULL, TASK_PRIORITY, NULL); + + vTaskStartScheduler(); + + return 0; +} diff --git a/examples/rpi_pico/z_get.c b/examples/rpi_pico/z_get.c new file mode 100644 index 000000000..db0d85473 --- /dev/null +++ b/examples/rpi_pico/z_get.c @@ -0,0 +1,105 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_QUERY == 1 + +#define KEYEXPR "demo/example/**" +#define VALUE "" + +void reply_dropper(void *ctx) { + (void)(ctx); + printf(">> Received query final notification\n"); +} + +void reply_handler(z_loaned_reply_t *reply, void *ctx) { + (void)(ctx); + if (z_reply_is_ok(reply)) { + const z_loaned_sample_t *sample = z_reply_ok(reply); + z_view_string_t keystr; + z_keyexpr_as_view_string(z_sample_keyexpr(sample), &keystr); + z_owned_string_t replystr; + z_bytes_to_string(z_sample_payload(sample), &replystr); + + printf(">> Received ('%.*s': '%.*s')\n", (int)z_string_len(z_loan(keystr)), z_string_data(z_loan(keystr)), + (int)z_string_len(z_loan(replystr)), z_string_data(z_loan(replystr))); + z_drop(z_move(replystr)); + } else { + printf(">> Received an error\n"); + } +} + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + + z_view_keyexpr_t ke; + if (z_view_keyexpr_from_str(&ke, KEYEXPR) < 0) { + printf("%s is not a valid key expression", KEYEXPR); + return; + } + + while (1) { + z_sleep_s(5); + printf("Sending Query '%s'...\n", KEYEXPR); + z_get_options_t opts; + z_get_options_default(&opts); + // Value encoding + z_owned_bytes_t payload; + if (strcmp(VALUE, "") != 0) { + z_bytes_from_static_str(&payload, VALUE); + opts.payload = z_move(payload); + } + z_owned_closure_reply_t callback; + z_closure(&callback, reply_handler, reply_dropper, NULL); + if (z_get(z_loan(s), z_loan(ke), "", z_move(callback), &opts) < 0) { + printf("Unable to send query.\n"); + return; + } + } + + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_QUERY but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_pub.c b/examples/rpi_pico/z_pub.c new file mode 100644 index 000000000..9ffcbcfe1 --- /dev/null +++ b/examples/rpi_pico/z_pub.c @@ -0,0 +1,85 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_PUBLICATION == 1 + +#define KEYEXPR "demo/example/zenoh-pico-pub" +#define VALUE "[RPI] Pub from Zenoh-Pico!" + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + + printf("Declaring publisher for '%s'...\n", KEYEXPR); + z_owned_publisher_t pub; + z_view_keyexpr_t ke; + z_view_keyexpr_from_str_unchecked(&ke, KEYEXPR); + if (z_declare_publisher(z_loan(s), &pub, z_loan(ke), NULL) < 0) { + printf("Unable to declare publisher for key expression!\n"); + return; + } + + // Publish data + char buf[256]; + for (int idx = 0; 1; ++idx) { + z_sleep_s(1); + snprintf(buf, 256, "[%4d] %s", idx, VALUE); + printf("Putting Data ('%s': '%s')...\n", KEYEXPR, buf); + + // Create payload + z_owned_bytes_t payload; + z_bytes_copy_from_str(&payload, buf); + + z_publisher_put_options_t options; + z_publisher_put_options_default(&options); + z_publisher_put(z_loan(pub), z_move(payload), &options); + } + + // Clean-up + z_drop(z_move(pub)); + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_PUBLICATION but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_pub_st.c b/examples/rpi_pico/z_pub_st.c new file mode 100644 index 000000000..83233da5e --- /dev/null +++ b/examples/rpi_pico/z_pub_st.c @@ -0,0 +1,84 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_PUBLICATION == 1 + +#define KEYEXPR "demo/example/zenoh-pico-pub" +#define VALUE "[RPI] Pub from Zenoh-Pico!" + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + printf("Declaring publisher for '%s'...\n", KEYEXPR); + z_owned_publisher_t pub; + z_view_keyexpr_t ke; + z_view_keyexpr_from_str_unchecked(&ke, KEYEXPR); + if (z_declare_publisher(z_loan(s), &pub, z_loan(ke), NULL) < 0) { + printf("Unable to declare publisher for key expression!\n"); + return; + } + + char *buf = (char *)pvPortMalloc(256); + z_clock_t now = z_clock_now(); + for (int idx = 0;; ++idx) { + if (z_clock_elapsed_ms(&now) > 1000) { + snprintf(buf, 256, "[%4d] %s", idx, VALUE); + printf("Putting Data ('%s': '%s')...\n", KEYEXPR, buf); + + // Create payload + z_owned_bytes_t payload; + z_bytes_copy_from_str(&payload, buf); + + z_publisher_put(z_loan(pub), z_move(payload), NULL); + ++idx; + + now = z_clock_now(); + } + + zp_read(z_loan(s), NULL); + zp_send_keep_alive(z_loan(s), NULL); + zp_send_join(z_loan(s), NULL); + } + + z_drop(z_move(pub)); + + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_PUBLICATION but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_pub_thr.c b/examples/rpi_pico/z_pub_thr.c new file mode 100644 index 000000000..29882d432 --- /dev/null +++ b/examples/rpi_pico/z_pub_thr.c @@ -0,0 +1,90 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_PUBLICATION == 1 + +#define KEYEXPR "test/thr" +#define PAYLOAD_SIZE 8 + +void z_free_with_context(void *ptr, void *context) { + (void)context; + z_free(ptr); +} + +void app_main(void) { + uint8_t *value = (uint8_t *)z_malloc(PAYLOAD_SIZE); + memset(value, 1, PAYLOAD_SIZE); + + // Set config + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + + // Declare publisher + z_owned_publisher_t pub; + z_view_keyexpr_t ke; + z_view_keyexpr_from_str(&ke, KEYEXPR); + if (z_declare_publisher(z_loan(s), &pub, z_loan(ke), NULL) < 0) { + printf("Unable to declare publisher for key expression!\n"); + return; + } + + printf("Send packets\n"); + z_owned_bytes_t payload; + z_bytes_from_buf(&payload, value, PAYLOAD_SIZE, z_free_with_context, NULL); + while (1) { + // Clone payload + z_owned_bytes_t p; + z_bytes_clone(&p, z_loan(payload)); + z_publisher_put(z_loan(pub), z_move(p), NULL); + } + + // Clean-up + z_drop(z_move(pub)); + z_drop(z_move(s)); + z_drop(z_move(payload)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_PUBLICATION but this example requires it.\n"); + return -2; +} +#endif diff --git a/examples/rpi_pico/z_pull.c b/examples/rpi_pico/z_pull.c new file mode 100644 index 000000000..175815e0d --- /dev/null +++ b/examples/rpi_pico/z_pull.c @@ -0,0 +1,97 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_SUBSCRIPTION == 1 + +#define KEYEXPR "demo/example/**" + +const size_t INTERVAL = 5000; +const size_t SIZE = 3; + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + + printf("Declaring Subscriber on '%s'...\n", KEYEXPR); + z_owned_closure_sample_t closure; + z_owned_ring_handler_sample_t handler; + z_ring_channel_sample_new(&closure, &handler, SIZE); + z_owned_subscriber_t sub; + z_view_keyexpr_t ke; + z_view_keyexpr_from_str(&ke, KEYEXPR); + if (z_declare_subscriber(z_loan(s), &sub, z_loan(ke), z_move(closure), NULL) < 0) { + printf("Unable to declare subscriber.\n"); + return; + } + + printf("Pulling data every %zu ms... Ring size: %zd\n", INTERVAL, SIZE); + z_owned_sample_t sample; + while (true) { + z_result_t res; + for (res = z_try_recv(z_loan(handler), &sample); res == Z_OK; res = z_try_recv(z_loan(handler), &sample)) { + z_view_string_t keystr; + z_keyexpr_as_view_string(z_sample_keyexpr(z_loan(sample)), &keystr); + z_owned_string_t value; + z_bytes_to_string(z_sample_payload(z_loan(sample)), &value); + printf(">> [Subscriber] Pulled ('%.*s': '%.*s')\n", (int)z_string_len(z_loan(keystr)), + z_string_data(z_loan(keystr)), (int)z_string_len(z_loan(value)), z_string_data(z_loan(value))); + z_drop(z_move(value)); + z_drop(z_move(sample)); + } + if (res == Z_CHANNEL_NODATA) { + printf(">> [Subscriber] Nothing to pull... sleep for %zu ms\n", INTERVAL); + z_sleep_ms(INTERVAL); + } else { + break; + } + } + + z_drop(z_move(sub)); + z_drop(z_move(handler)); + + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_PUBLICATION but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_put.c b/examples/rpi_pico/z_put.c new file mode 100644 index 000000000..bff22a1e8 --- /dev/null +++ b/examples/rpi_pico/z_put.c @@ -0,0 +1,86 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_PUBLICATION == 1 + +#define KEYEXPR "demo/example/zenoh-pico-pub" +#define VALUE "[RPI] Pub from Zenoh-Pico!" + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + + printf("Declaring key expression '%s'...\n", KEYEXPR); + z_owned_keyexpr_t ke; + z_view_keyexpr_t vke; + z_view_keyexpr_from_str_unchecked(&vke, KEYEXPR); + if (z_declare_keyexpr(z_loan(s), &ke, z_loan(vke)) < 0) { + printf("Unable to declare key expression!\n"); + z_drop(z_move(s)); + return; + } + + printf("Putting Data ('%s': '%s')...\n", KEYEXPR, VALUE); + z_put_options_t options; + z_put_options_default(&options); + + // Create payload + z_owned_bytes_t payload; + z_bytes_from_static_str(&payload, VALUE); + + if (z_put(z_loan(s), z_loan(ke), z_move(payload), &options) < 0) { + printf("Oh no! Put has failed...\n"); + } + + while (1) { + z_sleep_s(1); + } + + // Clean up + z_undeclare_keyexpr(z_loan(s), z_move(ke)); + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_PUBLICATION but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_queryable.c b/examples/rpi_pico/z_queryable.c new file mode 100644 index 000000000..dc36f2403 --- /dev/null +++ b/examples/rpi_pico/z_queryable.c @@ -0,0 +1,105 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_QUERYABLE == 1 + +#define KEYEXPR "demo/example/zenoh-pico-queryable" +#define VALUE "[RPI] Queryable from Zenoh-Pico!" + +void query_handler(z_loaned_query_t *query, void *ctx) { + (void)(ctx); + z_view_string_t keystr; + z_keyexpr_as_view_string(z_query_keyexpr(query), &keystr); + z_view_string_t params; + z_query_parameters(query, ¶ms); + printf(" >> [Queryable handler] Received Query '%.*s%.*s'\n", (int)z_string_len(z_loan(keystr)), + z_string_data(z_loan(keystr)), (int)z_string_len(z_loan(params)), z_string_data(z_loan(params))); + // Process value + z_owned_string_t payload_string; + z_bytes_to_string(z_query_payload(query), &payload_string); + if (z_string_len(z_loan(payload_string)) > 0) { + printf(" with value '%.*s'\n", (int)z_string_len(z_loan(payload_string)), + z_string_data(z_loan(payload_string))); + } + z_drop(z_move(payload_string)); + + z_query_reply_options_t options; + z_query_reply_options_default(&options); + // Reply value encoding + z_owned_bytes_t reply_payload; + z_bytes_from_static_str(&reply_payload, VALUE); + + z_query_reply(query, z_query_keyexpr(query), z_move(reply_payload), &options); +} + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + + z_view_keyexpr_t ke; + if (z_view_keyexpr_from_str(&ke, KEYEXPR) < 0) { + printf("%s is not a valid key expression", KEYEXPR); + return; + } + + printf("Creating Queryable on '%s'...\n", KEYEXPR); + z_owned_closure_query_t callback; + z_closure(&callback, query_handler, NULL, NULL); + z_owned_queryable_t qable; + if (z_declare_queryable(z_loan(s), &qable, z_loan(ke), z_move(callback), NULL) < 0) { + printf("Unable to create queryable.\n"); + return; + } + + while (1) { + z_sleep_s(1); + } + + z_drop(z_move(qable)); + + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_QUERYABLE but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_scout.c b/examples/rpi_pico/z_scout.c new file mode 100644 index 000000000..01ce5e935 --- /dev/null +++ b/examples/rpi_pico/z_scout.c @@ -0,0 +1,94 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include +#include +#include +#include + +#include "config.h" + +void fprintzid(FILE *stream, z_id_t zid) { + unsigned int zidlen = _z_id_len(zid); + if (zidlen == 0) { + fprintf(stream, "None"); + } else { + fprintf(stream, "Some("); + for (unsigned int i = 0; i < zidlen; i++) { + fprintf(stream, "%02X", (int)zid.id[i]); + } + fprintf(stream, ")"); + } +} + +void fprintwhatami(FILE *stream, z_whatami_t whatami) { + z_view_string_t s; + z_whatami_to_view_string(whatami, &s); + fprintf(stream, "\"%.*s\"", (int)z_string_len(z_loan(s)), z_string_data(z_loan(s))); +} + +void fprintlocators(FILE *stream, const z_loaned_string_array_t *locs) { + fprintf(stream, "["); + for (unsigned int i = 0; i < z_string_array_len(locs); i++) { + fprintf(stream, "\""); + const z_loaned_string_t *str = z_string_array_get(locs, i); + fprintf(stream, "%.*s", (int)z_string_len(str), z_string_data(str)); + fprintf(stream, "\""); + if (i < z_string_array_len(locs) - 1) { + fprintf(stream, ", "); + } + } + fprintf(stream, "]"); +} + +void fprinthello(FILE *stream, const z_loaned_hello_t *hello) { + fprintf(stream, "Hello { zid: "); + fprintzid(stream, z_hello_zid(hello)); + fprintf(stream, ", whatami: "); + fprintwhatami(stream, z_hello_whatami(hello)); + fprintf(stream, ", locators: "); + fprintlocators(stream, zp_hello_locators(hello)); + fprintf(stream, " }"); +} + +void callback(z_loaned_hello_t *hello, void *context) { + fprinthello(stdout, hello); + fprintf(stdout, "\n"); + (*(int *)context)++; +} + +void drop(void *context) { + int count = *(int *)context; + z_free(context); + if (!count) { + printf("Did not find any zenoh process.\n"); + } else { + printf("Dropping scout results.\n"); + } +} + +void app_main(void) { + int *context = (int *)z_malloc(sizeof(int)); + *context = 0; + z_owned_config_t config; + z_config_default(&config); + z_owned_closure_hello_t closure; + z_closure(&closure, callback, drop, context); + printf("Scouting...\n"); + z_scout(z_move(config), z_move(closure), NULL); + while (1) { + z_sleep_s(1); + } +} diff --git a/examples/rpi_pico/z_sub.c b/examples/rpi_pico/z_sub.c new file mode 100644 index 000000000..ea891a628 --- /dev/null +++ b/examples/rpi_pico/z_sub.c @@ -0,0 +1,85 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_SUBSCRIPTION == 1 + +#define KEYEXPR "demo/example/**" + +void data_handler(z_loaned_sample_t *sample, void *ctx) { + (void)(ctx); + z_view_string_t keystr; + z_keyexpr_as_view_string(z_sample_keyexpr(sample), &keystr); + z_owned_string_t value; + z_bytes_to_string(z_sample_payload(sample), &value); + printf(">> [Subscriber] Received ('%.*s': '%.*s')\n", (int)z_string_len(z_loan(keystr)), + z_string_data(z_loan(keystr)), (int)z_string_len(z_loan(value)), z_string_data(z_loan(value))); + z_drop(z_move(value)); +} + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + + z_owned_closure_sample_t callback; + z_closure(&callback, data_handler, NULL, NULL); + printf("Declaring Subscriber on '%s'...\n", KEYEXPR); + z_view_keyexpr_t ke; + z_view_keyexpr_from_str_unchecked(&ke, KEYEXPR); + z_owned_subscriber_t sub; + if (z_declare_subscriber(z_loan(s), &sub, z_loan(ke), z_move(callback), NULL) < 0) { + printf("Unable to declare subscriber.\n"); + return; + } + + while (1) { + z_sleep_s(1); + } + + // Clean-up + z_drop(z_move(sub)); + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_SUBSCRIPTION but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_sub_st.c b/examples/rpi_pico/z_sub_st.c new file mode 100644 index 000000000..aba1da9b2 --- /dev/null +++ b/examples/rpi_pico/z_sub_st.c @@ -0,0 +1,82 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include + +#include "config.h" + +#if Z_FEATURE_SUBSCRIPTION == 1 + +#define KEYEXPR "demo/example/zenoh-pico-pub" +#define VALUE "[RPI] Pub from Zenoh-Pico!" +int msg_nb = 0; + +void data_handler(z_loaned_sample_t *sample, void *ctx) { + (void)(ctx); + z_view_string_t keystr; + z_keyexpr_as_view_string(z_sample_keyexpr(sample), &keystr); + z_owned_string_t value; + z_bytes_to_string(z_sample_payload(sample), &value); + printf(">> [Subscriber] Received ('%.*s': '%.*s')\n", (int)z_string_len(z_loan(keystr)), + z_string_data(z_loan(keystr)), (int)z_string_len(z_loan(value)), z_string_data(z_loan(value))); + z_drop(z_move(value)); + msg_nb++; +} + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + z_owned_closure_sample_t callback; + z_closure(&callback, data_handler, NULL, NULL); + printf("Declaring Subscriber on '%s'...\n", KEYEXPR); + z_view_keyexpr_t ke; + z_view_keyexpr_from_str_unchecked(&ke, KEYEXPR); + z_owned_subscriber_t sub; + if (z_declare_subscriber(z_loan(s), &sub, z_loan(ke), z_move(callback), NULL) < 0) { + printf("Unable to declare subscriber.\n"); + return; + } + + while (true) { + zp_read(z_loan(s), NULL); + zp_send_keep_alive(z_loan(s), NULL); + zp_send_join(z_loan(s), NULL); + } + + z_drop(z_move(sub)); + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_SUBSCRIPTION but this example requires it.\n"); +} +#endif diff --git a/examples/rpi_pico/z_sub_thr.c b/examples/rpi_pico/z_sub_thr.c new file mode 100644 index 000000000..f3e4d09b6 --- /dev/null +++ b/examples/rpi_pico/z_sub_thr.c @@ -0,0 +1,121 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#if Z_FEATURE_SUBSCRIPTION == 1 + +#define KEYEXPR "test/thr" + +#define PACKET_NB 1000000 + +typedef struct { + volatile unsigned long count; + volatile unsigned long finished_rounds; + z_clock_t start; + z_clock_t first_start; +} z_stats_t; + +z_stats_t *z_stats_make(void) { + z_stats_t *stats = malloc(sizeof(z_stats_t)); + stats->count = 0; + stats->finished_rounds = 0; + stats->first_start.tv_nsec = 0; + return stats; +} + +void on_sample(z_loaned_sample_t *sample, void *context) { + (void)sample; + z_stats_t *stats = (z_stats_t *)context; + stats->count++; + // Start set measurement + if (stats->count == 1) { + stats->start = z_clock_now(); + if (stats->first_start.tv_nsec == 0) { + stats->first_start = stats->start; + } + } else if (stats->count >= PACKET_NB) { + // Stop set measurement + stats->finished_rounds++; + unsigned long elapsed_ms = z_clock_elapsed_ms(&stats->start); + printf("%f msg/s\n", (double)(PACKET_NB * 1000) / (double)elapsed_ms); + stats->count = 0; + } +} + +void drop_stats(void *context) { + z_stats_t *stats = (z_stats_t *)context; + unsigned long elapsed_ms = z_clock_elapsed_ms(&stats->first_start); + const unsigned long sent_messages = PACKET_NB * stats->finished_rounds + stats->count; + printf("Stats after unsubscribing: received %ld messages over %lu miliseconds (%.1f msg/s)\n", sent_messages, + elapsed_ms, (double)(sent_messages * 1000) / (double)elapsed_ms); + free(context); +} + +void app_main(void) { + z_owned_config_t config; + z_config_default(&config); + zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, ZENOH_CONFIG_MODE); + if (strcmp(ZENOH_CONFIG_CONNECT, "") != 0) { + printf("Connect endpoint: %s\n", ZENOH_CONFIG_CONNECT); + zp_config_insert(z_loan_mut(config), Z_CONFIG_CONNECT_KEY, ZENOH_CONFIG_CONNECT); + } + if (strcmp(ZENOH_CONFIG_LISTEN, "") != 0) { + printf("Listen endpoint: %s\n", ZENOH_CONFIG_LISTEN); + zp_config_insert(z_loan_mut(config), Z_CONFIG_LISTEN_KEY, ZENOH_CONFIG_LISTEN); + } + + printf("Opening %s session ...\n", ZENOH_CONFIG_MODE); + z_owned_session_t s; + if (z_open(&s, z_move(config), NULL) < 0) { + printf("Unable to open session!\n"); + return; + } + + // Start read and lease tasks for zenoh-pico + if (zp_start_read_task(z_loan_mut(s), NULL) < 0 || zp_start_lease_task(z_loan_mut(s), NULL) < 0) { + printf("Unable to start read and lease tasks\n"); + z_drop(z_move(s)); + return; + } + // Declare Subscriber/resource + z_stats_t *context = z_stats_make(); + z_owned_closure_sample_t callback; + z_closure(&callback, on_sample, drop_stats, (void *)context); + z_view_keyexpr_t ke; + z_view_keyexpr_from_str(&ke, KEYEXPR); + if (z_declare_background_subscriber(z_loan(s), z_loan(ke), z_move(callback), NULL) < 0) { + printf("Unable to declare subscriber.\n"); + return; + } + + while (1) { + z_sleep_s(1); + } + + // Clean-up + z_drop(z_move(s)); +} +#else +void app_main(void) { + printf("ERROR: Zenoh pico was compiled without Z_FEATURE_SUBSCRIPTION but this example requires it.\n"); +} +#endif diff --git a/extra_script.py b/extra_script.py index dc4332e65..6032a6b89 100644 --- a/extra_script.py +++ b/extra_script.py @@ -26,6 +26,7 @@ "-", "-", "-", + "-", "-", "-", "-", @@ -42,6 +43,7 @@ "-", "-", "-", + "-", "-", "-", "-", @@ -58,6 +60,7 @@ "-", "-", "-", + "-", "-", "-", "-", @@ -72,6 +75,7 @@ "-", "-", "-", + "-", "-", "-", "-", @@ -87,6 +91,7 @@ "-", "-", "-", + "-", "-", "-", "-", diff --git a/include/zenoh-pico/config.h b/include/zenoh-pico/config.h index 1578b1f50..7af396745 100644 --- a/include/zenoh-pico/config.h +++ b/include/zenoh-pico/config.h @@ -35,6 +35,7 @@ #define Z_FEATURE_LINK_BLUETOOTH 0 #define Z_FEATURE_LINK_WS 0 #define Z_FEATURE_LINK_SERIAL 0 +#define Z_FEATURE_LINK_SERIAL_USB 0 #define Z_FEATURE_SCOUTING_UDP 1 #define Z_FEATURE_LINK_UDP_MULTICAST 1 #define Z_FEATURE_LINK_UDP_UNICAST 1 diff --git a/include/zenoh-pico/config.h.in b/include/zenoh-pico/config.h.in index 175fc8fdf..9e58d1bbe 100644 --- a/include/zenoh-pico/config.h.in +++ b/include/zenoh-pico/config.h.in @@ -35,6 +35,7 @@ #define Z_FEATURE_LINK_BLUETOOTH @Z_FEATURE_LINK_BLUETOOTH@ #define Z_FEATURE_LINK_WS @Z_FEATURE_LINK_WS@ #define Z_FEATURE_LINK_SERIAL @Z_FEATURE_LINK_SERIAL@ +#define Z_FEATURE_LINK_SERIAL_USB @Z_FEATURE_LINK_SERIAL_USB@ #define Z_FEATURE_SCOUTING_UDP @Z_FEATURE_SCOUTING_UDP@ #define Z_FEATURE_LINK_UDP_MULTICAST @Z_FEATURE_LINK_UDP_MULTICAST@ #define Z_FEATURE_LINK_UDP_UNICAST @Z_FEATURE_LINK_UDP_UNICAST@ diff --git a/include/zenoh-pico/system/platform/rpi_pico.h b/include/zenoh-pico/system/platform/rpi_pico.h new file mode 100644 index 000000000..b5480548c --- /dev/null +++ b/include/zenoh-pico/system/platform/rpi_pico.h @@ -0,0 +1,88 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#ifndef ZENOH_PICO_SYSTEM_RPI_PICO_TYPES_H +#define ZENOH_PICO_SYSTEM_RPI_PICO_TYPES_H + +#include + +#include "FreeRTOS.h" +#include "event_groups.h" +#if Z_FEATURE_LINK_TCP == 1 || Z_FEATURE_LINK_UDP_MULTICAST == 1 || Z_FEATURE_LINK_UDP_UNICAST == 1 +#include "lwip/ip4_addr.h" +#endif +#if Z_FEATURE_LINK_SERIAL == 1 +#include "hardware/gpio.h" +#include "hardware/uart.h" +#endif +#include "semphr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if Z_FEATURE_MULTI_THREAD == 1 +typedef struct { + const char *name; + UBaseType_t priority; + size_t stack_depth; +} z_task_attr_t; + +typedef struct { + TaskHandle_t handle; + EventGroupHandle_t join_event; +} _z_task_t; + +typedef SemaphoreHandle_t _z_mutex_t; +typedef struct { + SemaphoreHandle_t mutex; + SemaphoreHandle_t sem; + int waiters; +} _z_condvar_t; +#endif // Z_MULTI_THREAD == 1 + +typedef struct timespec z_clock_t; +typedef struct timeval z_time_t; + +typedef struct { + union { +#if Z_FEATURE_LINK_TCP == 1 || Z_FEATURE_LINK_UDP_MULTICAST == 1 || Z_FEATURE_LINK_UDP_UNICAST == 1 + int _fd; +#endif +#if Z_FEATURE_LINK_SERIAL == 1 + uart_inst_t *_serial; +#endif + }; +} _z_sys_net_socket_t; + +typedef struct { + union { +#if Z_FEATURE_LINK_TCP == 1 || Z_FEATURE_LINK_UDP_MULTICAST == 1 || Z_FEATURE_LINK_UDP_UNICAST == 1 + struct addrinfo *_iptcp; +#endif + }; +} _z_sys_net_endpoint_t; + +#ifdef __cplusplus +} +#endif + +#if Z_FEATURE_LINK_SERIAL_USB == 1 +void _z_usb_uart_init(); +void _z_usb_uart_deinit(); +void _z_usb_uart_write(const uint8_t *buf, int length); +uint8_t _z_usb_uart_getc(); +#endif + +#endif // ZENOH_PICO_SYSTEM_RPI_PICO_TYPES_H diff --git a/include/zenoh-pico/system/platform_common.h b/include/zenoh-pico/system/platform_common.h index 60d85c4e6..9ab0d4fc9 100644 --- a/include/zenoh-pico/system/platform_common.h +++ b/include/zenoh-pico/system/platform_common.h @@ -46,6 +46,8 @@ #include "zenoh-pico/system/platform/flipper.h" #elif defined(ZENOH_FREERTOS_PLUS_TCP) #include "zenoh-pico/system/platform/freertos_plus_tcp.h" +#elif defined(ZENOH_RPI_PICO) +#include "zenoh-pico/system/platform/rpi_pico.h" #else #include "zenoh-pico/system/platform/void.h" #error "Unknown platform" diff --git a/src/api/api.c b/src/api/api.c index 8ff30c7cf..43a95a641 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -647,6 +647,7 @@ z_result_t z_open(z_owned_session_t *zs, z_moved_config_t *config, const z_open_ // Open session z_result_t ret = _z_open(&zs->_rc, &config->_this._val); if (ret != _Z_RES_OK) { + _Z_ERROR("_z_open failed: %i", ret); _z_session_rc_decr(&zs->_rc); z_internal_session_null(zs); z_config_drop(config); diff --git a/src/session/scout.c b/src/session/scout.c index 7c35d1e21..b6e758542 100644 --- a/src/session/scout.c +++ b/src/session/scout.c @@ -18,6 +18,7 @@ #include "zenoh-pico/link/manager.h" #include "zenoh-pico/protocol/codec/transport.h" #include "zenoh-pico/protocol/core.h" +#include "zenoh-pico/system/platform.h" #include "zenoh-pico/transport/multicast.h" #include "zenoh-pico/utils/logging.h" diff --git a/src/system/espidf/network.c b/src/system/espidf/network.c index bc91f50c3..ca3b58c18 100644 --- a/src/system/espidf/network.c +++ b/src/system/espidf/network.c @@ -685,7 +685,7 @@ size_t _z_read_serial(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len) uint32_t c_crc = _z_crc32(ptr, payload_len); if (c_crc != crc) { - _Z_ERROR("CRC mismatch: %d != %d ", c_crc, crc); + _Z_ERROR("CRC mismatch: %ld != %ld ", c_crc, crc); ret = _Z_ERR_GENERIC; } } else { diff --git a/src/system/rpi_pico/network.c b/src/system/rpi_pico/network.c new file mode 100644 index 000000000..135d05c84 --- /dev/null +++ b/src/system/rpi_pico/network.c @@ -0,0 +1,724 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include + +#include "zenoh-pico/collections/string.h" +#include "zenoh-pico/system/link/serial.h" +#include "zenoh-pico/system/platform.h" +#include "zenoh-pico/utils/checksum.h" +#include "zenoh-pico/utils/encoding.h" +#include "zenoh-pico/utils/logging.h" +#include "zenoh-pico/utils/pointers.h" +#include "zenoh-pico/utils/result.h" + +#if Z_FEATURE_LINK_TCP == 1 +#include "lwip/dns.h" +#include "lwip/ip4_addr.h" +#include "lwip/netdb.h" +#include "lwip/pbuf.h" +#include "lwip/sockets.h" +#include "lwip/udp.h" +#include "pico/cyw43_arch.h" + +/*------------------ TCP sockets ------------------*/ +z_result_t _z_create_endpoint_tcp(_z_sys_net_endpoint_t *ep, const char *s_address, const char *s_port) { + z_result_t ret = _Z_RES_OK; + + struct addrinfo hints; + (void)memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = IPPROTO_TCP; + + if (getaddrinfo(s_address, s_port, &hints, &ep->_iptcp) < 0) { + ret = _Z_ERR_GENERIC; + } + + return ret; +} + +void _z_free_endpoint_tcp(_z_sys_net_endpoint_t *ep) { freeaddrinfo(ep->_iptcp); } + +z_result_t _z_open_tcp(_z_sys_net_socket_t *sock, const _z_sys_net_endpoint_t rep, uint32_t tout) { + z_result_t ret = _Z_RES_OK; + + sock->_fd = socket(rep._iptcp->ai_family, rep._iptcp->ai_socktype, rep._iptcp->ai_protocol); + if (sock->_fd != -1) { + z_time_t tv; + tv.tv_sec = tout / (uint32_t)1000; + tv.tv_usec = (tout % (uint32_t)1000) * (uint32_t)1000; + if ((ret == _Z_RES_OK) && (setsockopt(sock->_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)) < 0)) { + ret = _Z_ERR_GENERIC; + } + + int flags = 1; + if ((ret == _Z_RES_OK) && + (setsockopt(sock->_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags)) < 0)) { + ret = _Z_ERR_GENERIC; + } +#if Z_FEATURE_TCP_NODELAY == 1 + if ((ret == _Z_RES_OK) && + (setsockopt(sock->_fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)) < 0)) { + ret = _Z_ERR_GENERIC; + } +#endif + struct linger ling; + ling.l_onoff = 1; + ling.l_linger = Z_TRANSPORT_LEASE / 1000; + if ((ret == _Z_RES_OK) && + (setsockopt(sock->_fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(struct linger)) < 0)) { + ret = _Z_ERR_GENERIC; + } + + struct addrinfo *it = NULL; + for (it = rep._iptcp; it != NULL; it = it->ai_next) { + if ((ret == _Z_RES_OK) && (connect(sock->_fd, it->ai_addr, it->ai_addrlen) < 0)) { + if (it->ai_next == NULL) { + ret = _Z_ERR_GENERIC; + break; + } + } else { + break; + } + } + + if (ret != _Z_RES_OK) { + close(sock->_fd); + } + } else { + ret = _Z_ERR_GENERIC; + } + + return ret; +} + +z_result_t _z_listen_tcp(_z_sys_net_socket_t *sock, const _z_sys_net_endpoint_t lep) { + z_result_t ret = _Z_RES_OK; + (void)sock; + (void)lep; + + // @TODO: To be implemented + ret = _Z_ERR_GENERIC; + + return ret; +} + +void _z_close_tcp(_z_sys_net_socket_t *sock) { + shutdown(sock->_fd, SHUT_RDWR); + close(sock->_fd); +} + +size_t _z_read_tcp(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len) { + ssize_t rb = recv(sock._fd, ptr, len, 0); + if (rb < (ssize_t)0) { + return SIZE_MAX; + } + + return (size_t)rb; +} + +size_t _z_read_exact_tcp(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len) { + size_t n = 0; + uint8_t *pos = &ptr[0]; + + do { + size_t rb = _z_read_tcp(sock, pos, len - n); + if (rb == SIZE_MAX) { + n = rb; + break; + } + + n = n + rb; + pos = _z_ptr_u8_offset(pos, n); + } while (n != len); + + return n; +} + +size_t _z_send_tcp(const _z_sys_net_socket_t sock, const uint8_t *ptr, size_t len) { + return send(sock._fd, ptr, len, 0); +} +#endif + +#if Z_FEATURE_LINK_UDP_UNICAST == 1 || Z_FEATURE_LINK_UDP_MULTICAST == 1 +/*------------------ UDP sockets ------------------*/ +z_result_t _z_create_endpoint_udp(_z_sys_net_endpoint_t *ep, const char *s_address, const char *s_port) { + z_result_t ret = _Z_RES_OK; + + struct addrinfo hints; + (void)memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = IPPROTO_UDP; + + if (getaddrinfo(s_address, s_port, &hints, &ep->_iptcp) < 0) { + ret = _Z_ERR_GENERIC; + } + + return ret; +} + +void _z_free_endpoint_udp(_z_sys_net_endpoint_t *ep) { freeaddrinfo(ep->_iptcp); } +#endif + +#if Z_FEATURE_LINK_UDP_UNICAST == 1 +z_result_t _z_open_udp_unicast(_z_sys_net_socket_t *sock, const _z_sys_net_endpoint_t rep, uint32_t tout) { + z_result_t ret = _Z_RES_OK; + + sock->_fd = socket(rep._iptcp->ai_family, rep._iptcp->ai_socktype, rep._iptcp->ai_protocol); + if (sock->_fd != -1) { + z_time_t tv; + tv.tv_sec = tout / (uint32_t)1000; + tv.tv_usec = (tout % (uint32_t)1000) * (uint32_t)1000; + if ((ret == _Z_RES_OK) && (setsockopt(sock->_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)) < 0)) { + ret = _Z_ERR_GENERIC; + } + + if (ret != _Z_RES_OK) { + close(sock->_fd); + } + } else { + ret = _Z_ERR_GENERIC; + } + + return ret; +} + +z_result_t _z_listen_udp_unicast(_z_sys_net_socket_t *sock, const _z_sys_net_endpoint_t lep, uint32_t tout) { + (void)sock; + (void)lep; + (void)tout; + z_result_t ret = _Z_RES_OK; + + // @TODO: To be implemented + ret = _Z_ERR_GENERIC; + + return ret; +} + +void _z_close_udp_unicast(_z_sys_net_socket_t *sock) { close(sock->_fd); } + +size_t _z_read_udp_unicast(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len) { + struct sockaddr_storage raddr; + long unsigned int addrlen = sizeof(struct sockaddr_storage); + + ssize_t rb = recvfrom(sock._fd, ptr, len, 0, (struct sockaddr *)&raddr, &addrlen); + if (rb < (ssize_t)0) { + return SIZE_MAX; + } + return (size_t)rb; +} + +size_t _z_read_exact_udp_unicast(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len) { + size_t n = 0; + uint8_t *pos = &ptr[0]; + + do { + size_t rb = _z_read_udp_unicast(sock, pos, len - n); + if (rb == SIZE_MAX) { + n = rb; + break; + } + + n = n + rb; + pos = _z_ptr_u8_offset(pos, (ptrdiff_t)n); + } while (n != len); + + return n; +} + +size_t _z_send_udp_unicast(const _z_sys_net_socket_t sock, const uint8_t *ptr, size_t len, + const _z_sys_net_endpoint_t rep) { + return (size_t)sendto(sock._fd, ptr, len, 0, rep._iptcp->ai_addr, rep._iptcp->ai_addrlen); +} +#endif + +#if Z_FEATURE_LINK_UDP_MULTICAST == 1 +long unsigned int __get_ip_from_iface(const char *iface, int sa_family, struct sockaddr **lsockaddr) { + unsigned int addrlen = 0U; + + struct netif *netif = &cyw43_state.netif[CYW43_ITF_STA]; + if (netif_is_up(netif)) { + struct sockaddr_in *lsockaddr_in = z_malloc(sizeof(struct sockaddr_in)); + if (lsockaddr != NULL) { + (void)memset(lsockaddr_in, 0, sizeof(struct sockaddr_in)); + const ip4_addr_t *ip4_addr = netif_ip4_addr(netif); + inet_addr_from_ip4addr(&lsockaddr_in->sin_addr, ip_2_ip4(ip4_addr)); + lsockaddr_in->sin_family = AF_INET; + lsockaddr_in->sin_port = htons(0); + + addrlen = sizeof(struct sockaddr_in); + *lsockaddr = (struct sockaddr *)lsockaddr_in; + } + } + + return addrlen; +} + +z_result_t _z_open_udp_multicast(_z_sys_net_socket_t *sock, const _z_sys_net_endpoint_t rep, _z_sys_net_endpoint_t *lep, + uint32_t tout, const char *iface) { + z_result_t ret = _Z_RES_OK; + + struct sockaddr *lsockaddr = NULL; + long unsigned int addrlen = __get_ip_from_iface(iface, rep._iptcp->ai_family, &lsockaddr); + if (addrlen != 0U) { + sock->_fd = socket(rep._iptcp->ai_family, rep._iptcp->ai_socktype, rep._iptcp->ai_protocol); + if (sock->_fd != -1) { + z_time_t tv; + tv.tv_sec = tout / (uint32_t)1000; + tv.tv_usec = (tout % (uint32_t)1000) * (uint32_t)1000; + if ((ret == _Z_RES_OK) && (setsockopt(sock->_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)) < 0)) { + ret = _Z_ERR_GENERIC; + } + + if ((ret == _Z_RES_OK) && (bind(sock->_fd, lsockaddr, addrlen) < 0)) { + ret = _Z_ERR_GENERIC; + } + + // Get the randomly assigned port used to discard loopback messages + if ((ret == _Z_RES_OK) && (getsockname(sock->_fd, lsockaddr, &addrlen) < 0)) { + ret = _Z_ERR_GENERIC; + } + + // Create lep endpoint + if (ret == _Z_RES_OK) { + struct addrinfo *laddr = (struct addrinfo *)z_malloc(sizeof(struct addrinfo)); + if (laddr != NULL) { + laddr->ai_flags = 0; + laddr->ai_family = rep._iptcp->ai_family; + laddr->ai_socktype = rep._iptcp->ai_socktype; + laddr->ai_protocol = rep._iptcp->ai_protocol; + laddr->ai_addrlen = addrlen; + laddr->ai_addr = lsockaddr; + laddr->ai_canonname = NULL; + laddr->ai_next = NULL; + lep->_iptcp = laddr; + } else { + ret = _Z_ERR_GENERIC; + } + } + + if (ret != _Z_RES_OK) { + close(sock->_fd); + } + } else { + ret = _Z_ERR_GENERIC; + } + + if (ret != _Z_RES_OK) { + z_free(lsockaddr); + } + } else { + ret = _Z_ERR_GENERIC; + } + + return ret; +} + +z_result_t _z_listen_udp_multicast(_z_sys_net_socket_t *sock, const _z_sys_net_endpoint_t rep, uint32_t tout, + const char *iface, const char *join) { + z_result_t ret = _Z_RES_OK; + + struct sockaddr *lsockaddr = NULL; + unsigned int addrlen = __get_ip_from_iface(iface, rep._iptcp->ai_family, &lsockaddr); + if (addrlen != 0U) { + sock->_fd = socket(rep._iptcp->ai_family, rep._iptcp->ai_socktype, rep._iptcp->ai_protocol); + if (sock->_fd != -1) { + z_time_t tv; + tv.tv_sec = tout / (uint32_t)1000; + tv.tv_usec = (tout % (uint32_t)1000) * (uint32_t)1000; + if ((ret == _Z_RES_OK) && (setsockopt(sock->_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)) < 0)) { + ret = _Z_ERR_GENERIC; + } + + if (rep._iptcp->ai_family == AF_INET) { + struct sockaddr_in address; + (void)memset(&address, 0, sizeof(address)); + address.sin_family = (sa_family_t)rep._iptcp->ai_family; + address.sin_port = ((struct sockaddr_in *)rep._iptcp->ai_addr)->sin_port; + inet_pton(address.sin_family, "0.0.0.0", &address.sin_addr); + if ((ret == _Z_RES_OK) && (bind(sock->_fd, (struct sockaddr *)&address, sizeof(address)) < 0)) { + ret = _Z_ERR_GENERIC; + } + } else { + ret = _Z_ERR_GENERIC; + } + + // Join the multicast group + if (rep._iptcp->ai_family == AF_INET) { + struct ip_mreq mreq; + (void)memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)rep._iptcp->ai_addr)->sin_addr.s_addr; + mreq.imr_interface.s_addr = ((struct sockaddr_in *)lsockaddr)->sin_addr.s_addr; + if ((ret == _Z_RES_OK) && + (setsockopt(sock->_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)) { + ret = _Z_ERR_GENERIC; + } + } else { + ret = _Z_ERR_GENERIC; + } + // Join any additional multicast group + if (join != NULL) { + char *joins = _z_str_clone(join); + for (char *ip = strsep(&joins, "|"); ip != NULL; ip = strsep(&joins, "|")) { + if (rep._iptcp->ai_family == AF_INET) { + struct ip_mreq mreq; + (void)memset(&mreq, 0, sizeof(mreq)); + inet_pton(rep._iptcp->ai_family, ip, &mreq.imr_multiaddr); + mreq.imr_interface.s_addr = ((struct sockaddr_in *)lsockaddr)->sin_addr.s_addr; + if ((ret == _Z_RES_OK) && + (setsockopt(sock->_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)) { + ret = _Z_ERR_GENERIC; + } + } + } + z_free(joins); + } + + if (ret != _Z_RES_OK) { + close(sock->_fd); + } + } else { + ret = _Z_ERR_GENERIC; + } + + z_free(lsockaddr); + } else { + ret = _Z_ERR_GENERIC; + } + + return ret; +} + +void _z_close_udp_multicast(_z_sys_net_socket_t *sockrecv, _z_sys_net_socket_t *socksend, + const _z_sys_net_endpoint_t rep, const _z_sys_net_endpoint_t lep) { + if (rep._iptcp->ai_family == AF_INET) { + struct ip_mreq mreq; + (void)memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)rep._iptcp->ai_addr)->sin_addr.s_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + setsockopt(sockrecv->_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + } else { + // Do nothing. It must never not enter here. + // Required to be compliant with MISRA 15.7 rule + } + _ZP_UNUSED(lep); + + close(sockrecv->_fd); + close(socksend->_fd); +} + +size_t _z_read_udp_multicast(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len, const _z_sys_net_endpoint_t lep, + _z_slice_t *addr) { + struct sockaddr_storage raddr; + long unsigned int replen = sizeof(struct sockaddr_storage); + + ssize_t rb = 0; + do { + rb = recvfrom(sock._fd, ptr, len, 0, (struct sockaddr *)&raddr, &replen); + if (rb < (ssize_t)0) { + return SIZE_MAX; + } + + if (lep._iptcp->ai_family == AF_INET) { + struct sockaddr_in *a = ((struct sockaddr_in *)lep._iptcp->ai_addr); + struct sockaddr_in *b = ((struct sockaddr_in *)&raddr); + if (!((a->sin_port == b->sin_port) && (a->sin_addr.s_addr == b->sin_addr.s_addr))) { + // If addr is not NULL, it means that the rep was requested by the upper-layers + if (addr != NULL) { + *addr = _z_slice_make(sizeof(in_addr_t) + sizeof(in_port_t)); + (void)memcpy((uint8_t *)addr->start, &b->sin_addr.s_addr, sizeof(in_addr_t)); + (void)memcpy((uint8_t *)(addr->start + sizeof(in_addr_t)), &b->sin_port, sizeof(in_port_t)); + } + break; + } + } else { + continue; // FIXME: support error report on invalid packet to the upper layer + } + } while (1); + + return (size_t)rb; +} + +size_t _z_read_exact_udp_multicast(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len, + const _z_sys_net_endpoint_t lep, _z_slice_t *addr) { + size_t n = 0; + uint8_t *pos = &ptr[0]; + + do { + size_t rb = _z_read_udp_multicast(sock, pos, len - n, lep, addr); + if (rb == SIZE_MAX) { + n = rb; + break; + } + + n = n + rb; + pos = _z_ptr_u8_offset(pos, (ptrdiff_t)n); + } while (n != len); + + return n; +} + +size_t _z_send_udp_multicast(const _z_sys_net_socket_t sock, const uint8_t *ptr, size_t len, + const _z_sys_net_endpoint_t rep) { + return (size_t)sendto(sock._fd, ptr, len, 0, rep._iptcp->ai_addr, rep._iptcp->ai_addrlen); +} + +#endif + +#if Z_FEATURE_LINK_SERIAL == 1 + +// Define allowed TX/RX pin combinations for UART0 and UART1 +static const struct { + uint32_t tx_pin; + uint32_t rx_pin; + uart_inst_t *uart; + const char *alias; +} allowed_uart_pins[] = {{0, 1, uart0, "uart0_0"}, + {4, 5, uart1, "uart1_0"}, + {8, 9, uart1, "uart1_1"}, + {12, 13, uart0, "uart0_1"}, + {16, 17, uart0, "uart0_2"}}; + +#define NUM_UART_PIN_COMBINATIONS (sizeof(allowed_uart_pins) / sizeof(allowed_uart_pins[0])) + +static void _z_open_serial_impl(uart_inst_t *uart, uint32_t txpin, uint32_t rxpin, uint32_t baudrate) { + uart_init(uart, baudrate); + + gpio_set_function(txpin, UART_FUNCSEL_NUM(uart, txpin)); + gpio_set_function(rxpin, UART_FUNCSEL_NUM(uart, rxpin)); + + uart_set_format(uart, 8, 1, UART_PARITY_NONE); // 8-N-1: Default in Zenoh Rust +} + +z_result_t _z_open_serial_from_pins(_z_sys_net_socket_t *sock, uint32_t txpin, uint32_t rxpin, uint32_t baudrate) { + z_result_t ret = _Z_RES_OK; + + if (!sock || baudrate == 0) { + return _Z_ERR_INVALID; + } + + sock->_serial = NULL; + + for (size_t i = 0; i < NUM_UART_PIN_COMBINATIONS; i++) { + if (allowed_uart_pins[i].tx_pin == txpin && allowed_uart_pins[i].rx_pin == rxpin) { + sock->_serial = allowed_uart_pins[i].uart; + break; + } + } + + if (sock->_serial == NULL) { + _Z_ERROR("invalid pin combination"); + return _Z_ERR_INVALID; + } + + _z_open_serial_impl(sock->_serial, txpin, rxpin, baudrate); + + return ret; +} + +z_result_t _z_open_serial_from_dev(_z_sys_net_socket_t *sock, char *dev, uint32_t baudrate) { + z_result_t ret = _Z_RES_OK; + + sock->_serial = NULL; + +#if Z_FEATURE_LINK_SERIAL_USB == 1 + if (strcmp("usb", dev) == 0) { + _z_usb_uart_init(); + return ret; + } +#endif + + uint32_t txpin = 0; + uint32_t rxpin = 0; + + for (size_t i = 0; i < NUM_UART_PIN_COMBINATIONS; i++) { + if (strcmp(allowed_uart_pins[i].alias, dev) == 0) { + sock->_serial = allowed_uart_pins[i].uart; + txpin = allowed_uart_pins[i].tx_pin; + rxpin = allowed_uart_pins[i].rx_pin; + break; + } + } + + if (sock->_serial == NULL) { + _Z_ERROR("invalid device name"); + return _Z_ERR_INVALID; + } + + _z_open_serial_impl(sock->_serial, txpin, rxpin, baudrate); + + return ret; +} + +z_result_t _z_listen_serial_from_pins(_z_sys_net_socket_t *sock, uint32_t txpin, uint32_t rxpin, uint32_t baudrate) { + z_result_t ret = _Z_RES_OK; + (void)(sock); + (void)(txpin); + (void)(rxpin); + (void)(baudrate); + + // @TODO: To be implemented + ret = _Z_ERR_GENERIC; + + return ret; +} + +z_result_t _z_listen_serial_from_dev(_z_sys_net_socket_t *sock, char *dev, uint32_t baudrate) { + z_result_t ret = _Z_RES_OK; + (void)(sock); + (void)(dev); + (void)(baudrate); + + // @TODO: To be implemented + ret = _Z_ERR_GENERIC; + + return ret; +} + +void _z_close_serial(_z_sys_net_socket_t *sock) { + if (sock->_serial != NULL) { + uart_deinit(sock->_serial); + } else { +#if Z_FEATURE_LINK_SERIAL_USB == 1 + _z_usb_uart_deinit(); +#endif + } +} + +size_t _z_read_serial(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len) { + z_result_t ret = _Z_RES_OK; + + uint8_t *before_cobs = (uint8_t *)z_malloc(_Z_SERIAL_MAX_COBS_BUF_SIZE); + size_t rb = 0; + for (size_t i = 0; i < _Z_SERIAL_MAX_COBS_BUF_SIZE; i++) { +#if Z_FEATURE_LINK_SERIAL_USB == 1 + before_cobs[i] = (sock._serial == NULL) ? _z_usb_uart_getc() : uart_getc(sock._serial); +#else + before_cobs[i] = uart_getc(sock._serial); +#endif + + rb = rb + (size_t)1; + if (before_cobs[i] == (uint8_t)0x00) { + break; + } + } + + uint8_t *after_cobs = (uint8_t *)z_malloc(_Z_SERIAL_MFS_SIZE); + size_t trb = _z_cobs_decode(before_cobs, rb, after_cobs); + + size_t i = 0; + uint16_t payload_len = 0; + for (; i < sizeof(payload_len); i++) { + payload_len |= (after_cobs[i] << ((uint8_t)i * (uint8_t)8)); + } + + if (trb == (size_t)(payload_len + (uint16_t)6)) { + (void)memcpy(ptr, &after_cobs[i], payload_len); + i = i + (size_t)payload_len; + + uint32_t crc = 0; + for (uint8_t j = 0; j < sizeof(crc); j++) { + crc |= (uint32_t)(after_cobs[i] << (j * (uint8_t)8)); + i = i + (size_t)1; + } + + uint32_t c_crc = _z_crc32(ptr, payload_len); + if (c_crc != crc) { + ret = _Z_ERR_GENERIC; + } + } else { + ret = _Z_ERR_GENERIC; + } + + z_free(before_cobs); + z_free(after_cobs); + + rb = payload_len; + if (ret != _Z_RES_OK) { + rb = SIZE_MAX; + } + + return rb; +} + +size_t _z_read_exact_serial(const _z_sys_net_socket_t sock, uint8_t *ptr, size_t len) { + size_t n = 0; + uint8_t *pos = &ptr[0]; + + do { + size_t rb = _z_read_serial(sock, ptr, len - n); + if (rb == SIZE_MAX) { + n = rb; + break; + } + + n = n + rb; + pos = _z_ptr_u8_offset(pos, n); + } while (n != len); + + return n; +} + +size_t _z_send_serial(const _z_sys_net_socket_t sock, const uint8_t *ptr, size_t len) { + z_result_t ret = _Z_RES_OK; + + uint8_t *before_cobs = (uint8_t *)z_malloc(_Z_SERIAL_MFS_SIZE); + size_t i = 0; + for (; i < sizeof(uint16_t); ++i) { + before_cobs[i] = (len >> (i * (size_t)8)) & (size_t)0XFF; + } + + (void)memcpy(&before_cobs[i], ptr, len); + i = i + len; + + uint32_t crc = _z_crc32(ptr, len); + for (uint8_t j = 0; j < sizeof(crc); j++) { + before_cobs[i] = (crc >> (j * (uint8_t)8)) & (uint32_t)0XFF; + i = i + (size_t)1; + } + + uint8_t *after_cobs = (uint8_t *)z_malloc(_Z_SERIAL_MAX_COBS_BUF_SIZE); + ssize_t twb = _z_cobs_encode(before_cobs, i, after_cobs); + after_cobs[twb] = 0x00; // Manually add the COBS delimiter + if (sock._serial == NULL) { +#if Z_FEATURE_LINK_SERIAL_USB == 1 + _z_usb_uart_write(after_cobs, twb + (ssize_t)1); +#endif + } else { + uart_write_blocking(sock._serial, after_cobs, twb + (ssize_t)1); + } + + z_free(before_cobs); + z_free(after_cobs); + + size_t sb = len; + if (ret != _Z_RES_OK) { + sb = SIZE_MAX; + } + + return sb; +} +#endif + +#if Z_FEATURE_LINK_BLUETOOTH == 1 +#error "Bluetooth not supported yet on Raspberry Pi Pico W port of Zenoh-Pico" +#endif + +#if Z_FEATURE_RAWETH_TRANSPORT == 1 +#error "Raw ethernet transport not supported yet on Raspberry Pi Pico W port of Zenoh-Pico" +#endif diff --git a/src/system/rpi_pico/system.c b/src/system/rpi_pico/system.c new file mode 100644 index 000000000..eb7f6c4cc --- /dev/null +++ b/src/system/rpi_pico/system.c @@ -0,0 +1,311 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FreeRTOS.h" +#include "zenoh-pico/config.h" +#include "zenoh-pico/system/platform.h" +#include "zenoh-pico/utils/result.h" + +/*------------------ Random ------------------*/ +uint8_t z_random_u8(void) { return (uint8_t)get_rand_32(); } + +uint16_t z_random_u16(void) { return (uint16_t)get_rand_32(); } + +uint32_t z_random_u32(void) { return get_rand_32(); } + +uint64_t z_random_u64(void) { return get_rand_64(); } + +void z_random_fill(void *buf, size_t len) { + for (size_t i = 0; i < len; i++) { + *((uint8_t *)buf) = z_random_u8(); + } +} + +/*------------------ Memory ------------------*/ +void *z_malloc(size_t size) { return pvPortMalloc(size); } + +void *z_realloc(void *ptr, size_t size) { + _ZP_UNUSED(ptr); + _ZP_UNUSED(size); + // realloc not implemented in FreeRTOS + assert(false); + return NULL; +} + +void z_free(void *ptr) { vPortFree(ptr); } + +#if Z_FEATURE_MULTI_THREAD == 1 +// In FreeRTOS, tasks created using xTaskCreate must end with vTaskDelete. +// A task function should __not__ simply return. +typedef struct { + void *(*fun)(void *); + void *arg; + EventGroupHandle_t join_event; +} z_task_arg; + +static void z_task_wrapper(void *arg) { + z_task_arg *targ = (z_task_arg *)arg; + targ->fun(targ->arg); + xEventGroupSetBits(targ->join_event, 1); + vTaskDelete(NULL); +} + +static z_task_attr_t z_default_task_attr = { + .name = "", + .priority = configMAX_PRIORITIES / 2, + .stack_depth = 5120, +}; + +/*------------------ Thread ------------------*/ +z_result_t _z_task_init(_z_task_t *task, z_task_attr_t *attr, void *(*fun)(void *), void *arg) { + z_task_arg *z_arg = (z_task_arg *)z_malloc(sizeof(z_task_arg)); + if (z_arg == NULL) { + return -1; + } + + z_arg->fun = fun; + z_arg->arg = arg; + z_arg->join_event = task->join_event = xEventGroupCreate(); + + if (attr == NULL) { + attr = &z_default_task_attr; + } + + if (xTaskCreate(z_task_wrapper, attr->name, attr->stack_depth, z_arg, attr->priority, &task->handle) != pdPASS) { + return -1; + } + + return 0; +} + +z_result_t _z_task_join(_z_task_t *task) { + xEventGroupWaitBits(task->join_event, 1, pdFALSE, pdFALSE, portMAX_DELAY); + return 0; +} + +z_result_t _z_task_detach(_z_task_t *task) { + _ZP_UNUSED(task); + assert(false); + return _Z_ERR_GENERIC; +} + +z_result_t _z_task_cancel(_z_task_t *task) { + vTaskDelete(task->handle); + return 0; +} + +void _z_task_free(_z_task_t **task) { + z_free((*task)->join_event); + z_free(*task); +} + +/*------------------ Mutex ------------------*/ +z_result_t _z_mutex_init(_z_mutex_t *m) { + *m = xSemaphoreCreateRecursiveMutex(); + return *m == NULL ? -1 : 0; +} + +z_result_t _z_mutex_drop(_z_mutex_t *m) { + z_free(*m); + return 0; +} + +z_result_t _z_mutex_lock(_z_mutex_t *m) { return xSemaphoreTakeRecursive(*m, portMAX_DELAY) == pdTRUE ? 0 : -1; } + +z_result_t _z_mutex_try_lock(_z_mutex_t *m) { return xSemaphoreTakeRecursive(*m, 0) == pdTRUE ? 0 : -1; } + +z_result_t _z_mutex_unlock(_z_mutex_t *m) { return xSemaphoreGiveRecursive(*m) == pdTRUE ? 0 : -1; } + +/*------------------ CondVar ------------------*/ +static UBaseType_t CONDVAR_MAX_WAITERS_COUNT = UINT_MAX; + +z_result_t _z_condvar_init(_z_condvar_t *cv) { + if (!cv) { + return _Z_ERR_GENERIC; + } + cv->mutex = xSemaphoreCreateMutex(); + cv->sem = xSemaphoreCreateCounting(CONDVAR_MAX_WAITERS_COUNT, 0); + cv->waiters = 0; + + if (!cv->mutex || !cv->sem) { + return _Z_ERR_GENERIC; + } + return _Z_RES_OK; +} + +z_result_t _z_condvar_drop(_z_condvar_t *cv) { + if (!cv) { + return _Z_ERR_GENERIC; + } + vSemaphoreDelete(cv->sem); + vSemaphoreDelete(cv->mutex); + return _Z_RES_OK; +} + +z_result_t _z_condvar_signal(_z_condvar_t *cv) { + if (!cv) { + return _Z_ERR_GENERIC; + } + + xSemaphoreTake(cv->mutex, portMAX_DELAY); + if (cv->waiters > 0) { + xSemaphoreGive(cv->sem); + cv->waiters--; + } + xSemaphoreGive(cv->mutex); + + return _Z_RES_OK; +} + +z_result_t _z_condvar_signal_all(_z_condvar_t *cv) { + if (!cv) { + return _Z_ERR_GENERIC; + } + + xSemaphoreTake(cv->mutex, portMAX_DELAY); + while (cv->waiters > 0) { + xSemaphoreGive(cv->sem); + cv->waiters--; + } + xSemaphoreGive(cv->mutex); + + return _Z_RES_OK; +} + +z_result_t _z_condvar_wait(_z_condvar_t *cv, _z_mutex_t *m) { + if (!cv || !m) { + return _Z_ERR_GENERIC; + } + + xSemaphoreTake(cv->mutex, portMAX_DELAY); + cv->waiters++; + xSemaphoreGive(cv->mutex); + + _z_mutex_unlock(m); + + xSemaphoreTake(cv->sem, portMAX_DELAY); + + return _z_mutex_lock(m); +} +#endif // Z_MULTI_THREAD == 1 + +/*------------------ Sleep ------------------*/ +z_result_t z_sleep_us(size_t time) { + vTaskDelay(pdMS_TO_TICKS(time / 1000)); + return 0; +} + +z_result_t z_sleep_ms(size_t time) { + vTaskDelay(pdMS_TO_TICKS(time)); + return 0; +} + +z_result_t z_sleep_s(size_t time) { + vTaskDelay(pdMS_TO_TICKS(time * 1000)); + return 0; +} + +/*------------------ Clock ------------------*/ +void __z_clock_gettime(z_clock_t *ts) { + uint64_t m = xTaskGetTickCount() / portTICK_PERIOD_MS; + ts->tv_sec = m / (uint64_t)1000; + ts->tv_nsec = (m % (uint64_t)1000) * (uint64_t)1000000; +} + +z_clock_t z_clock_now(void) { + z_clock_t now; + __z_clock_gettime(&now); + return now; +} + +unsigned long z_clock_elapsed_us(z_clock_t *instant) { + z_clock_t now; + __z_clock_gettime(&now); + + unsigned long elapsed = + (unsigned long)(1000000 * (now.tv_sec - instant->tv_sec) + (now.tv_nsec - instant->tv_nsec) / 1000); + return elapsed; +} + +unsigned long z_clock_elapsed_ms(z_clock_t *instant) { + z_clock_t now; + __z_clock_gettime(&now); + + unsigned long elapsed = + (unsigned long)(1000 * (now.tv_sec - instant->tv_sec) + (now.tv_nsec - instant->tv_nsec) / 1000000); + return elapsed; +} + +unsigned long z_clock_elapsed_s(z_clock_t *instant) { + z_clock_t now; + __z_clock_gettime(&now); + + unsigned long elapsed = (unsigned long)(now.tv_sec - instant->tv_sec); + return elapsed; +} + +/*------------------ Time ------------------*/ +z_time_t z_time_now(void) { + z_time_t now; + gettimeofday(&now, NULL); + return now; +} + +const char *z_time_now_as_str(char *const buf, unsigned long buflen) { + z_time_t tv = z_time_now(); + struct tm ts; + ts = *localtime(&tv.tv_sec); + strftime(buf, buflen, "%Y-%m-%dT%H:%M:%SZ", &ts); + return buf; +} + +unsigned long z_time_elapsed_us(z_time_t *time) { + z_time_t now; + gettimeofday(&now, NULL); + + unsigned long elapsed = (unsigned long)(1000000 * (now.tv_sec - time->tv_sec) + (now.tv_usec - time->tv_usec)); + return elapsed; +} + +unsigned long z_time_elapsed_ms(z_time_t *time) { + z_time_t now; + gettimeofday(&now, NULL); + + unsigned long elapsed = (unsigned long)(1000 * (now.tv_sec - time->tv_sec) + (now.tv_usec - time->tv_usec) / 1000); + return elapsed; +} + +unsigned long z_time_elapsed_s(z_time_t *time) { + z_time_t now; + gettimeofday(&now, NULL); + + unsigned long elapsed = (unsigned long)(now.tv_sec - time->tv_sec); + return elapsed; +} + +z_result_t _z_get_time_since_epoch(_z_time_since_epoch *t) { + z_time_t now; + gettimeofday(&now, NULL); + t->secs = (uint32_t)now.tv_sec; + t->nanos = (uint32_t)now.tv_usec * 1000; + return 0; +} diff --git a/src/system/rpi_pico/usb_uart.c b/src/system/rpi_pico/usb_uart.c new file mode 100644 index 000000000..3a00c02c1 --- /dev/null +++ b/src/system/rpi_pico/usb_uart.c @@ -0,0 +1,247 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +#include "zenoh-pico/system/platform.h" +#include "zenoh-pico/utils/logging.h" + +#if Z_FEATURE_LINK_SERIAL == 1 && Z_FEATURE_LINK_SERIAL_USB == 1 + +#include "pico/unique_id.h" +#include "tusb.h" + +// ===== USB descriptors ===== +// Copied from +// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_stdio_usb/stdio_usb_descriptors.c +// +#ifndef USBD_VID +#define USBD_VID (0x2E8A) // Raspberry Pi +#endif + +#ifndef USBD_PID +#if PICO_RP2040 +#define USBD_PID (0x000a) // Raspberry Pi Pico SDK CDC for RP2040 +#else +#define USBD_PID (0x0009) // Raspberry Pi Pico SDK CDC +#endif +#endif + +#ifndef USBD_MANUFACTURER +#define USBD_MANUFACTURER "Raspberry Pi" +#endif + +#ifndef USBD_PRODUCT +#define USBD_PRODUCT "Pico" +#endif + +#define TUD_RPI_RESET_DESC_LEN 9 +#if !PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE +#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN) +#else +#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_RPI_RESET_DESC_LEN) +#endif +#if !PICO_STDIO_USB_DEVICE_SELF_POWERED +#define USBD_CONFIGURATION_DESCRIPTOR_ATTRIBUTE (0) +#define USBD_MAX_POWER_MA (250) +#else +#define USBD_CONFIGURATION_DESCRIPTOR_ATTRIBUTE TUSB_DESC_CONFIG_ATT_SELF_POWERED +#define USBD_MAX_POWER_MA (1) +#endif + +#define USBD_ITF_CDC (0) // needs 2 interfaces +#if !PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE +#define USBD_ITF_MAX (2) +#else +#define USBD_ITF_RPI_RESET (2) +#define USBD_ITF_MAX (3) +#endif + +#define USBD_CDC_EP_CMD (0x81) +#define USBD_CDC_EP_OUT (0x02) +#define USBD_CDC_EP_IN (0x82) +#define USBD_CDC_CMD_MAX_SIZE (8) +#define USBD_CDC_IN_OUT_MAX_SIZE (64) + +#define USBD_STR_0 (0x00) +#define USBD_STR_MANUF (0x01) +#define USBD_STR_PRODUCT (0x02) +#define USBD_STR_SERIAL (0x03) +#define USBD_STR_CDC (0x04) +#define USBD_STR_RPI_RESET (0x05) + +// Note: descriptors returned from callbacks must exist long enough for transfer to complete + +static const tusb_desc_device_t usbd_desc_device = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, +// On Windows, if bcdUSB = 0x210 then a Microsoft OS 2.0 descriptor is required, else the device won't be detected +// This is only needed for driverless access to the reset interface - the CDC interface doesn't require these +// descriptors for driverless access, but will still not work if bcdUSB = 0x210 and no descriptor is provided. Therefore +// always use bcdUSB = 0x200 if the Microsoft OS 2.0 descriptor isn't enabled +#if PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE && PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_MS_OS_20_DESCRIPTOR + .bcdUSB = 0x0210, +#else + .bcdUSB = 0x0200, +#endif + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = USBD_VID, + .idProduct = USBD_PID, + .bcdDevice = 0x0100, + .iManufacturer = USBD_STR_MANUF, + .iProduct = USBD_STR_PRODUCT, + .iSerialNumber = USBD_STR_SERIAL, + .bNumConfigurations = 1, +}; + +#define TUD_RPI_RESET_DESCRIPTOR(_itfnum, _stridx) \ + /* Interface */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_VENDOR_SPECIFIC, RESET_INTERFACE_SUBCLASS, \ + RESET_INTERFACE_PROTOCOL, _stridx, + +static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { + TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_0, USBD_DESC_LEN, USBD_CONFIGURATION_DESCRIPTOR_ATTRIBUTE, + USBD_MAX_POWER_MA), + + TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, + USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), + +#if PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE + TUD_RPI_RESET_DESCRIPTOR(USBD_ITF_RPI_RESET, USBD_STR_RPI_RESET) +#endif +}; + +static char usbd_serial_str[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1]; + +static const char *const usbd_desc_str[] = { + [USBD_STR_MANUF] = USBD_MANUFACTURER, [USBD_STR_PRODUCT] = USBD_PRODUCT, + [USBD_STR_SERIAL] = usbd_serial_str, [USBD_STR_CDC] = "Board CDC", +#if PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE + [USBD_STR_RPI_RESET] = "Reset", +#endif +}; + +const uint8_t *tud_descriptor_device_cb(void) { return (const uint8_t *)&usbd_desc_device; } + +const uint8_t *tud_descriptor_configuration_cb(__unused uint8_t index) { return usbd_desc_cfg; } + +const uint16_t *tud_descriptor_string_cb(uint8_t index, __unused uint16_t langid) { +#ifndef USBD_DESC_STR_MAX +#define USBD_DESC_STR_MAX (20) +#elif USBD_DESC_STR_MAX > 127 +#error USBD_DESC_STR_MAX too high (max is 127). +#elif USBD_DESC_STR_MAX < 17 +#error USBD_DESC_STR_MAX too low (min is 17). +#endif + static uint16_t desc_str[USBD_DESC_STR_MAX]; + + // Assign the SN using the unique flash id + if (!usbd_serial_str[0]) { + pico_get_unique_board_id_string(usbd_serial_str, sizeof(usbd_serial_str)); + } + + uint8_t len; + if (index == 0) { + desc_str[1] = 0x0409; // supported language is English + len = 1; + } else { + if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0])) { + return NULL; + } + const char *str = usbd_desc_str[index]; + for (len = 0; len < USBD_DESC_STR_MAX - 1 && str[len]; ++len) { + desc_str[1 + len] = str[len]; + } + } + + // first byte is length (including header), second byte is string type + desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * len + 2)); + + return desc_str; +} + +// ===== USB UART methods ===== +static _z_mutex_t _z_usb_uart_mutex; +static _z_task_t _z_usb_uart_task; +static volatile bool _z_usb_uart_task_run; + +static void *_z_usb_uart_task_proc(void *) { + while (_z_usb_uart_task_run) { + _z_mutex_lock(&_z_usb_uart_mutex); + tud_task(); + _z_mutex_unlock(&_z_usb_uart_mutex); + z_sleep_ms(1); + } + return NULL; +} + +void _z_usb_uart_init() { + tud_init(BOARD_TUD_RHPORT); + _z_mutex_init(&_z_usb_uart_mutex); + _z_usb_uart_task_run = true; + _z_task_init(&_z_usb_uart_task, NULL, &_z_usb_uart_task_proc, NULL); + _Z_DEBUG("whating for host..."); + while (!tud_cdc_connected()) { + z_sleep_ms(100); + } +} + +void _z_usb_uart_deinit() { + _z_usb_uart_task_run = false; + _z_task_join(&_z_usb_uart_task); + _z_mutex_drop(&_z_usb_uart_mutex); + tud_deinit(BOARD_TUD_RHPORT); +} + +void _z_usb_uart_write(const uint8_t *buf, int length) { + _z_mutex_lock(&_z_usb_uart_mutex); + for (int i = 0; i < length;) { + int n = length - i; + int avail = (int)tud_cdc_write_available(); + if (n > avail) { + n = avail; + } + if (n != 0) { + int n2 = (int)tud_cdc_write(buf + i, (uint32_t)n); + tud_task(); + tud_cdc_write_flush(); + i += n2; + } else { + tud_task(); + tud_cdc_write_flush(); + } + } + _z_mutex_unlock(&_z_usb_uart_mutex); +} + +uint8_t _z_usb_uart_getc() { + uint8_t ret = 0; + bool ready = false; + while (true) { + _z_mutex_lock(&_z_usb_uart_mutex); + ready = tud_cdc_available(); + if (ready) { + ret = tud_cdc_read_char(); + } + _z_mutex_unlock(&_z_usb_uart_mutex); + if (ready) { + break; + } else { + z_sleep_ms(1); + } + } + return ret; +} +#endif