Full Code of mcknly/breadboard-os for AI

main 354d69e454e7 cached
73 files
317.7 KB
83.0k tokens
223 symbols
1 requests
Download .txt
Showing preview only (341K chars total). Download the full file or copy to clipboard to get everything.
Repository: mcknly/breadboard-os
Branch: main
Commit: 354d69e454e7
Files: 73
Total size: 317.7 KB

Directory structure:
gitextract_w576021d/

├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── README.md
├── cli/
│   ├── CMakeLists.txt
│   ├── cli_utils.c
│   ├── cli_utils.h
│   ├── motd.h
│   ├── node_bin.c
│   ├── node_dev.c
│   ├── node_etc.c
│   ├── node_lib.c
│   ├── node_mnt.c
│   ├── node_net.c
│   ├── node_proc.c
│   ├── node_root.c
│   ├── shell.c
│   ├── shell.h
│   └── shell_cmd.c
├── driver_lib/
│   ├── CMakeLists.txt
│   ├── bme280.c
│   ├── device_drivers.c
│   ├── device_drivers.h
│   └── mcp4725.c
├── hardware/
│   └── rp2xxx/
│       ├── CMakeLists.txt
│       ├── hardware_config.c
│       ├── hardware_config.h
│       ├── hw_adc.c
│       ├── hw_clocks.c
│       ├── hw_cores.c
│       ├── hw_gpio.c
│       ├── hw_i2c.c
│       ├── hw_net.c
│       ├── hw_registers.c
│       ├── hw_reset.c
│       ├── hw_spi.c
│       ├── hw_uart.c
│       ├── hw_usb.c
│       ├── hw_versions.c
│       ├── hw_watchdog.c
│       ├── hw_wifi.c
│       ├── net_inc/
│       │   ├── httpd_content/
│       │   │   ├── 404.html
│       │   │   ├── index.shtml
│       │   │   └── test.shtml
│       │   ├── hw_net.h
│       │   ├── hw_wifi.h
│       │   └── lwipopts.h
│       ├── onboard_flash.c
│       ├── onboard_led.c
│       ├── pico_sdk_import.cmake
│       ├── prebuild.cmake
│       └── rtos_config.h
├── littlefs/
│   └── CMakeLists.txt
├── main.c
├── project.cmake
├── rtos/
│   ├── CMakeLists.txt
│   ├── FreeRTOSConfig.h
│   ├── rtos_utils.c
│   └── rtos_utils.h
├── services/
│   ├── CMakeLists.txt
│   ├── cli_service.c
│   ├── heartbeat_service.c
│   ├── netman_service.c
│   ├── service_queues.c
│   ├── service_queues.h
│   ├── services.c
│   ├── services.h
│   ├── storman_service.c
│   ├── taskman_service.c
│   ├── usb_service.c
│   └── watchdog_service.c
└── version.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
build
.vscode
.trunk
.idea/


================================================
FILE: .gitmodules
================================================
[submodule "microshell"]
	path = microshell
	url = https://github.com/mcknly/microshell.git
[submodule "littlefs/littlefs"]
	path = littlefs/littlefs
	url = https://github.com/littlefs-project/littlefs.git
[submodule "cmake-git-version-tracking"]
	path = git_version
	url = https://github.com/andrew-hardin/cmake-git-version-tracking.git


================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.18)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Project/hardware-specific setup - set platform in project.cmake
# Platform-specific prebuild.cmake contains more information about boards
include(project.cmake)
set(hardware_dir ${CMAKE_CURRENT_SOURCE_DIR}/hardware/${PLATFORM})
if(EXISTS ${hardware_dir}/prebuild.cmake)
    include(${hardware_dir}/prebuild.cmake)
endif()
project(${PROJ_NAME} C CXX ASM)

add_definitions(-DPROJECT_NAME=${PROJ_NAME})   # pass the project name to the preprocessor for use in the code
add_definitions(-DPROJECT_VERSION=${PROJ_VER}) # pass the project version number to the preprocessor for use in the code
add_definitions(-DCLI_USE_USB=${CLI_IFACE})    # 0: use UART for CLI (default), 1: use USB for CLI
add_definitions(-DBOARD=${BOARD})              # pass the board type to the preprocessor for further decision making
#add_definitions(-DSCHED_TEST_DELAY)           # uncomment to force delay tasks for scheduler testing, see 'task_sched_update()' function

# FreeRTOS kernel import (platform-specific)
include($ENV{FREERTOS_KERNEL_PATH}/${freertos_port_path})

# add cli/microshell library subdirectory (submodule)
add_subdirectory(${PROJECT_SOURCE_DIR}/cli cli)

# add littlefs library subdirectory (submodule)
add_subdirectory(${PROJECT_SOURCE_DIR}/littlefs littlefs)

# add git version library subdirectory (submodule)
add_subdirectory(${PROJECT_SOURCE_DIR}/git_version cmake_git_version_tracking)

# create the main executable
add_executable(${PROJ_NAME}
    main.c
)
target_link_libraries(${PROJ_NAME} PUBLIC
    FreeRTOS-Kernel
    FreeRTOS-Kernel-Heap4
    cli
    littlefs
    cmake_git_version_tracking
    ${hardware_libs}
)
target_include_directories(${PROJ_NAME} PUBLIC
    ${PROJECT_SOURCE_DIR}
    ${PROJECT_SOURCE_DIR}/rtos
    ${PROJECT_SOURCE_DIR}/services
    ${PROJECT_SOURCE_DIR}/driver_lib
    ${PROJECT_SOURCE_DIR}/littlefs/littlefs
    ${PROJECT_SOURCE_DIR}/git_version
    ${hardware_includes}
)

# add additional source directories that are not libraries/submodules
add_subdirectory(${PROJECT_SOURCE_DIR}/rtos)
add_subdirectory(${PROJECT_SOURCE_DIR}/services)
add_subdirectory(${PROJECT_SOURCE_DIR}/driver_lib)
if(DEFINED hardware_dir)
    add_subdirectory(${hardware_dir})
endif()

# add any additional definitions, options
target_compile_definitions(${PROJ_NAME} PUBLIC 
    CFG_TUSB_CONFIG_FILE="hardware_config.h" # override standard TinyUSB config file location
)

# hardware-specific build options, function defined in hardware/[platform]/CMakeLists.txt
if(COMMAND hardware_build_extra)
    cmake_language(CALL hardware_build_extra)
endif()

# global compiler options for the project
target_compile_options(
    ${PROJ_NAME}
    PRIVATE
    -Werror -g -O0
)
# TODO: try to add -Wall and -Wextra to compile options to clean up more warnings


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)

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.


================================================
FILE: README.md
================================================
# [ B r e a d b o a r d O S ]
```
                             ___          ___
    _____       _____       /  /\        /  /\
   /  /::\     /  /::\     /  /::\      /  /:/_
  /  /:/\:\   /  /:/\:\   /  /:/\:\    /  /:/ /\
 /  /:/~/::\ /  /:/~/::\ /  /:/  \:\  /  /:/ /::\
/__/:/ /:/\:/__/:/ /:/\:/__/:/ \__\:\/__/:/ /:/\:\
\  \:\/:/~/:|  \:\/:/~/:|  \:\ /  /:/\  \:\/:/~/:/
 \  \::/ /:/ \  \::/ /:/ \  \:\  /:/  \  \::/ /:/
  \  \:\/:/   \  \:\/:/   \  \:\/:/    \__\/ /:/
   \  \::/     \  \::/     \  \::/       /__/:/
    \__\/       \__\/       \__\/        \__\/

```
  
**BreadboardOS** (BBOS, if you like) - as its namesake implies - is a firmware platform aimed at quick prototyping. BBOS was built around the following principles:
1. **_Every project should start with a CLI._** A command line interface makes prototyping, debugging, and testing about 1000% easier, especially over the lifespan of multiple projects with re-used pieces.
2. **_Write it once._** Don't waste time writing and re-writing the basic infrastructure for your projects. Modularize, duplicate, and profit!
3. **_Get running QUICKLY._** The faster you can bring up a project, the faster you can find out that you need to re-tool the whole dang thing. _But at least you don't have to write a CLI again!_

## Functional Description

BreadboardOS is built on top of FreeRTOS, enabling fast integration of new functional blocks and allowing for task concurrency without breaking the base system. Existing knowledge of RTOS is not necessarily required.  
  
The central component of BBOS is the fantastic [microshell](https://microshell.pl/) project, which provides CLI functionality. Currently, a [fork](https://github.com/mcknly/microshell) of microshell is used, which includes some additional customization. The CLI implementation is organized into POSIX-style folders/files providing a recognizable user-interface for interacting with MCU hardware.  
  
As of the v0.4 release, BBOS is implemented on a single MCU family - the Raspberry Pi RP2xxx (Pico, Pico W, Pico2, etc). However, the project has been structured such that all hardware-specific code resides in a single directory, with a header file providing HAL functionality. The platform was built with porting in mind.

### _Notable Features_

- Rich set of informational tools for checking system resources on the fly, a'la: `ps top free df`
- RTOS task manager for dynamically controlling system services at runtime
- Onboard flash filesystem with wear-leveling (thanks to [littlefs](https://github.com/littlefs-project/littlefs))
- Selective peripheral hardware initialization routines
- Interacting with chip I/O and serial buses directly from command line
- Watchdog service for system failsafe recovery
- Command history using arrows or ctrl-a/ctrl-z
- WiFi networking support with HTTP server for project dashboard

### _Demo_

As they say, a Youtube video is worth 10^6 words.  
[https://www.youtube.com/watch?v=fQiYE_wlPt0](https://www.youtube.com/watch?v=fQiYE_wlPt0)

### _In The Media_

<div align="left">
<img style="vertical-align:middle"
     src="assets/hackaday.png"
>
<a href="https://hackaday.com/2024/05/14/breadboardos-a-command-line-interface-for-the-pico/">Hackaday</a><br>
<img style="vertical-align:middle"
     src="assets/toms-hardware.png"
>
<a href="https://www.tomshardware.com/raspberry-pi/breadboardos-is-the-best-thing-since-sliced-bread-for-your-raspberry-pi-pico">Tom's Hardware</a><br>
<img style="vertical-align:middle"
     src="assets/adafruit.png"
>
<a href="https://blog.adafruit.com/2024/05/14/breadboardos-for-raspberry-pi-pico/">Adafruit</a><br>
<img style="vertical-align:middle"
     src="assets/cnx-software.png"
>
<a href="https://www.cnx-software.com/2024/05/21/breadboardos-firmware-for-the-raspberry-pi-rp2040-features-a-linux-like-terminal/">CNX Software</a><br>
<img style="vertical-align:middle"
     src="assets/electromaker.png"
>
<a href="https://www.electromaker.io/blog/article/transform-your-raspberry-pi-pico-with-breadboard-os">Electromaker</a>
</div>

## Getting started

The following build instructions for BreadboardOS assume a GNU/Linux environment, or alternatively WSL under Windows. Building in other environments may be possible but is not covered here.

### Prerequisites

- A basic embedded ARM development enviroment: `build-essential gcc-arm-none-eabi newlib-arm-none-eabi cmake`
- The [FreeRTOS Kernel](https://github.com/FreeRTOS/FreeRTOS-Kernel) in an accessible location on the system
- The Raspberry Pi [Pico SDK](https://github.com/raspberrypi/pico-sdk) in an accessible location on the system

Ensure that the environment variables `$FREERTOS_KERNEL_PATH` and `$PICO_SDK_PATH` are set and contain the absolute paths to those projects:  
  
`export FREERTOS_KERNEL_PATH=/path/to/freertos/kernel`  
`export PICO_SDK_PATH=/path/to/pico/sdk`  
  
It is suggested to add these commands to the user's `~/.profile`, `~/.bashrc`, etc depending on the system.

**NOTE for RP2350**: Please ensure that you pull the latest mainline versions of `pico-sdk` and `FreeRTOS-Kernel`, as fixes/support has been recently added (as of early `25).

### Setting up BreadboardOS

- Clone the repository: `git clone https://github.com/mcknly/breadboard-os.git`
- Enter the project folder: `cd breadboard-os`
- Pull in submodules: `git submodule update --init` (also do this for pico-sdk and FreeRTOS-Kernel!)

Open `project.cmake` and edit the following parameters:
```
# PROJECT NAME - in quotes, no spaces
set(PROJ_NAME "my-bbos-proj")

# PROJECT VERSION - in quotes, no spaces, can contain alphanumeric if necessary
set(PROJ_VER "0.0")

# CLI INTERFACE - 0: use UART for CLI (default), 1: use USB for CLI
set(CLI_IFACE 0)

# MCU PLATFORM - set the MCU platform being used (i.e. the subdir in 'hardware/')
set(PLATFORM rp2xxx)

# BOARD - set the board being used (platform-specific prebuild.cmake contains more information about boards)
set(BOARD pico2)

# HOSTNAME - hostname will be shown at CLI prompt, and used for network connections
set(HOSTNAME "bbos")
```

Using CLI over USB requires no additional hardware; using CLI over UART will require a USB-UART adapter (i.e. FTDI FT232 or SiLabs CP2102). Using CLI/UART enables some additional early boot status prints.

### Building

- Create & enter build folder: `mkdir build && cd build`
- Generate the build files with cmake: `cmake ..` (note: this will default to a 'Release' build)
- Compile the project: `make -j4` (# cores can be altered)

This should generate firmware image formats `.bin`, `.hex`, `.uf2`, to be programmed to flash via your preferred method.

### Debugging

For a full debugging-enabled IDE-like experience (recommended), using MS VS Code plus the `C/C++`, `CMake`, and `Cortex-Debug` extensions with your debug probe of choice (Pi Debug Probe, J-Link, etc) is the widely preferred method. See [this](https://www.digikey.com/en/maker/projects/raspberry-pi-pico-and-rp2040-cc-part-2-debugging-with-vs-code/470abc7efb07432b82c95f6f67f184c0) great tutorial by DigiKey.

## Usage - building your own application

Creating your own application on the BreadboardOS platform entails adding one or more new "services" - which are essentially standalone FreeRTOS tasks. An example of this is given in the `services/heartbeat_service.c` file, the structure of which can be used as a basis for your new service/application. See `services/services.h` which includes extensive documentation on how services/tasks are implemented on BBOS.  
  
To understand how your new service can interact with the system, refer to the function implementations in the file nodes within the `cli` directory - these have been provided as a test bed for the underlying system functionality, and the intention is for any new services/tasks to also have their functions mapped into new CLI folders & files - think of this as a test menu system for your project. The CLI could also be leveraged as an automated production test interface!

## Code Organization & Technical Detail

Project directory structure:  
- `cli/` - directory/file structure for the CLI, and interface to the microshell submodule
- `driver_lib/` - drivers for external peripheral hardware - library to grow over time
- `hardware/` - all hardware-specific (HAL) functional implementation, each MCU platform has its own subdirectory
- `littlefs/` - contains the 'littlefs' submodule and CMake wrapper
- `rtos/` - FreeRTOS interface & configuration files
- `services/` - where the main application elements (FreeRTOS tasks) reside
- `git_version/` - submodule for adding GIT repo metadata into the binary
- `microshell/` - submodule for microshell

## Contributing

Contributions to the project are welcome! Here are some specific ways to contribute:
- Use the project! build cool things, identify bugs, submit issues
- Add additional peripheral hardware drivers to `driver_lib`
- Create useful utility services
- Port the project to a new MCU platform

To contribute - fork the project, make enhancements, submit a PR.

## Acknowledgements

BreadboardOS is built in no small part upon the substantial work of others:  
[microshell](https://github.com/marcinbor85/microshell)  
[littlefs](https://github.com/littlefs-project/littlefs)  
[cmake-git-version-tracking](https://github.com/andrew-hardin/cmake-git-version-tracking)  
[freertos](https://github.com/FreeRTOS/FreeRTOS-Kernel)  
[pico-sdk](https://github.com/raspberrypi/pico-sdk)  

## License

This project is released under the [MIT License](https://opensource.org/license/mit/).  
In lieu of including the full license text in every source file, the following tag is used:  
`// SPDX-License-Identifier: MIT`  
  
---
---
  
<div align="center">
<img src=assets/breadboard.png alt="breadboard" width="600" align=center/>
<br>
it's fresh
</div>


================================================
FILE: cli/CMakeLists.txt
================================================
# This file configures the build for the microshell submodule, which is included
# as source for the CLI which will be built as a static library to be used by
# the main application.

# pull in microshell setup
add_subdirectory(${PROJECT_SOURCE_DIR}/microshell/src microshell)

# run microshell build setup script
build_ush(microshell)

# defines to use in microshell build
target_compile_definitions(
    microshell
    PUBLIC
    PICO
)

# create the source file list for the CLI library
set(cli_sources
    ${CMAKE_CURRENT_LIST_DIR}/shell.c
    ${CMAKE_CURRENT_LIST_DIR}/shell_cmd.c
    ${CMAKE_CURRENT_LIST_DIR}/cli_utils.c
    ${CMAKE_CURRENT_LIST_DIR}/node_root.c
    ${CMAKE_CURRENT_LIST_DIR}/node_dev.c
    ${CMAKE_CURRENT_LIST_DIR}/node_bin.c
    ${CMAKE_CURRENT_LIST_DIR}/node_proc.c
    ${CMAKE_CURRENT_LIST_DIR}/node_mnt.c
    ${CMAKE_CURRENT_LIST_DIR}/node_etc.c
    ${CMAKE_CURRENT_LIST_DIR}/node_lib.c
)
if (ENABLE_WIFI)
    list(APPEND cli_sources
        ${CMAKE_CURRENT_LIST_DIR}/node_net.c
    )
endif()

# create cli library to use at top level (linked against microshell)
add_library(cli STATIC
    ${cli_sources}
)
target_link_libraries(cli PUBLIC
    microshell
    FreeRTOS-Kernel
    cmake_git_version_tracking
    ${hardware_libs}
)
target_include_directories(cli PUBLIC
    ${CMAKE_CURRENT_LIST_DIR}
    ${PROJECT_SOURCE_DIR}
    ${PROJECT_SOURCE_DIR}/rtos
    ${PROJECT_SOURCE_DIR}/driver_lib
    ${PROJECT_SOURCE_DIR}/services
    ${PROJECT_SOURCE_DIR}/littlefs/littlefs
    ${PROJECT_SOURCE_DIR}/git_version
    ${hardware_includes}
)

# add any additional definitions, options
target_compile_definitions(cli PUBLIC 
    CFG_TUSB_CONFIG_FILE="hardware_config.h" # override standard TinyUSB config file location
)
if(ENABLE_MOTD)
        target_compile_definitions(cli PUBLIC -DPRINT_MOTD_AT_BOOT)
endif()
target_compile_options(
    cli
    PRIVATE
    -Werror -g -O0
)
# TODO: try to add -Wall and -Wextra to compile options to clean up more warnings


================================================
FILE: cli/cli_utils.c
================================================
/******************************************************************************
 * @file cli_utils.c
 *
 * @brief Utility functions to use for CLI interaction
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <stdint.h>
#include "shell.h"
#include "cli_utils.h"
#include "FreeRTOS.h"


size_t hex_string_to_byte_array(char *hex_string, uint8_t *byte_array) {
    if (hex_string[0] != '0' || hex_string[1] != 'x') {
        return 0; // error if no "0x" prefix
    }
    int len = strlen(hex_string + 2); // hex string length minus the prefix
    if (len % 2 != 0) {
        return 0; // error if not an even number of full bytes
    }
    int string_index, byte_index;
    for (string_index = 0, byte_index = 0; byte_index < len / 2; string_index += 2, byte_index++) {
        // craaaaaazy long if to check for characters in the range 0-9, a-f, or A-F
        if (((((hex_string[string_index + 2] - 48) <= 9) && ((hex_string[string_index + 2] - 48) >= 0))  || // first character 0-9?...or
             (((hex_string[string_index + 2] - 65) <= 5) && ((hex_string[string_index + 2] - 65) >= 0))  || // first character A-F?...or
             (((hex_string[string_index + 2] - 97) <= 5) && ((hex_string[string_index + 2] - 97) >= 0)))    // first character a-f?
            &&                                                                                              // plus
            ((((hex_string[string_index + 3] - 48) <= 9) && ((hex_string[string_index + 3] - 48) >= 0))  || // second character 0-9?...or
             (((hex_string[string_index + 3] - 65) <= 5) && ((hex_string[string_index + 3] - 65) >= 0))  || // second character A-F?...or
             (((hex_string[string_index + 3] - 97) <= 5) && ((hex_string[string_index + 3] - 97) >= 0))))   // second character a-f?
        // whitespace for the win!
        {
            // prepare yourself for this wizardry... convert 2-char ASCII hex to decimal integer
            // this works due to some clever spacing between 0, A, and a in the ASCII table.
            // thanks to: https://gist.github.com/xsleonard/7341172 for the tip
            byte_array[byte_index] = (hex_string[string_index + 2] % 32 + 9) % 25 * 16 + (hex_string[string_index + 3] % 32 + 9) % 25;
        }
        else {return 0;} // error if not a hexadecimal value
    }
    return byte_index;
}

void print_motd(void) {
    // global Message of the Day stored in motd.h. Declared here and defined in the header (violates best practice, sorry y'all)
    extern const char *motd_ascii;
    
    // print directly from MOTD message stored in flash
    shell_print_slow(motd_ascii);
}

================================================
FILE: cli/cli_utils.h
================================================
/******************************************************************************
 * @file cli_utils.h
 *
 * @brief Utility functions to use for CLI interaction
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#ifndef CLI_UTILS_H
#define CLI_UTILS_H

#include <string.h>
#include <stdint.h>


/**
* @brief Convert a string of hex values to an integer byte array.
*
* Takes an ASCII string (prefixed with "0x") and converts each hex-digit pair
* into its 8-bit integer equivalent, then appends into an array of bytes. The
* function will check for valid hexadecimal format, returning either error or
* the length of the array (number of bytes). Note that there must be an even
* number of hex characters (full bytes).
*
* @param hex_string pointer to the hex string char array
* @param byte_array pointer to the location to store the resulting byte array
*
* @return length of the resulting byte array (number of bytes > 0) if success,
*         0 if formatting error on input string
*/
size_t hex_string_to_byte_array(char *hex_string, uint8_t *byte_array);

/**
* @brief Print the Message of the Day (MOTD)
*
* Calling this function will print the MOTD out to the CLI using an advanced
* Graphical Ascii Slow Processing (GASP!) algorithm.
*
* @param none
*
* @return nothing
*/
void print_motd(void);

#endif /* CLI_UTILS_H */

================================================
FILE: cli/motd.h
================================================
/******************************************************************************
 * @file motd.h
 *
 * @brief contains the ASCII string (or art!) for MOTD (message of the day)
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#ifndef MOTD_H
#define MOTD_H

#include <microshell.h>
#include "version.h"

// stringify helper - use xstr() to convert #define to a usable string
#define str(s) # s
#define xstr(s) str(s)

// place motd message in motd_ascii character array pointer
// thanks to:
// great ASCII art generator  - https://github.com/TheZoraiz/ascii-image-converter
// great ASCII font generator - https://patorjk.com/software/taag
//
// remember...any forward slashes need to be escaped by an extra /
// also make sure there are no quotes in the art, replace them with some other char
//
// the image below relies on "braille" characters (UTF-8/Unicode) and requires a suitable
// terminal font to render properly, specifically support for characters U+2800..U+28FF.
const char *motd_ascii =
"\r\n"
USH_SHELL_FONT_COLOR_YELLOW
" ⣿⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⡠⠤⢤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠐⠒⠊⢉⣉⣉⣉⣉⠁⠀⠀⠀⡀⠀⠠⠄⣉⠑⠒⠤⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⢀⠊⢁⡠⢔⣚⣭⡵⠒⠀⠈⠶⠭⠑⢒⡦⢄⠉⠲⡢⢄⠉⠲⡢⣄⡈⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⡐⢁⠔⠕⠘⠻⢛⣻⣿⣀⠀⠀⠀⠀⠀⠀⠀⠑⠅⡢⡈⠑⢍⢄⠈⢄⢮⣕⡢⣀⠑⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⢰⠀⠆⣶⣄⣰⣿⣿⣿⡿⠿⠁⠀⠀⠀⠀⢀⣀⣀⣀⣈⠊⢕⢄⠑⠂⠈⣂⣀⣀⣀⣉⣢⣌⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠸⡀⠑⢝⠿⣿⣿⣿⣷⣦⣀⣀⣀⡀⢸⣿⣿⠿⢟⡫⠭⠒⠒⢉⣉⠤⠤⠤⠤⠤⠤⠤⢄⣈⣁⠀⠑⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⢀⣀⣐⡱⠀⠀⠑⠪⣛⠿⣿⣿⣿⣿⣿⣿⡮⡫⠕⢉⣡⠤⣖⣒⣭⣵⣶⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⡍⡂⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⠒⢉⣉⣁⠤⠤⠤⢤⠀⢄⣀⠀⠉⠒⠭⡻⢿⡿⢋⠊⡠⢒⣵⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣿⣿⣿⡇⡃⢠⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⠀⡈⠢⡲⣿⣿⣿⢸⠀⢰⢰⣕⣒⠄⡀⠈⠑⠊⠁⢐⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⠛⠟⠔⠀⡊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⡄⢨⠢⡈⠪⡻⣿⣜⢀⠐⠬⡻⢿⣿⣮⣕⢄⡀⠀⢸⢸⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠛⠛⠃⡀⣸⣿⣿⣾⠞⠂⠐⠤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣔⢄⠑⢌⠢⡈⢊⠻⣷⣅⡢⣈⠑⠭⡻⢿⣷⡎⡆⠨⡘⣅⡀⠁⠀⢀⣠⣤⠤⠴⢶⣒⣛⡫⠭⠭⠕⠒⠒⠊⢀⣉⣀⣀⣀⣈⡉⠒⠢⢄⣀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠁⠑⠄⠑⢌⠢⡑⢌⠻⣿⣶⣕⠤⡈⠑⠍⡣⢣⠀⠇⡿⡫⠭⠭⠒⠒⠒⢉⣉⣁⡠⠤⠤⢔⣒⣒⣺⣭⣭⣵⣶⣶⣶⣶⣶⣮⣭⣖⡦⢄⣉⡉⠒⠒⠦⡀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠪⡢⡀⠠⡈⢂⠑⢜⢿⣿⣿⣮⣒⢄⡈⠑⠀⢉⠊⠀⠶⣒⣒⠭⢭⡽⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⢗⣒⡬⠭⠓⠂⢰⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠈⢂⠈⠢⡑⢄⠑⢝⢿⣿⣿⣷⣮⡲⢄⣀⡀⠀⠀⠀⠀⠉⠑⡙⢿⣿⣿⣿⣿⣿⣿⣿⠿⣛⣛⣛⠭⠭⠭⠭⠗⠒⠒⠋⢉⣁⣀⠤⠔⠒⠀⡸⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠐⠕⢄⠀⢄⠑⢄⠑⠝⢿⣿⣿⣿⣷⣶⣔⢄⡀⠀⠀⠀⠀⠀⠉⠒⠺⠭⠕⠒⠒⠒⠉⠤⠤⠤⡀⠒⠒⣒⠒⠊⣭⡉⠁⢀⣀⡠⠤⠊⡠⣈⠑⠤⡻ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠁⡀⠑⢄⠑⡀⠢⡙⣿⣿⣿⣿⣿⣷⣮⣖⡢⢄⠀⠀⠀⠒⠒⠒⠒⠒⠉⠍⢄⡑⠪⠥⠤⠀⠑⠻⠵⠒⠒⠋⢉⣁⣠⠤⠔⠊⢞⣒⣑⠢⠈ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠈⠪⡢⡀⠠⡨⡢⡈⠪⡻⣿⣿⣿⣿⣿⣿⣿⣷⣕⢄⡀⠑⠒⠒⠒⠒⠒⠒⠊⢉⣀⠠⠔⣒⣒⣒⡲⠦⠭⠭⠓⠒⠒⠊⢉⣉⣁⣠⠤⠤⠀ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⢄⠈⠪⡪⡢⡈⠪⡻⣿⣿⣿⣿⣿⡿⠿⠷⢎⣉⣑⡲⠶⠶⠟⠛⠛⠋⢉⣉⣉⣀⠤⠤⠤⠔⠒⠒⠒⠉⠉⠉⠁⠀⠰⠾⢟⡐⢀ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠕⢄⠐⢌⢈⠢⡈⠪⠭⠝⠒⠒⠊⢉⣉⣁⡠⠤⠤⢔⣒⣒⣪⣭⣭⣽⣶⣶⠦⠤⢄⣀⣀⡠⠤⠤⠴⠒⠒⠒⠉⢉⣉⣁⡠⢬ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡡⡀⠑⠙⢨⠀⠀⡒⣪⣭⣭⣵⣶⣶⣾⡿⠿⢟⣛⣛⡭⠭⠭⠒⠒⠚⢉⣉⣁⡠⠤⠤⠤⠒⠒⠒⠉⠉⠉⠁⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠪⡢⡈⠪⡀⠀⢑⣛⣛⡯⠭⠽⠒⠒⠊⣉⣉⡠⠤⠤⠖⠒⠒⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠪⠢⢄⣀⣀⡠⠤⠤⠖⠒⠚⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣿ \r\n"
USH_SHELL_FONT_STYLE_RESET;

// the bbos ascii header is seperate from MOTD so we can choose to print just one.
// it doesn't look right here because of the extra '\' escapes, but will magically
// look great in the terminal.
const char *bbos_header_ascii =
USH_SHELL_FONT_COLOR_YELLOW
"                                   ___          ___                     \r\n"
"          _____       _____       /  /\\        /  /\\                  \r\n"
"         /  /::\\     /  /::\\     /  /::\\      /  /:/_                \r\n"
"        /  /:/\\:\\   /  /:/\\:\\   /  /:/\\:\\    /  /:/ /\\           \r\n"
"       /  /:/~/::\\ /  /:/~/::\\ /  /:/  \\:\\  /  /:/ /::\\            \r\n"
"      /__/:/ /:/\\:/__/:/ /:/\\:/__/:/ \\__\\:\\/__/:/ /:/\\:\\         \r\n"
"      \\  \\:\\/:/~/:|  \\:\\/:/~/:|  \\:\\ /  /:/\\  \\:\\/:/~/:/      \r\n"
"       \\  \\::/ /:/ \\  \\::/ /:/ \\  \\:\\  /:/  \\  \\::/ /:/        \r\n"
"        \\  \\:\\/:/   \\  \\:\\/:/   \\  \\:\\/:/    \\__\\/ /:/       \r\n"
"         \\  \\::/     \\  \\::/     \\  \\::/       /__/:/             \r\n"
"          \\__\\/       \\__\\/       \\__\\/        \\__\\/  "
xstr(BBOS_VERSION_MAJOR) "." xstr(BBOS_VERSION_MINOR) // add version number to header
" " // add an extra space that will be replaced by BBOS_VERSION_MOD
"\r\n"
"\r\n"
USH_SHELL_FONT_COLOR_MAGENTA
USH_SHELL_FONT_STYLE_BOLD
"                [["
USH_SHELL_FONT_COLOR_CYAN
" Welcome to BreadboardOS! "
USH_SHELL_FONT_COLOR_MAGENTA
"]]\r\n"
"\r\n"
USH_SHELL_FONT_STYLE_RESET;

#endif /* MOTD_H */

================================================
FILE: cli/node_bin.c
================================================
/******************************************************************************
 * @file node_bin.c
 *
 * @brief /bin folder for the CLI, contains system executables
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <stdio.h>
#include <microshell.h>
#include "hardware_config.h"
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "FreeRTOSConfig.h"
#include "task.h"


/**
* @brief '/bin/ps' executable callback function.
*
* Prints out FreeRTOS task list information in a similar style to *nix 'ps'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void ps_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    // initialize array to hold task list ASCII output. Assume 40 bytes per task + header
    const char *tasks_header =  USH_SHELL_FONT_STYLE_BOLD
                                USH_SHELL_FONT_COLOR_BLUE
                                "                                Min\r\n"
                                "Task            State   Pri     Stack   No\r\n"
                                "------------------------------------------\r\n"
                                USH_SHELL_FONT_STYLE_RESET;
    int header_len = strlen(tasks_header);
    int tasks_maxlen = 40 * uxTaskGetNumberOfTasks();
    char *ps_msg = pvPortMalloc(header_len + tasks_maxlen);
    strcpy(ps_msg, tasks_header);

    // call FreeRTOS vTaskList API and print to CLI
    vTaskListTasks(ps_msg + header_len, tasks_maxlen); // note this is a blocking, processor intensive function
    shell_print(ps_msg);
    vPortFree(ps_msg);
}

/**
* @brief '/bin/top' executable callback function.
*
* Prints out FreeRTOS task runtime stats information in a similar style to *nix 'top'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void top_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    // initialize char array to hold task stats ASCII output. Assume 40 bytes per task + header
    const char *tasks_header =  USH_SHELL_FONT_STYLE_BOLD
                                USH_SHELL_FONT_COLOR_BLUE
                                "Task            Runtime(us)     Percentage\r\n"
                                "------------------------------------------\r\n"
                                USH_SHELL_FONT_STYLE_RESET;
    int header_len = strlen(tasks_header);
    int tasks_maxlen = 40 * uxTaskGetNumberOfTasks();
    char *top_msg = pvPortMalloc(header_len + tasks_maxlen);
    strcpy(top_msg, tasks_header);

    // call FreeRTOS vTaskGetRunTimeStats API and print to CLI
    vTaskGetRunTimeStatistics(top_msg + header_len, tasks_maxlen);
    shell_print(top_msg);
    vPortFree(top_msg);
}

/**
* @brief '/bin/free' executable callback function.
*
* Prints out the FreeRTOS heap memory manager usage statistics in a similar style to *nix 'free'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void free_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    HeapStats_t *heap_stats = pvPortMalloc(sizeof(HeapStats_t)); // structure to hold heap stats results
    vPortGetHeapStats(heap_stats);  // get the heap stats
    const int heap_stats_maxlen = 300; // just a rough estimate for dynamic mem needed
    char *heap_stats_msg = pvPortMalloc(heap_stats_maxlen);
    unsigned int total_heap_size = configTOTAL_HEAP_SIZE;
    
    // format the heap stats
    snprintf(heap_stats_msg, heap_stats_maxlen,
            USH_SHELL_FONT_STYLE_BOLD
            USH_SHELL_FONT_COLOR_BLUE
            "Memory Statistics       Bytes\r\n"
            "------------------------------\r\n"
            USH_SHELL_FONT_STYLE_RESET
            "Total heap:\t\t%u\r\n"
            "Used heap:\t\t%u\r\n"
            "Available heap:\t\t%u\r\n"
            "Largest free block:\t%u\r\n"
            "Smallest free block:\t%u\r\n"
            "Num free blocks:\t%u\r\n"
            "Min ever heap:\t\t%u\r\n"
            "Num mallocs:\t\t%u\r\n"
            "Num frees:\t\t%u\r\n",
            total_heap_size,
            (total_heap_size - heap_stats->xAvailableHeapSpaceInBytes),
            heap_stats->xAvailableHeapSpaceInBytes,
            heap_stats->xSizeOfLargestFreeBlockInBytes,
            heap_stats->xSizeOfSmallestFreeBlockInBytes,
            heap_stats->xNumberOfFreeBlocks,
            heap_stats->xMinimumEverFreeBytesRemaining,
            heap_stats->xNumberOfSuccessfulAllocations,
            heap_stats->xNumberOfSuccessfulFrees
    );
    
    shell_print(heap_stats_msg);
    vPortFree(heap_stats);
    vPortFree(heap_stats_msg);
}

/**
* @brief '/bin/df' executable callback function.
*
* Prints out the internal/onboard (program) flash memory usage in a similar style to *nix 'df'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void df_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    flash_usage_t flash_usage;
    const int flash_usage_msg_maxlen = 300;
    char *flash_usage_msg = pvPortMalloc(flash_usage_msg_maxlen);

    // get the flash usage data struct
    flash_usage = onboard_flash_usage();

    // format the flash usage printout
    snprintf(flash_usage_msg, flash_usage_msg_maxlen,
            USH_SHELL_FONT_STYLE_BOLD
            USH_SHELL_FONT_COLOR_BLUE
            "Flash Statistics                KBytes\r\n"
            "--------------------------------------\r\n"
            USH_SHELL_FONT_STYLE_RESET
            "Total flash size:\t\t%u\r\n"
            "Program binary size:\t\t%u\r\n"
            "Filesystem reserved size:\t%u\r\n"
            "Free flash space:\t\t%u\r\n",
            flash_usage.flash_total_size  / 1024,
            flash_usage.program_used_size / 1024,
            flash_usage.fs_reserved_size  / 1024,
            flash_usage.flash_free_size   / 1024
    );

    shell_print(flash_usage_msg);
    vPortFree(flash_usage_msg);
}

/**
* @brief '/bin/kill' executable callback function.
*
* Kill an RTOS task using the name given by 'ps'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void kill_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    static char err_msg[100]; // buffer for holding the error output message

    if (argc == 2) {
        struct taskman_item_t tmi;
        tmi.task = xTaskGetHandle(argv[1]);
        tmi.action = DELETE;

        // put the kill request into the taskmanager queue
        if (tmi.task != NULL) {
            taskman_request(&tmi);
        }
        else {
            sprintf(err_msg, "%s is not a currently running task", argv[1]);
            shell_print(err_msg);
        }
    }
    else {
        shell_print("command requires exactly one argument, see 'help <kill>'");
    }
}

/**
* @brief '/bin/service' executable callback function.
*
* Interact with system services (list/start/suspend/resume). Services are defined in services.h
* Note that stopping services is performed with '/bin/kill'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void service_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    char *err_msg = pvPortMalloc(100);    // buffer for holding the error output message
    char *service_msg = pvPortMalloc(32); // buffer for holding the service success message

    if (argc == 3) {
        struct taskman_item_t tmi;
        tmi.task = xTaskGetHandle(argv[2]);
        if (strcmp(argv[1], "start") == 0) { // START a service (task)
            if (tmi.task != NULL) {
                sprintf(err_msg, "%s is already running", argv[2]);
                shell_print(err_msg);
            }
            else {
                // Iterate through service_descriptors array to find the matching service index
                int i;
                for(i = 0; i < service_descriptors_length; i++) {
                    if (strcmp(argv[2], service_descriptors[i].name) == 0) {
                        service_descriptors[i].service_func(); // call the function pointer for the matching service
                        break;
                    }
                    if (i == (service_descriptors_length - 1)) {
                        sprintf(err_msg, "%s is not an available service, try 'service list'", argv[2]);
                        shell_print(err_msg);
                        break;
                    }
                }
            }
        }
        else if (strcmp(argv[1], "suspend") == 0) { // SUSPEND a running service (task)
            if (tmi.task == NULL) {
                sprintf(err_msg, "%s is not a running service, try '/bin/ps'", argv[2]);
                shell_print(err_msg);
            }
            else {
                tmi.action = SUSPEND;
                taskman_request(&tmi);
                sprintf(service_msg, "%s service suspended", argv[2]);
                shell_print(service_msg);
            }
        }
        else if (strcmp(argv[1], "resume") == 0 && tmi.task != NULL) { // RESUME a suspended service (task)
            if (tmi.task == NULL) {
                sprintf(err_msg, "%s is not a running service, try '/bin/ps'", argv[2]);
                shell_print(err_msg);
            }
            else {
                tmi.action = RESUME;
                taskman_request(&tmi);
                sprintf(service_msg, "%s service resumed", argv[2]);
                shell_print(service_msg);
            }
        }
        else {
            shell_print("command syntax error, see 'help <service>'");
        }
    }
    else if (argc == 2 && strcmp(argv[1], "list") == 0) { // LIST available services and their current state
        int i;
        const char *service_list_header =   USH_SHELL_FONT_STYLE_BOLD
                                            USH_SHELL_FONT_COLOR_BLUE
                                            "Available Services\tStatus\r\n"
                                            "------------------------------------\r\n"
                                            USH_SHELL_FONT_STYLE_RESET;
        char *service_list_msg = pvPortMalloc(strlen(service_list_header) +
                                 (service_descriptors_length *
                                 (configMAX_TASK_NAME_LEN + 16))); // add 16 bytes per line for service state and whitespace
        TaskHandle_t service_taskhandle;
        char service_state[12];

        strcpy(service_list_msg, service_list_header);

        // interate through available services, get their states from RTOS
        for (i = 0; i < service_descriptors_length; i++) {
            service_taskhandle = xTaskGetHandle(service_descriptors[i].name);
            if (service_taskhandle == NULL) {
                strcpy(service_state, "not started");
            }
            else {
                switch (eTaskGetState(service_taskhandle)) {
                    case (eRunning):
                    case (eBlocked):
                        strcpy(service_state, "running");
                        break;
                    case (eSuspended):
                        strcpy(service_state, "suspended");
                        break;
                    case (eReady):
                        strcpy(service_state, "not started");
                        break;
                    default:
                        break;
                }
            }
            
            // copy current service name into table
            strcpy(service_list_msg + strlen(service_list_msg), service_descriptors[i].name);
            // add an extra tab to short service names to make the table look better
            if (strlen(service_descriptors[i].name) < (configMAX_TASK_NAME_LEN - 8)) {
                strcpy(service_list_msg + strlen(service_list_msg), "\t");
            }
            // copy current service state into table
            sprintf(service_list_msg + strlen(service_list_msg), "\t\t%s\r\n", service_state);
        }

        shell_print(service_list_msg);
        vPortFree(service_list_msg);
    }
    else {
        shell_print("command syntax error, see 'help <service>'");
    }

    // free heap used for the string buffers
    vPortFree(err_msg);
    vPortFree(service_msg);
}

/**
* @brief '/bin/reboot' executable callback function.
*
* Reboot the MCU by forcing a watchdog timeout.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void reboot_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    if (argc == 1) { // if there are no arguments, perform a normal watchdog reboot
        shell_print("rebooting system...");
        // stop the watchdog service so we can force a watchdog reboot
        struct taskman_item_t tmi; // create a taskmanager action item
        tmi.task = xTaskGetHandle("watchdog");
        tmi.action = SUSPEND;
        if (tmi.task != NULL) { // only try to suspend watchdog if it is actually running
            taskman_request(&tmi); // put the action in taskmanager queue
            // wait here until watchdog task state is "suspended"
            while(eTaskGetState(tmi.task) != eSuspended) {}
        }

        // now we can force a watchdog reboot
        force_watchdog_reboot();
    }
    else if (argc == 2 && strcmp(argv[1], "bootloader") == 0) { // reboot to bootloader
        shell_print("rebooting to bootloader...");
        wait_here_us(1E6); // wait for a second so user can see output
        reset_to_bootloader();
    }
    else {
        shell_print("command syntax error, see 'help <reboot>'");
    }
}

// bin directory files descriptor
static const struct ush_file_descriptor bin_files[] = {
    {
        .name = "ps",                                   // file name (required)
        .description = "print running service info",    // optional file description
        .help = NULL,                                   // optional help manual
        .exec = ps_exec_callback,                       // optional execute callback
        .get_data = NULL,                               // optional get data (cat) callback
        .set_data = NULL                                // optional set data (echo) callback
    },
    {
        .name = "top",
        .description = "print runtime stats for services",
        .help = NULL,
        .exec = top_exec_callback,
        .get_data = NULL,
        .set_data = NULL 
    },
    {
        .name = "free",
        .description = "print heap memory (RAM) usage stats",
        .help = NULL,
        .exec = free_exec_callback,
        .get_data = NULL,
        .set_data = NULL 
    },
    {
        .name = "df",
        .description = "print flash memory usage stats",
        .help = NULL,
        .exec = df_exec_callback,
        .get_data = NULL,
        .set_data = NULL 
    },
    {
        .name = "kill",
        .description = "kill the service name given by 'bin/ps'",
        .help = "usage: kill <\e[3mservicename\e[0m>\r\n",
        .exec = kill_exec_callback,
        .get_data = NULL,
        .set_data = NULL 
    },
    {
        .name = "service",
        .description = "interact with available services",
        .help = "usage: service <list|start|suspend|resume> <\e[3mservicename\e[0m>\r\n",
        .exec = service_exec_callback,
        .get_data = NULL,
        .set_data = NULL 
    },
    {
        .name = "reboot",
        .description = "reboot device",
        .help = "usage: reboot            - normal mode\r\n"     
                "       reboot bootloader - UF2 mode\r\n",
        .exec = reboot_exec_callback,
        .get_data = NULL,
        .set_data = NULL 
    }
};

// bin directory handler
static struct ush_node_object bin;

void shell_bin_mount(void)
{
    // mount bin directory
    ush_node_mount(&ush, "/bin", &bin, bin_files, sizeof(bin_files) / sizeof(bin_files[0]));
}


================================================
FILE: cli/node_dev.c
================================================
/******************************************************************************
 * @file node_dev.c
 *
 * @brief /dev folder for the CLI, contains hardware peripheral access functions
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <microshell.h>
#include "hardware_config.h"
#include "shell.h"
#include "cli_utils.h"
#include "services.h"
#include "service_queues.h"
#include "lfs.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"


/**
* @brief '/dev/led' get data callback function.
*
* Returns the current state of the onboard LED using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the LED state data (0=OFF or 1=ON)
*/
size_t led_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    bool led_state;
    static char led_state_msg[16];

    if (onboard_led_get() == true) {
        strcpy(led_state_msg, "LED STATE ON\r\n");
    } else {
        strcpy(led_state_msg, "LED STATE OFF\r\n");
    }

    // set pointer to data
    *data = (uint8_t*)led_state_msg;
    // return data size
    return strlen(led_state_msg);
}

/**
* @brief '/dev/led' set data callback function.
*
* Sets the state of the onboard LED using the built-in 'echo' CLI command.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void led_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
    if (strcmp(data, "0") == 0) {
        onboard_led_set(0);
    } else if (strcmp(data, "1") == 0) {
        onboard_led_set(1);
    } else {
        shell_print("set value should be <0> or <1>");
    }
}

/**
* @brief '/dev/time' get data callback function.
*
* Returns the microseconds value of the system timer using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the timer value data
*/
size_t time_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    static char time_msg[57]; // text string plus 14 digits for timer value, ~3 years before rollover
    // of course... the 64-bit timer value can count to 585 thousand years,
    // in case we want to ever throw a couple more bytes at it for longetivity
    snprintf(time_msg, 57, "current system timer value: %llu microseconds\r\n", get_time_us());

    // set pointer to data
    *data = (uint8_t*)time_msg;
    // return data size
    return strlen(time_msg);
}

/**
* @brief '/dev/gpio' executable callback function.
*
* Read/write value (0, 1) to individual GPIO pins. Note that the 'GPIO num'
* supplied to the command should match the GPIO_x_MCU_ID index number configured
* in hardware_config.h.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void gpio_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    bool syntax_err = false;
    char *gpio_msg = pvPortMalloc(20);

    if (strcmp(argv[1], "read") == 0 && argc == 3) {
        int gpio_index = atoi(argv[2]);
        // make sure the GPIO number provided is in range
        if (gpio_index < GPIO_COUNT) {
            sprintf(gpio_msg, "GPIO_%d value: %d", gpio_index, gpio_read_single(gpio_index));
        }
        else {
            syntax_err = true;
            shell_print("pin is not a configured GPIO");
        }
    }
    else if (strcmp(argv[1], "write") == 0 && argc == 4) {
        int gpio_index = atoi(argv[2]);
        int gpio_val = atoi(argv[3]);

        // make sure the value is 0 or 1
        if (gpio_val == 0 || gpio_val == 1) {
            // make sure the GPIO number provided is in range
            if (gpio_index < GPIO_COUNT && gpio_settings.gpio_direction[gpio_index] == GPIO_OUT) {
                gpio_write_single(gpio_index, gpio_val);
                sprintf(gpio_msg, "GPIO_%d set to %d", gpio_index, gpio_read_single(gpio_index));
            }
            else {
                syntax_err = true;
                shell_print("pin is not a configured GPIO output");
            }
        }
        else {
            syntax_err = true;
            shell_print("value must be 0 or 1");
        }
    }
    else {
        syntax_err = true;
        shell_print("command syntax error, see 'help <gpio>'");
    }

    if (!syntax_err) {
        shell_print(gpio_msg);
    }
    vPortFree(gpio_msg);
}

/**
* @brief '/dev/gpio' get data callback function.
*
* Prints the value of all configured GPIOs and their directions.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t gpio_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    uint32_t gpio_values;
    char *gpio_states_msg = pvPortMalloc(80 + 20 * GPIO_COUNT);
    char direction[4];
    
    strcpy(gpio_states_msg,
            USH_SHELL_FONT_STYLE_BOLD
            USH_SHELL_FONT_COLOR_BLUE
            "GPIO_ID\t\tDirection\tValue\r\n"
            "-------------------------------------\r\n"
            USH_SHELL_FONT_STYLE_RESET);
    
    for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
        if (gpio_settings.gpio_direction[gpio_num] == GPIO_IN) {
            strcpy(direction, "IN");
        }
        else {
            strcpy(direction, "OUT");
        }
        sprintf(gpio_states_msg + strlen(gpio_states_msg),
                    "GPIO_%d\t\t%s\t\t%d\r\n",
                    gpio_num,
                    direction,
                    gpio_read_single(gpio_num)
                );
    }
    
    // print directly from this function rather than returning pointer to uShell.
    // this allows us to malloc/free rather than using static memory
    shell_print(gpio_states_msg);
    vPortFree(gpio_states_msg);
    // return null since we already printed output
    return 0;
}

/**
* @brief '/dev/i2c0' executable callback function.
*
* Interact with I2C bus 0 by reading/writing raw bytes targetted to a specific
* client device address.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void i2c0_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    bool syntax_err = false;

    if (argc == 4 &&
        (strcmp(argv[1], "read") == 0 || strcmp(argv[1], "write") == 0) &&
        (argv[2][0] == '0' && argv[2][1] == 'x')) {

        // get i2c target address
        uint8_t addr = strtol(&argv[2][2], NULL, 16);

        // i2c read
        if (strcmp(argv[1], "read") == 0) {
            size_t nbytes = strtol(argv[3], NULL, 10);
            uint8_t rxdata[nbytes];

            // read data from i2c bus
            if (i2c0_read(addr, rxdata, nbytes) > 0) {
                char *rx_msg_prefix = "Received: 0x";
                // allocate heap memory for printable rx data
                char *rx_msg = pvPortMalloc(nbytes * 3 + strlen(rx_msg_prefix));
                sprintf(rx_msg, rx_msg_prefix);

                for (int rx_byte = 0; rx_byte < nbytes; rx_byte++) {
                    sprintf(rx_msg + strlen(rx_msg_prefix) + (rx_byte * 3), "%02x ", rxdata[rx_byte]);
                }

                shell_print(rx_msg);
                vPortFree(rx_msg);
            }
            else {
                shell_print("No response");
            }
        }

        // i2c write
        if (strcmp(argv[1], "write") == 0) {
            // guess the length of the data to write before actually checking
            size_t nbytes = (strlen(argv[3]) - 2) / 2;
            uint8_t txdata[nbytes];
            // convert string to data and get real number of bytes
            nbytes = hex_string_to_byte_array(argv[3], txdata);
            // write data on i2c bus if formatted correctly
            if (nbytes > 0) {
                int bytes_written = i2c0_write(addr, txdata, nbytes);
                if(bytes_written > 0) {
                    char *tx_msg = pvPortMalloc(16);
                    sprintf(tx_msg, "Wrote %d bytes", bytes_written);
                    shell_print(tx_msg);
                    vPortFree(tx_msg);
                }
                else {
                    shell_print("Error writing to bus");
                }
            }
            else {syntax_err = true;}
        }
    }
    else {syntax_err = true;}

    if (syntax_err) {
        shell_print("command syntax error, see 'help <i2c0>'");
    }
}

/**
* @brief '/dev/i2c0' get data callback function.
*
* Sweep through all 7-bit I2C addresses, to see if any client devices are present
* on the I2C bus. Print out a table that looks like this:
*
* I2C Bus Scan
*   0 1 2 3 4 5 6 7 8 9 A B C D E F
* 0
* 1     @
* 2
* 3         @
* 4
* 5
* 6
* 7
*
* E.g. if peripheral device addresses 0x12 and 0x34 were acknowledged.
* Functional idea borrowed from:
* https://github.com/raspberrypi/pico-examples/blob/master/i2c/bus_scan/bus_scan.c
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t i2c0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    // use malloc rather than passing a pointer to a static char back to uShell,
    // since it is a rather large array
    char *i2c0_scan_msg = pvPortMalloc(400);

    sprintf(i2c0_scan_msg,  USH_SHELL_FONT_STYLE_BOLD
                            USH_SHELL_FONT_COLOR_BLUE
                            "I2C0 Bus Scan\r\n"
                            USH_SHELL_FONT_STYLE_RESET
                            "   0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n");

    for (int addr = 0; addr < (1 << 7); ++addr) {
        if (addr % 16 == 0) {
            sprintf(i2c0_scan_msg + strlen(i2c0_scan_msg), "%02x ", addr);
        }

        // Perform a 1-byte dummy read from the probe address. If a peripheral device
        // acknowledges this address, the function returns the number of bytes
        // transferred. If the address byte is ignored, the function returns
        // -1.
        int ret;
        uint8_t rxdata;
        if ((addr & 0x78) == 0 || (addr & 0x78) == 0x78) // skip over i2c "reserved" 0000xxx or 1111xxx addresses
            ret = -1;
        else
            ret = i2c0_read(addr, &rxdata, 1);

        sprintf(i2c0_scan_msg + strlen(i2c0_scan_msg), ret < 0 ? "." : "@");
        sprintf(i2c0_scan_msg + strlen(i2c0_scan_msg), addr % 16 == 15 ? "\r\n" : " ");
    }

    // print directly from this function rather than returning pointer to uShell.
    // this allows us to malloc/free rather than using static memory
    shell_print(i2c0_scan_msg);
    vPortFree(i2c0_scan_msg);
    // return null since we already printed output
    return 0;
}

/**
* @brief '/dev/spi0' executable callback function.
*
* Interact with SPI bus 0 by reading/writing to a specific register address.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void spi0_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    bool syntax_err = false;

    if (argc == 4 &&
        (strcmp(argv[1], "read") == 0 || strcmp(argv[1], "write") == 0) &&
        (argv[2][0] == '0' && argv[2][1] == 'x')) {

        // get spi target register address
        uint8_t addr = strtol(&argv[2][2], NULL, 16);

        // spi read
        if (strcmp(argv[1], "read") == 0) {
            size_t nbytes = strtol(argv[3], NULL, 10);
            uint8_t rxdata[nbytes];

            // read data from spi bus
            if (spi0_read_registers(SPI0_TARGET_DEV_0_CS, addr, rxdata, nbytes) > 0) {
                char *rx_msg_prefix = "Received: 0x";
                // allocate heap memory for printable rx data
                char *rx_msg = pvPortMalloc(nbytes * 3 + strlen(rx_msg_prefix));
                sprintf(rx_msg, rx_msg_prefix);

                for (int rx_byte = 0; rx_byte < nbytes; rx_byte++) {
                    sprintf(rx_msg + strlen(rx_msg_prefix) + (rx_byte * 3), "%02x ", rxdata[rx_byte]);
                }

                shell_print(rx_msg);
                vPortFree(rx_msg);
            }
            else {
                shell_print("No response");
            }
        }

        // spi write
        if (strcmp(argv[1], "write") == 0) {
            // guess the length of the data to write before actually checking
            size_t nbytes = (strlen(argv[3]) - 2) / 2;
            uint8_t txdata[nbytes];
            // convert string to data and get real number of bytes
            nbytes = hex_string_to_byte_array(argv[3], txdata);
            // write data on i2c bus if formatted correctly
            if (nbytes == 1) { // for now we are only expecting to write a single byte to a single register addr
                int bytes_written = spi0_write_register(SPI0_TARGET_DEV_0_CS, addr, txdata[0]);
                if(bytes_written > 0) {
                    char *tx_msg = pvPortMalloc(16);
                    sprintf(tx_msg, "Wrote %d bytes", bytes_written);
                    shell_print(tx_msg);
                    vPortFree(tx_msg);
                }
                else {
                    shell_print("Error writing to bus");
                }
            }
            else {syntax_err = true;}
        }
    }
    else {syntax_err = true;}

    if (syntax_err) {
        shell_print("command syntax error, see 'help <i2c0>'");
    }
}

/**
* @brief '/dev/spi0' get data callback function.
*
* Interrogates any devices on SPI bus 0 for its ID number.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t spi0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    // read out the device ID of expected target on the bus
    // device ID specified in hardware_config.h
    // if more than one device is attached to the bus, this can be made into a loop through chip selects
    uint8_t device_id;
    int bytes_read = spi0_read_registers(SPI0_TARGET_DEV_0_CS, SPI0_TARGET_DEV_0_ID_REG, &device_id, 1);
    char *device_id_msg = pvPortMalloc(30);
    if (bytes_read == 1 && device_id != 0) { // assuming device ID is never 0, that would be weird
        sprintf(device_id_msg, "found device id: 0x%x\r\n", device_id);
    }
    else {
        sprintf(device_id_msg, "no response on SPI\r\n");
    }
    
    // print directly from this function rather than returning pointer to uShell.
    // this allows us to malloc/free rather than using static memory
    shell_print(device_id_msg);
    vPortFree(device_id_msg);

    // return null since we already printed output
    return 0;
}

/**
* @brief '/dev/adc' get data callback function.
*
* Reads the value of the ADC peripheral and prints the value converted to a
* voltage. See the settings in hardware_config.h for which channel/pin this
* corresponds to.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the ADC value
*/
size_t adc0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    static char adc_val[9] = {'\0'};

    sprintf(adc_val, "%.3fV\r\n", read_adc(0));

    // set pointer to data
    *data = (uint8_t*)adc_val;
    // return data size
    return strlen(adc_val);
}

/**
* @brief '/dev/usb0' get data callback function.
*
* Reads any bytes that are available in the USB device RX buffer, and attempts
* to print them as a string. This of course assumes the other end is sending a
* proper string.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t usb0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    uint8_t *usb_rx_data = pvPortMalloc(CFG_TUD_CDC_RX_BUFSIZE);
    usb_rx_data[0] = '\0'; // make sure initial strlen is zero
    
    while (usb_data_get(usb_rx_data + strlen(usb_rx_data))) {
        // try to get all data from usb rx queue
    }
    
    // print directly from this function rather than returning pointer to uShell.
    // this allows us to malloc/free rather than using static memory
    shell_print(usb_rx_data);
    vPortFree(usb_rx_data);
    // return null since we already printed output
    return 0;
}

/**
* @brief '/dev/usb0' set data callback function.
*
* Puts bytes (string) into the USB device TX buffer to send to the host endpoint.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void usb0_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
    usb_data_put(data);
}

/**
* @brief '/dev/uart1' get data callback function.
*
* Reads any bytes that are available in the auxilliary UART RX FIFO, and attempts
* to print them as a string.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the UART data byte array
*/
size_t uart1_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    static uint8_t uart_rx_data[UART_RX_FIFO_SIZE_AUX];
    uart_rx_data[0] = '\0'; // make sure initial strlen is zero

    // set pointer to data
    *data = (uint8_t*)uart_rx_data;

    if (aux_uart_read(uart_rx_data, UART_RX_FIFO_SIZE_AUX) > 0) {
        // return data size
        return strlen(uart_rx_data);
    }
    else {
        return 0;
    }
}

/**
* @brief '/dev/uart1' set data callback function.
*
* Puts bytes (string) into the UART TX FIFO.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void uart1_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
    aux_uart_write(data, size);
}

// dev directory files descriptor
static const struct ush_file_descriptor dev_files[] = {
#if HW_USE_ONBOARD_LED
    {
        .name = "led",                          // file name (required)
        .description = "onboard LED",           // optional file description
        .help = NULL,                           // optional help manual
        .exec = NULL,                           // optional execute callback
        .get_data = led_get_data_callback,      // optional get data (cat) callback
        .set_data = led_set_data_callback       // optional set data (echo) callback
    },
#endif /* HW_USE_ONBOARD_LED */
    {
        .name = "time",
        .description = "system timer",
        .help = NULL,
        .exec = NULL,
        .get_data = time_get_data_callback,
        .set_data = NULL
    },
#if HW_USE_GPIO
    {
        .name = "gpio",
        .description = "GPIO pins",
        .help = "usage: gpio <read>  <\e[3mGPIO num\e[0m>\r\n"
                "            <write> <\e[3mGPIO num\e[0m> <\e[3mvalue\e[0m>\r\n"
                "\r\n"
                "       cat gpio - print all GPIO states\r\n",
        .exec = gpio_exec_callback,
        .get_data = gpio_get_data_callback,
        .set_data = NULL
    },
#endif /* HW_USE_GPIO */
#if HW_USE_I2C0
    {
        .name = "i2c0",
        .description = "I2C bus 0",
        .help = "usage: i2c0 <read>  <\e[3maddress(0x...)\e[0m> <\e[3mnbytes\e[0m>\r\n"
                "            <write> <\e[3maddress(0x...)\e[0m> <\e[3mdata(0x...)\e[0m>\r\n"
                "\r\n"
                "       cat i2c0 - scan i2c0 bus and print a table of responding addresses\r\n",
        .exec = i2c0_exec_callback,
        .get_data = i2c0_get_data_callback,
        .set_data = NULL
    },
#endif /* HW_USE_I2C0 */
#if HW_USE_SPI0
    {
        .name = "spi0",
        .description = "SPI bus 0",
        .help = "usage: spi0 <read>  <\e[3mreg addr(0x...)\e[0m> <\e[3mnbytes\e[0m>\r\n"
                "            <write> <\e[3mreg addr(0x...)\e[0m> <\e[3mdata byte(0x...)\e[0m>\r\n"
                "\r\n"
                "       cat spi0 - read device IDs of all devices in chip select table\r\n",
        .exec = spi0_exec_callback,
        .get_data = spi0_get_data_callback,
        .set_data = NULL
    },
#endif /* HW_USE_SPI0 */
#if HW_USE_ADC && ADC0_INIT
    {
        .name = "adc0",
        .description = "Analog-to-Digital Converter",
        .help = NULL,
        .exec = NULL,
        .get_data = adc0_get_data_callback,
        .set_data = NULL
    },
#endif /* HW_USE_ADC && ADC0_INIT */
#if HW_USE_USB && !CLI_USE_USB
    {
        .name = "usb0",
        .description = "USB data interface",
        .help = NULL,
        .exec = NULL,
        .get_data = usb0_get_data_callback,
        .set_data = usb0_set_data_callback
    },
#endif /* HW_USE_USB && !CLI_USE_USB */
#if HW_USE_AUX_UART
    {
        .name = "uart1",
        .description = "auxilliary UART",
        .help = NULL,
        .exec = NULL,
        .get_data = uart1_get_data_callback,
        .set_data = uart1_set_data_callback
    }
#endif /* HW_USE_AUX_UART */
};

// dev directory handler
static struct ush_node_object dev;

void shell_dev_mount(void)
{
    // mount dev directory
    ush_node_mount(&ush, "/dev", &dev, dev_files, sizeof(dev_files) / sizeof(dev_files[0]));
}

================================================
FILE: cli/node_etc.c
================================================
/******************************************************************************
 * @file node_etc.c
 *
 * @brief /etc folder for the CLI, contains various system configurations
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <microshell.h>
#include "shell.h"
#include "motd.h"
#include "FreeRTOS.h"


/**
* @brief '/etc/motd' get data callback function.
*
* Prints out the Message of the Day (MOTD) character array that is stored in
* motd.h.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t motd_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    // copy motd string into ram
    char *motd_msg = pvPortMalloc(strlen(motd_ascii));
    strcpy(motd_msg, motd_ascii);
    // print to cli
    shell_print(motd_msg);
    vPortFree(motd_msg);

    // return null since we already printed output
    return 0;
}

// etc directory files descriptor
static const struct ush_file_descriptor etc_files[] = {
    {
        .name = "motd",                         // file name (required)
        .description = "message of the day",    // optional file description
        .help = NULL,                           // optional help manual
        .exec = NULL,                           // optional execute callback
        .get_data = motd_get_data_callback,     // optional get data (cat) callback
        .set_data = NULL                        // optional set data (echo) callback
    }
};

// etc directory handler
static struct ush_node_object etc;

void shell_etc_mount(void)
{
    // mount the /mnt directory
    ush_node_mount(&ush, "/etc", &etc, etc_files, sizeof(etc_files) / sizeof(etc_files[0]));
}

================================================
FILE: cli/node_lib.c
================================================
/******************************************************************************
 * @file node_lib.c
 *
 * @brief /lib folder for the CLI, contains hardware peripheral driver interfaces
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <microshell.h>
#include "hardware_config.h"
#include "device_drivers.h"
#include "shell.h"


/**
* @brief '/lib/bme280' get data callback function.
*
* Print readings from Bosch BME280 environmental sensor peripheral.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the sensor readings string
*/
size_t bme280_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    bme280_sensor_data_t sensor_data;
    static char bme280_data_msg[60];

    // read compensation data from device and then get sensor readings
    if(bme280_read_sensors(&bme280_compensation_params_glob, &sensor_data)) {
        sprintf(bme280_data_msg,
                "Temp:\t %.1f degC\r\n"
                "Hum:\t %.1f%%\r\n"
                "Pres:\t %.1f hPa\r\n",
                sensor_data.temperature,
                sensor_data.humidity,
                sensor_data.pressure);
    }
    else {
        sprintf(bme280_data_msg, "error reading sensor\r\n");
    }

    // set pointer to data
    *data = (uint8_t*)bme280_data_msg;
    // return data size
    return strlen(bme280_data_msg);
}

/**
* @brief '/lib/mcp4725' get data callback function.
*
* Prints the current MCP4725 DAC setting converted to a voltage.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the voltage setting string
*/
size_t mcp4725_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    static char mcp4725_data_msg[30];
    float voltage;

    voltage = mcp4725_get_voltage();
    if (voltage >= 0) {
        sprintf(mcp4725_data_msg, "%.2fV\r\n", voltage);
    }
    else {
        strcpy(mcp4725_data_msg, "error reading DAC value\r\n");
    }

    // set pointer to data
    *data = (uint8_t*)mcp4725_data_msg;
    // return data size
    return strlen(mcp4725_data_msg);
}

/**
* @brief '/lib/mcp4725' set data callback function.
*
* Sets the DAC output register given a user-defined voltage.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void mcp4725_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
    float voltage_setting = strtof(data, NULL);
    if (voltage_setting >= 0 && voltage_setting <= MCP4725_VDD) {
        mcp4725_set_voltage(voltage_setting, false);
    }
}

// lib directory files descriptor
static const struct ush_file_descriptor lib_files[] = {
    #if HW_USE_SPI0 && BME280_ATTACHED
    {
        .name = "bme280",                                       // file name (required)
        .description = "Bosch BME280 environmental sensor",     // optional file description
        .help = NULL,                                           // optional help manual
        .exec = NULL,                                           // optional execute callback
        .get_data = bme280_get_data_callback,                   // optional get data (cat) callback
        .set_data = NULL                                        // optional set data (echo) callback
    },
    #endif
    #if HW_USE_I2C0 && MCP4725_ATTACHED
    {
        .name = "mcp4725",
        .description = "Microchip MCP4725 digital-to-analog converter",
        .help = "usage: cat mcp4725 - get current voltage setting of DAC\r\n"
                "       echo \e[3mx.xxx\e[0m > mcp4725 - set DAC voltage\r\n",
        .exec = NULL,
        .get_data = mcp4725_get_data_callback,
        .set_data = mcp4725_set_data_callback
    }
    #endif
};

// lib directory handler
static struct ush_node_object lib;

void shell_lib_mount(void)
{
    // mount dev directory
    ush_node_mount(&ush, "/lib", &lib, lib_files, sizeof(lib_files) / sizeof(lib_files[0]));
}

================================================
FILE: cli/node_mnt.c
================================================
/******************************************************************************
 * @file node_mnt.c
 *
 * @brief /mnt folder for the CLI, contains littlefs filesystem(s)
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdbool.h>
#include <string.h>
#include <microshell.h>
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"


/**
* @brief '/mnt/flash0' executable callback function.
*
* Interact with the onboard flash0 filesystem (list directories, read/write
* files, etc.), by putting the action request into the storagemanager's queue
* for servicing, and then reading out any resulting data from the global
* storagemanager data structure.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void flash0_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    bool syntax_err = false;
    struct storman_item_t smi;

    // make sure storagemanager is actually running
    if (xTaskGetHandle(xstr(SERVICE_NAME_STORMAN)) == NULL) {
        shell_print("error, " xstr(SERVICE_NAME_STORMAN) " service is not running");
    }
    else if (argc > 1) {
        if (strcmp(argv[1], "lsdir") == 0 && argc == 3) {
            smi.action = LSDIR;
            strcpy(smi.sm_item_name, argv[2]);
            storman_request(&smi);
            // wait for storagemanager to provide semaphore indicating data is ready
            if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
                shell_print(smi_glob.sm_item_data);
            }
        }
        else if (strcmp(argv[1], "mkdir") == 0 && argc == 3) {
            smi.action = MKDIR;
            strcpy(smi.sm_item_name, argv[2]);
            storman_request(&smi);
        }
        else if (strcmp(argv[1], "rmdir") == 0 && argc == 3) {
            smi.action = RMDIR;
            strcpy(smi.sm_item_name, argv[2]);
            storman_request(&smi);
        }
        else if (strcmp(argv[1], "mkfile") == 0 && argc == 3) {
            smi.action = MKFILE;
            strcpy(smi.sm_item_name, argv[2]);
            storman_request(&smi);
        }
        else if (strcmp(argv[1], "rmfile") == 0 && argc == 3) {
            smi.action = RMFILE;
            strcpy(smi.sm_item_name, argv[2]);
            storman_request(&smi);
        }
        else if (strcmp(argv[1], "dumpfile") == 0 && argc == 3) {
            smi.action = DUMPFILE;
            strcpy(smi.sm_item_name, argv[2]);
            storman_request(&smi);
            // wait for storagemanager to provide semaphore indicating data is ready
            if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
                shell_print(smi_glob.sm_item_data);
            }
        }
        else if (strcmp(argv[1], "readfile") == 0 && argc == 5) {
            smi.action = READFILE;
            strcpy(smi.sm_item_name, argv[2]);
            smi.sm_item_offset = (lfs_soff_t)strtol(argv[3], NULL, 10);
            smi.sm_item_size = strtol(argv[4], NULL, 10);
            storman_request(&smi);
            // wait for storagemanager to provide semaphore indicating data is ready
            if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
                shell_print(smi_glob.sm_item_data);
            }
        }
        else if (strcmp(argv[1], "writefile") == 0 && argc == 4) {
            smi.action = WRITEFILE;
            strcpy(smi.sm_item_name, argv[2]);
            strcpy(smi.sm_item_data, argv[3]);
            storman_request(&smi);
        }
        else if (strcmp(argv[1], "appendfile") == 0 && argc == 4) {
            smi.action = APPENDFILE;
            strcpy(smi.sm_item_name, argv[2]);
            strcpy(smi.sm_item_data, argv[3]);
            storman_request(&smi);
        }
        else if (strcmp(argv[1], "filestat") == 0 && argc == 3) {
            smi.action = FILESTAT;
            strcpy(smi.sm_item_name, argv[2]);
            storman_request(&smi);
            // wait for storagemanager to provide semaphore indicating data is ready
            if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
                shell_print(smi_glob.sm_item_data);
            }
        }
        else if (strcmp(argv[1], "fsstat") == 0 && argc == 2) {
            smi.action = FSSTAT;
            storman_request(&smi);
            // wait for storagemanager to provide semaphore indicating data is ready
            if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
                shell_print(smi_glob.sm_item_data);
            }
        }
        else if (strcmp(argv[1], "format") == 0 && argc == 2) {
            smi.action = FORMAT;
            storman_request(&smi);
            // wait for storagemanager to provide semaphore indicating data is ready
            if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
                shell_print(smi_glob.sm_item_data);
            }
        }
        else if (strcmp(argv[1], "unmount") == 0 && argc == 2) {
            smi.action = UNMOUNT;
            storman_request(&smi);
            shell_print("/mnt folder unmounted, restart storagemanager service to re-mount");
        }
        else {syntax_err = true;}
    }
    else {syntax_err = true;}

    if (syntax_err) {
        shell_print("command syntax error, see 'help <flash0>'");
    }
}

// mnt directory files descriptor
static const struct ush_file_descriptor mnt_files[] = {
    {
        .name = "flash0",
        .description = "onboard flash filesystem",
        .help = "usage: flash0 <lsdir|mkdir|rmdir|mkfile|rmfile|dumpfile> <\e[3mname\e[0m>,\r\n"
                "              <readfile> <\e[3mname\e[0m> <\e[3moffset\e[0m> <\e[3mlength\e[0m>,\r\n"
                "              <writefile|appendfile> <\e[3mname\e[0m> <\e[3mdata\e[0m>,\r\n"
                "              <filestat> <\e[3mname\e[0m>,\r\n"
                "              <fsstat>\r\n"
                "              <format>\r\n"
                "              <unmount>\r\n",
        .exec = flash0_exec_callback,
        .get_data = NULL,
        .set_data = NULL
    }
};

// mnt directory handler
static struct ush_node_object mnt;

void shell_mnt_mount(void)
{
    // mount the /mnt directory
    ush_node_mount(&ush, "/mnt", &mnt, mnt_files, sizeof(mnt_files) / sizeof(mnt_files[0]));
}

void shell_mnt_unmount(void)
{
    // unmount the /mnt directory
    ush_node_unmount(&ush, "/mnt");
}

================================================
FILE: cli/node_net.c
================================================
/******************************************************************************
 * @file node_net.c
 *
 * @brief /net folder for the CLI, contains network tools and network hardware 
 *        interaction functions
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 01-24-2025
 *
 * @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 *
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <microshell.h>
#include "hardware_config.h"
#include "shell.h"
#include "cli_utils.h"
#include "services.h"
#include "service_queues.h"
#include "lfs.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"


static void wifi_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    netman_action_t nma;

    if (argc == 4) {
        if (strcmp(argv[1], "setauth") == 0) { // set the wifi network authentication parameters
            // create wifi_auth file with new credentials (or overwrite existing)
            struct storman_item_t smi;
            smi.action = WRITEFILE;
            strcpy(smi.sm_item_name, "wifi_auth");
            sprintf(smi.sm_item_data, "%s,%s", argv[2], argv[3]);
            storman_request(&smi);
            shell_print("wifi network credentials set");
        }
        else {
            shell_print("command syntax error, see 'help <wifi>'");
        }
    }
    else if (argc == 2) {
        if (strcmp(argv[1], "connect") == 0) { // connect to the wifi network defined by 'setauth'
            nma = NETJOIN;
            netman_request(nma);
        }
        else if (strcmp(argv[1], "disconnect") == 0) { // disconnect from the wifi network
            nma = NETLEAVE;
            netman_request(nma);
        }
        else {
            shell_print("command syntax error, see 'help <wifi>'");
        }
    }
    else {
        shell_print("command syntax error, see 'help <wifi>'");
    }
}

// net directory files descriptor
static const struct ush_file_descriptor net_files[] = {
#if HW_USE_WIFI
    {
        .name = "wifi",                                        // file name (required)
        .description = "WiFi network interface",               // optional file description
        .help = "usage: wifi <connect|disconnect>\r\n"         // optional help manual
                "       wifi setauth <\e[3mssid\e[0m>"         // optional help manual
                                   " <\e[3mpassword\e[0m>\r\n",
        .exec = wifi_exec_callback,                            // optional execute callback
        .get_data = NULL,                                      // optional get data (cat) callback
        .set_data = NULL                                       // optional set data (echo) callback
    },
#endif /* HW_USE_WIFI */
};

// net directory handler
static struct ush_node_object net;

void shell_net_mount(void)
{
    // mount net directory
    ush_node_mount(&ush, "/net", &net, net_files, sizeof(net_files) / sizeof(net_files[0]));
}

================================================
FILE: cli/node_proc.c
================================================
/******************************************************************************
 * @file node_proc.c
 *
 * @brief /proc folder for the CLI, contains system information
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <stdint.h>
#include <microshell.h>
#include <lfs.h>
#include <git.h>
#include "hardware_config.h"
#include "shell.h"
#include "FreeRTOS.h"
#include "task.h"
#include "version.h"


/**
* @brief '/proc/mcuinfo' get data callback function.
*
* Returns infomation about the MCU by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t mcuinfo_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    // use malloc rather than passing a pointer to a static char back to uShell,
    // since it is a rather large array
    char *mcuinfo_msg = pvPortMalloc(250);
    const char* scheduler_state[] = {"suspended", "not started", "running"};
    
    sprintf(mcuinfo_msg, "MCU: " xstr(MCU_NAME) ", running at %ld Hz\r\n", get_sys_clk_hz());
    sprintf(mcuinfo_msg + strlen(mcuinfo_msg), "Chip version: %d\r\n", get_chip_version()); // MCU silicon version
    sprintf(mcuinfo_msg + strlen(mcuinfo_msg), "ROM version: %d\r\n", get_rom_version());   // MCU chip ROM version
    sprintf(mcuinfo_msg + strlen(mcuinfo_msg), "Board: %s\r\n", xstr(BOARD));
    sprintf(mcuinfo_msg + strlen(mcuinfo_msg),
            "RTOS scheduler is [ %s ], %ld tasks registered\r\n",
            scheduler_state[(int32_t)xTaskGetSchedulerState()], uxTaskGetNumberOfTasks());
    sprintf(mcuinfo_msg + strlen(mcuinfo_msg), xstr(PROJECT_NAME) " CLI running on core: %d\r\n", get_core());

    // print directly from this function rather than returning pointer to uShell.
    // this allows us to malloc/free rather than using static memory
    shell_print(mcuinfo_msg);
    vPortFree(mcuinfo_msg);
    // return null since we already printed output
    return 0;
}

/**
* @brief '/proc/version' get data callback function.
*
* Returns version information by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t version_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    // use malloc rather than passing a pointer to a static char array back to uShell,
    // since it is a rather large array
    char *version_msg = pvPortMalloc(400);

    sprintf(version_msg, "" USH_SHELL_FONT_STYLE_BOLD USH_SHELL_FONT_COLOR_BLUE);
    sprintf(version_msg + strlen(version_msg), xstr(PROJECT_NAME) " version:\t" xstr(PROJECT_VERSION) "\r\n"); // Top level project version
    sprintf(version_msg + strlen(version_msg), "" USH_SHELL_FONT_STYLE_RESET);
    sprintf(version_msg + strlen(version_msg), "Build branch:\t\t%s\r\n",   git_Branch());               // Git commit date of build
    sprintf(version_msg + strlen(version_msg), "Git commit date:\t%s\r\n",  git_CommitDate());           // Git commit date of build
    sprintf(version_msg + strlen(version_msg), "Git commit hash:\t%s\r\n",  git_CommitSHA1());           // Git commit hash of build
    sprintf(version_msg + strlen(version_msg), "%s version:\t%d.%d%c\r\n",  BBOS_NAME,                   // BreadboardOS version          
                                                                            BBOS_VERSION_MAJOR,
                                                                            BBOS_VERSION_MINOR,
                                                                            BBOS_VERSION_MOD);
    sprintf(version_msg + strlen(version_msg), "FreeRTOS version:\t%s\r\n", tskKERNEL_VERSION_NUMBER);   // FreeRTOS Kernel version
    sprintf(version_msg + strlen(version_msg), "%s version:\t%s\r\n", USH_NAME, USH_VERSION);            // microshell version
    sprintf(version_msg + strlen(version_msg), "littlefs version:\t%d.%d\r\n",  LFS_VERSION_MAJOR,       // littlefs version
                                                                                LFS_VERSION_MINOR);

    // print directly from this function rather than returning pointer to uShell.
    // this allows us to malloc/free rather than using static memory
    shell_print(version_msg);
    vPortFree(version_msg);
    // return null since we already printed output
    return 0;
}

/**
* @brief '/proc/resetreason' get data callback function.
*
* Returns the reason for the last reset by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the reset reason string
*/
size_t resetreason_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    // get reset reason string using global reset reason type captured at boot
    char *reset_reason_string = get_reset_reason_string(last_reset_reason);
    // copy the pointer to the reset reason string
    *data = (uint8_t*)reset_reason_string;
    // return data size
    return strlen(reset_reason_string);
}

/**
* @brief '/proc/uptime' get data callback function.
*
* Print the system uptime (days, hours, minutes, seconds) since last system boot.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t uptime_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    // get current microsecond timer value
    uint64_t current_time_us = get_time_us();

    // calculate time divisions, i.e. maths
    uint16_t num_seconds = (uint16_t) (current_time_us / 1E6) % 60;
    uint16_t num_minutes = (uint16_t) (((current_time_us / 1E6) - num_seconds) / 60) % 60;
    uint16_t num_hours   = (uint16_t) (((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) % 24;
    uint16_t num_days    = (uint16_t) ((((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) - num_hours) / 24;

    // build formatted string
    char *uptime_msg = pvPortMalloc(55);
    snprintf(uptime_msg, 55, "System up %d days, %d hours, %d minutes, %d seconds\r\n", num_days, num_hours, num_minutes, num_seconds);

    // print the uptime msg
    shell_print(uptime_msg);
    vPortFree(uptime_msg);

    // return null since we printed directly to the shell
    return 0;
}

// proc directory files descriptor
static const struct ush_file_descriptor proc_files[] = {
    {
        .name = "mcuinfo",                              // file name (required)
        .description = "get details about the MCU",     // optional file description
        .help = NULL,                                   // optional help manual
        .exec = NULL,                                   // optional execute callback
        .get_data = mcuinfo_get_data_callback,          // optional get data (cat) callback
        .set_data = NULL                                // optional set data (echo) callback
    },
    {
        .name = "version",
        .description = "get version numbers of firmware libraries",
        .help = NULL,
        .exec = NULL,
        .get_data = version_get_data_callback,
        .set_data = NULL
    },
    {
        .name = "resetreason",
        .description = "get the last reset reason",
        .help = NULL,
        .exec = NULL,
        .get_data = resetreason_get_data_callback,
        .set_data = NULL
    },
    {
        .name = "uptime",
        .description = "get system uptime since boot",
        .help = NULL,
        .exec = NULL,
        .get_data = uptime_get_data_callback,
        .set_data = NULL
    }
};

// proc directory handler
static struct ush_node_object proc;

void shell_proc_mount(void)
{
    // mount proc directory
    ush_node_mount(&ush, "/proc", &proc, proc_files, sizeof(proc_files) / sizeof(proc_files[0]));
}


================================================
FILE: cli/node_root.c
================================================
/******************************************************************************
 * @file node_root.c
 *
 * @brief root folder ('/') for the CLI containing directories and files
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <microshell.h>
#include "hardware_config.h"

/**
* @brief 'info.txt' get data callback function.
*
* Returns the "contents" of the info.txt file by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the text strings to print
*/
size_t info_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
    static char *info = xstr(PROJECT_NAME) ", compiled with love\r\n"; // the contents of the file

    // set pointer to data
    *data = (uint8_t*)info;
    // return data size
    return strlen(info);
}

// root directory files descriptor
static const struct ush_file_descriptor root_files[] = {
    {
        .name = "info.txt",                     // file name (required)
        .description = NULL,                    // optional file description
        .help = NULL,                           // optional help manual
        .exec = NULL,                           // optional execute callback
        .get_data = info_get_data_callback,     // optional get data (cat) callback
        .set_data = NULL                        // optional set data (echo) callback
    }
};

// root directory handler
static struct ush_node_object root;

void shell_root_mount(void)
{
    // mount root directory
    ush_node_mount(&ush, "/", &root, root_files, sizeof(root_files) / sizeof(root_files[0]));
}


================================================
FILE: cli/shell.c
================================================
/******************************************************************************
 * @file shell.h
 *
 * @brief Microshell instance definition, settings, & wrapper functions to use
 *        at CLI application level
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <microshell.h>
#include "hardware_config.h"
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "rtos_utils.h"


// microshell character read interface
static int ush_read(struct ush_object *self, char *ch)
{
    char inchar = NOCHAR;
    if (CLI_USE_USB) {
        inchar = cli_usb_getc();  // CLI over USB get single char
    }
    else {
        inchar = cli_uart_getc(); // CLI over UART get single char
    }
    if(inchar != NOCHAR) {
        *ch = inchar;
        return 1;
    }
    return 0;
}

// microshell character write interface
static int ush_write(struct ush_object *self, char ch)
{
    if (CLI_USE_USB) {
        return cli_usb_putc(ch);  // CLI over USB put single char
    }
    else {
        return cli_uart_putc(ch); // CLI over UART put single char
    }
}

// I/O interface descriptor
static const struct ush_io_interface ush_iface = {
    .read = ush_read,
    .write = ush_write,
};

static char ush_in_buf[BUF_IN_SIZE];
static char ush_out_buf[BUF_OUT_SIZE];
static char ush_hist_buf[SHELL_HISTORY_LINES * SHELL_WORK_BUFFER_SIZE];

// global microshell instance handler (extern declared in shell.h)
struct ush_object ush;

// microshell custom prompt formatting structure - defined in shell.h
static const struct ush_prompt_format ush_prompt = {
    .prompt_prefix = SHELL_PROMPT_PREFIX,
    .prompt_space = SHELL_PROMPT_SPACE,
    .prompt_suffix = SHELL_PROMPT_SUFFIX
};

// microshell command history structure - sizes in shell.h
static ush_history ush_cmd_hist = {
    .lines = SHELL_HISTORY_LINES,
    .length = SHELL_WORK_BUFFER_SIZE,
    .buffer = ush_hist_buf
};

// microshell descriptor
static const struct ush_descriptor ush_desc = {
    .io = &ush_iface,                           // I/O interface pointer
    .input_history = &ush_cmd_hist,             // command history buffer
    .input_buffer = ush_in_buf,                 // working input buffer
    .input_buffer_size = sizeof(ush_in_buf),    // working input buffer size
    .output_buffer = ush_out_buf,               // working output buffer
    .output_buffer_size = sizeof(ush_out_buf),  // working output buffer size
    .path_max_length = PATH_MAX_SIZE,           // path maximum length (stack)
    .hostname = HOST_NAME,                // hostname (in prompt) - defined in hardware_config.h
    .prompt_format = &ush_prompt                // custom prompt formatting
};

// declare the extern functions to build the global directory structure - defined in individual source files
extern void shell_commands_add(void);
extern void shell_root_mount(void);
extern void shell_dev_mount(void);
extern void shell_bin_mount(void);
extern void shell_proc_mount(void);
extern void shell_etc_mount(void);
extern void shell_lib_mount(void);
extern void shell_net_mount(void);

void shell_init(void)
{
    // initialize microshell instance
    ush_init(&ush, &ush_desc);

    // add commands
    shell_commands_add();

    // mount nodes (root must be first) - order printed with 'ls' follows reverse
    // of this mount order - if nodes are mounted on the fly later, they will
    // be on the top
    shell_root_mount();
    shell_lib_mount();
    shell_dev_mount();
    shell_proc_mount();
    shell_bin_mount();
    shell_etc_mount();
    // note that /mnt is currently mounted by storagemanager service
    // note that /net is currently mounted by networkmanager service
}

void shell_service(void)
{
    ush_service(&ush);
}

void shell_print(char *buf)
{
    // print buffer using microshell interface
    ush_print(&ush, buf);
    // wait until printing is finished to exit function (blocking)
    while(shell_is_printing()) {}
}

void shell_print_slow(const char *buf) {
    uint16_t string_index = 0;
    char char_buf[2];
    bool end_of_string = false;

    // move through the print buffer one character at a time
    while (!end_of_string) {
        char_buf[0] = buf[string_index]; // copy current char into print buffer
        char_buf[1] = '\0'; // terminate the single char as a string
        if (buf[string_index] == '\n') { // upon newline block printing for specified delay time
            string_index++;
            task_delay_ms(SLOW_PRINT_LINE_DELAY_MS);
        }
        else if (buf[string_index] == '\0') { // end of print buffer
            end_of_string = true;
        }
        else {
            string_index++;
            task_delay_ms(SLOW_PRINT_CHAR_DELAY_MS);
        }
        if (!end_of_string) {
            ush_print(&ush, char_buf);
            while(shell_is_printing()) {}
        }
    }
}

bool shell_is_printing(void)
{
    // check if microshell is currently in writing state
    if (ush.state == USH_STATE_WRITE_CHAR) {
        ush_service(&ush); // keep servicing the shell if it is still writing chars
        return true;
    } else {
        return false;
    }
}

char * timestamp(void)
{
    static char timestamp_msg[TIMESTAMP_LEN]; // 14 bytes plus spaces and brackets, ~3 years before rollover
    snprintf(timestamp_msg, TIMESTAMP_LEN, "[%llu\t] ", get_time_us());
    return timestamp_msg;
}

================================================
FILE: cli/shell.h
================================================
/******************************************************************************
 * @file shell.h
 *
 * @brief Microshell custom settings and wrapper functions to use at the CLI
 *        application layer
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#ifndef SHELL_H
#define SHELL_H

#include <stdbool.h>
#include <microshell.h>


// microshell in/out buffer allocations
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_ARCH_STM32)
    #define BUF_IN_SIZE    128
    #define BUF_OUT_SIZE   128
    #define PATH_MAX_SIZE  128
    #define SHELL_HISTORY_LINES 6
    #define SHELL_WORK_BUFFER_SIZE 256
#elif defined(PICO)
    #define BUF_IN_SIZE    128
    #define BUF_OUT_SIZE   1024
    #define PATH_MAX_SIZE  128
    #define SHELL_HISTORY_LINES 6
    #define SHELL_WORK_BUFFER_SIZE 256
#else
    #define BUF_IN_SIZE    32
    #define BUF_OUT_SIZE   32
    #define PATH_MAX_SIZE  32
    #define SHELL_HISTORY_LINES 6
    #define SHELL_WORK_BUFFER_SIZE 256
#endif

// custom prompt formatting -
// microshell prompt format is prompt_prefix+HOST_NAME+prompt_space+[path]+prompt_suffix
// see "ush_shell.h" for font formatting codes
#ifndef HOST_NAME
#define HOST_NAME "bbos"
#endif
#define SHELL_PROMPT_PREFIX USH_SHELL_FONT_COLOR_MAGENTA \
                            USH_SHELL_FONT_STYLE_BOLD \
                            "[" \
                            USH_SHELL_FONT_COLOR_YELLOW
#define SHELL_PROMPT_SPACE  USH_SHELL_FONT_COLOR_CYAN \
                            " "
#define SHELL_PROMPT_SUFFIX USH_SHELL_FONT_COLOR_MAGENTA \
                            USH_SHELL_FONT_STYLE_BOLD \
                            "]$ " \
                            USH_SHELL_FONT_STYLE_RESET

// miscellaneous definitions
#define NOCHAR 255 // return value of cli_uart_getc() in ush_read() if timeout occurs
#define TIMESTAMP_LEN 20 // length of timestamp() string to use when sizing print buffers
#define SLOW_PRINT_CHAR_DELAY_MS 1   // shell_print_slow() OS tick delay between chars
#define SLOW_PRINT_LINE_DELAY_MS 5  // shell_print_slow() OS tick delay between lines

// global microshell instance handler
extern struct ush_object ush;

/**
* @brief Initialize the CLI shell.
*
* Initializes the microshell instance and mounts all directory and file nodes.
*
* @param none
*
* @return nothing
*/
void shell_init(void);

/**
* @brief Service routine for the CLI shell.
*
* Runs in a loop to service all microshell functions.
*
* @param none
*
* @return nothing
*/
void shell_service(void);

/**
* @brief Print string output in the shell.
*
* Wrapper which allows other functions within the CLI to print output to the shell.
* Anything outside the CLI task should use the print queue (cli_print_raw() or cli_print_timestamped()).
* Note that this function blocks until printing is complete.
*
* @param buf pointer to the string to print
*
* @return nothing
*/
void shell_print(char *buf);

/**
* @brief Print string output in the shell, slowly.
*
* Similar to shell_print(), with the addition of RTOS task delays to give the output
* text that groovy retro console feel. This is a silly function with no real world
* value. Note that this is only intended for large multi-line prints like MOTD,
* which are stored in flash (const char*)
*
* @param buf pointer to the string to print
*
* @return nothing
*/
void shell_print_slow(const char *buf);

/**
* @brief Check if the shell is currently printing.
*
* Wrap this function in while() to wait until the shell is done printing all
* characters of a string before moving on. This is implemented inside shell_print().
*
* @param none
*
* @return true if shell is still printing the current string, false when complete
*/
bool shell_is_printing(void);

/**
* @brief Generate a timestamp that can be used in shell prints.
*
* Provides a pointer to a string in '[ xxx ]' format that is useful for
* displaying clear timestamps along with shell output.
*
* @param none
*
* @return pointer to the timestamp string
*/
char * timestamp(void);

/**
* @brief Shell serial read interface.
*
* Hardware-abstracted, non-blocking read interface for microshell to receive
* input characters.
*
* @param ush_io_interface_read_char Params given by typedef ush_io_interface_read_char. see ush_types.h
*
* @return See ush_io_interface_read_char in ush_types.h
*/
static int ush_read(struct ush_object *self, char *ch);

/**
* @brief Shell serial write interface.
*
* Hardware-abstracted, non-blocking write interface for microshell to send
* output characters.
*
* @param ush_io_interface_write_char Params given by typedef ush_io_interface_write_char. see ush_types.h
*
* @return See ush_io_interface_read_write in ush_types.h
*/
static int ush_write(struct ush_object *self, char ch);

#endif /* SHELL_H */


================================================
FILE: cli/shell_cmd.c
================================================
/******************************************************************************
 * @file shell_cmd.c
 *
 * @brief Defines any additional CLI builtin shell commands 
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <microshell.h>
#include "shell.h"

/**
* @brief 'clear' shell command callback function.
*
* Clear the screen - works when using a ANSI-compatible terminal.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void clear_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
    static char clear_msg[] =   "" USH_SHELL_CLEAR_SCREEN
                                USH_SHELL_HOME;
    shell_print(clear_msg);
}

// cmd files descriptor
static const struct ush_file_descriptor cmd_files[] = {
    {
        .name = "clear",
        .description = "clear screen",
        .help = NULL,
        .exec = clear_exec_callback,
    },
};

// cmd commands handler
static struct ush_node_object cmd;

void shell_commands_add(void)
{
    // add custom commands
    ush_commands_add(&ush, &cmd, cmd_files, sizeof(cmd_files) / sizeof(cmd_files[0]));
}


================================================
FILE: driver_lib/CMakeLists.txt
================================================
# add source files to the top-level project
target_sources(${PROJ_NAME} PRIVATE
    device_drivers.c
    bme280.c
    mcp4725.c
)

================================================
FILE: driver_lib/bme280.c
================================================
/******************************************************************************
 * @file bme280.c
 *
 * @brief Hardware interface driver functions for the Bosch BME280 environmental
 *        sensor peripheral. Calibration routines based on Bosch datasheet, using
 *        a ridiculously convoluted confluence of algorithms.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdint.h>
#include "device_drivers.h"
#include "hardware_config.h"

// global storage for bme280 compensation params, read out once in bme280_init()
bme280_compensation_params_t bme280_compensation_params_glob;


static int32_t bme280_compensate_temperature(bme280_compensation_params_t *compensation_params, int32_t adc_T) {
    int32_t var1, var2, T;

    // calculate compensation factors for temperature reading - is there any possible way there isn't a typo here?
    var1 = ((((adc_T >> 3) - ((int32_t) compensation_params->temp_comp.T1 << 1))) * ((int32_t) compensation_params->temp_comp.T2)) >> 11;
    var2 = (((((adc_T >> 4) - ((int32_t) compensation_params->temp_comp.T1)) * ((adc_T >> 4) - ((int32_t) compensation_params->temp_comp.T1))) >> 12) * ((int32_t) compensation_params->temp_comp.T3)) >> 14;

    // save fine temperature for other compensations - temp calc needs to always come before pressure, humidity
    compensation_params->temp_fine = var1 + var2;
    // calculate calibrated temperature
    T = (compensation_params->temp_fine * 5 + 128) >> 8;

    return T;
}

static uint32_t bme280_compensate_pressure(bme280_compensation_params_t *compensation_params, int32_t adc_P) {
    int64_t var1, var2, P;

    // calculate compensation factors for barometric pressure reading - seriously I have never manipulated so many bits
    var1 = ((int64_t) compensation_params->temp_fine) - 128000;
    var2 = var1 * var1 * (int64_t) compensation_params->press_comp.P6;
    var2 = var2 + ((var1 * (int64_t) compensation_params->press_comp.P5) << 17);
    var2 = var2 + ((int64_t) compensation_params->press_comp.P4 << 35);
    var1 = ((var1 * var1 * (int64_t) compensation_params->press_comp.P3) >> 8) + ((var1 * (int64_t) compensation_params->press_comp.P2) << 12);
    var1 = (((((int64_t) 1) << 47) + var1)) * ((int64_t) compensation_params->press_comp.P1) >> 33;

    if (var1 == 0) return 0; // avoid possible divide by zero exception by returning early

    P = 1048576 - adc_P;
    P = (((P << 31) - var2) * 3125) / var1;

    // further calculate, the BME280 is a great example of how not to design an ASIC data interface
    var1 = (((int64_t) compensation_params->press_comp.P9) * (P >> 13) * (P >> 13)) >> 25;
    var2 = (((int64_t) compensation_params->press_comp.P8) * P) >> 19;

    // calculate final calibrated barometric pressure
    P = ((P + var1 + var2) >> 8) + (((int64_t) compensation_params->press_comp.P7) << 4);

    return (uint32_t) P;
}

static uint32_t bme280_compensate_humidity(bme280_compensation_params_t *compensation_params, int32_t adc_H) {
    int32_t H;
    
    // here we go again...
    H = (compensation_params->temp_fine - ((int32_t) 76800));
    H = (((((adc_H << 14) - (((int32_t) compensation_params->hum_comp.H4) << 20) - (((int32_t) compensation_params->hum_comp.H5) * H)) +
        ((int32_t) 16384)) >> 15) * (((((((H * ((int32_t) compensation_params->hum_comp.H6)) >> 10) * (((H *((int32_t) compensation_params->hum_comp.H3)) >> 11) +
        ((int32_t) 32768))) >> 10) + ((int32_t) 2097152)) * ((int32_t) compensation_params->hum_comp.H2) + 8192) >> 14));
    H = (H - (((((H >> 15) * (H >> 15)) >> 7) * ((int32_t) compensation_params->hum_comp.H1)) >> 4));
    H = (H < 0 ? 0 : H);
    H = (H > 419430400 ? 419430400 : H);
    // I'm as stumped as you are

    return (uint32_t) (H >> 12);
}

int bme280_read_compensation_parameters(bme280_compensation_params_t *compensation_params) {
    uint8_t comp_params_buffer[26];
    int bytes_read;

    // read out factory compensation parameter data bytes 0-25 (comp data is split between 2 memory blocks)
    bytes_read = spi0_read_registers(SPI0_TARGET_DEV_0_CS, 0x88, comp_params_buffer, 26);

    if (bytes_read == 26) {
        // break out data into individual compensation factors - following
        // sec. 4.2.2 of BME280 data sheet "Trimming parameter readout"

        // temperature compensation
        compensation_params->temp_comp.T1 = comp_params_buffer[0] | (comp_params_buffer[1] << 8);
        compensation_params->temp_comp.T2 = comp_params_buffer[2] | (comp_params_buffer[3] << 8);
        compensation_params->temp_comp.T3 = comp_params_buffer[4] | (comp_params_buffer[5] << 8);

        // barometric pressure compensation
        compensation_params->press_comp.P1 = comp_params_buffer[6] | (comp_params_buffer[7] << 8);
        compensation_params->press_comp.P2 = comp_params_buffer[8] | (comp_params_buffer[9] << 8);
        compensation_params->press_comp.P3 = comp_params_buffer[10] | (comp_params_buffer[11] << 8);
        compensation_params->press_comp.P4 = comp_params_buffer[12] | (comp_params_buffer[13] << 8);
        compensation_params->press_comp.P5 = comp_params_buffer[14] | (comp_params_buffer[15] << 8);
        compensation_params->press_comp.P6 = comp_params_buffer[16] | (comp_params_buffer[17] << 8);
        compensation_params->press_comp.P7 = comp_params_buffer[18] | (comp_params_buffer[19] << 8);
        compensation_params->press_comp.P8 = comp_params_buffer[20] | (comp_params_buffer[21] << 8);
        compensation_params->press_comp.P9 = comp_params_buffer[22] | (comp_params_buffer[23] << 8);

        // humidity compensation
        compensation_params->hum_comp.H1 = comp_params_buffer[25];
        // read out factory compensation parameter data bytes 26-32, re-using the same buffer
        spi0_read_registers(SPI0_TARGET_DEV_0_CS, 0xE1, comp_params_buffer, 7);
        compensation_params->hum_comp.H2 = comp_params_buffer[0] | (comp_params_buffer[1] << 8);
        compensation_params->hum_comp.H3 = comp_params_buffer[2];
        compensation_params->hum_comp.H4 = (comp_params_buffer[3] << 4) | (comp_params_buffer[4] & 0x0F);
        compensation_params->hum_comp.H5 = (comp_params_buffer[4] >> 4) | (comp_params_buffer[5] << 4);
        compensation_params->hum_comp.H6 = comp_params_buffer[7];

        return 1;
    }
    else return 0;
}

int bme280_init(void) {
    int registers_written = 0;

    // read out compensation parameters from device ROM and store in global structure
    if (bme280_read_compensation_parameters(&bme280_compensation_params_glob)) {
        // humidity oversampling register = x1
        registers_written += spi0_write_register(SPI0_TARGET_DEV_0_CS, 0xF2, 0x01);
        // set other oversampling modes, run mode normal
        registers_written += spi0_write_register(SPI0_TARGET_DEV_0_CS, 0xF4, 0x27);
    }

    // make sure BME280 configuration has taken place
    if (registers_written == 2)
        return 1;
    else return 0;
}

int bme280_read_sensors(bme280_compensation_params_t *compensation_params, bme280_sensor_data_t *sensor_data) {
    uint8_t  raw_data_buffer[8];
    int32_t  temp_raw;
    uint32_t press_raw, hum_raw;
    
    // read raw data (uncalibrated) from sensors
    if (spi0_read_registers(SPI0_TARGET_DEV_0_CS, 0xF7, raw_data_buffer, 8) == 8) {
        // shift data into the appropriate bin (temp, press, hum)
        temp_raw  = ((uint32_t) raw_data_buffer[3] << 12) | ((uint32_t) raw_data_buffer[4] << 4) | (raw_data_buffer[5] >> 4);
        press_raw = ((uint32_t) raw_data_buffer[0] << 12) | ((uint32_t) raw_data_buffer[1] << 4) | (raw_data_buffer[2] >> 4);
        hum_raw   = ((uint32_t) raw_data_buffer[6] << 8) | raw_data_buffer[7];

        // apply corrections and save to sensor data struct
        sensor_data->temperature = (float) bme280_compensate_temperature(compensation_params, temp_raw) / 100;
        sensor_data->pressure    = (float) bme280_compensate_pressure(compensation_params, press_raw) / 25600;
        sensor_data->humidity    = (float) bme280_compensate_humidity(compensation_params, hum_raw) / 1024;

        return 1;
    }
    else return 0;
}

================================================
FILE: driver_lib/device_drivers.c
================================================
/******************************************************************************
 * @file device_drivers.c
 *
 * @brief Monolithic function to initialize all peripheral devices connected to
 *        the system. Calls individual device initialization functions from the
 *        other source files.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdbool.h>
#include "hardware_config.h"
#include "device_drivers.h"


void driver_init(void) {

    // print initialization status text for externally connected devices.
    // This will only be visible if connected to the CLI UART; if CLI over USB
    // is used it will not show initial boot prints because all CLI output is
    // via FreeRTOS tasks/queues, and the OS kernel is not running yet.
    uart_puts(UART_ID_CLI, timestamp());
    uart_puts(UART_ID_CLI, "Initializing connected devices:\r\n\t  {");

    if (HW_USE_SPI0 && BME280_ATTACHED) {
        uart_puts(UART_ID_CLI, " bme280");
        if (bme280_init()) {
            uart_puts(UART_ID_CLI, "[PASS]");
        }
        else {
            uart_puts(UART_ID_CLI, "[FAIL]");
        }
    }

    if (HW_USE_I2C0 && MCP4725_ATTACHED) {
        uart_puts(UART_ID_CLI, " mcp4725");
        if (mcp4725_init()) {
            uart_puts(UART_ID_CLI, "[PASS]");
        }
        else {
            uart_puts(UART_ID_CLI, "[FAIL]");
        }
    }

    uart_puts(UART_ID_CLI, " }\r\n");
}

================================================
FILE: driver_lib/device_drivers.h
================================================
/******************************************************************************
 * @file device_drivers.h
 *
 * @brief Driver interfaces for peripheral hardware devices
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#ifndef DEVICE_DRIVERS_H
#define DEVICE_DRIVERS_H

#include <stdint.h>
#include <stdbool.h>


/**************************
 * Peripheral Configuration
***************************/

// use the below settings to specify which devices are attached to the system
// set to true to load driver and run init for this peripheral device
#define BME280_ATTACHED  true
#define MCP4725_ATTACHED true

/**
* @brief Initialize all attached peripheral devices.
*
* Single function to initialize all peripheral devices attached to the system,
* to be run once after all onboard hardware interfaces have been brought up
* with hardware_init().
*
* @param none
*
* @return nothing
*/
void driver_init(void);


/************************************
 * Bosch BME280 Environmental Sensor
*************************************/

// bme280 compensation parameters typedef
// these parameters are written into device ROM at manufacture
typedef struct bme280_compensation_params_t {
    struct {
        uint16_t T1;
        int16_t T2, T3;
    } temp_comp;
    struct {
        uint16_t P1;
        int16_t P2, P3, P4, P5, P6, P7, P8, P9;
    } press_comp;
    struct {
        uint8_t H1, H3;
        int8_t H6;
        int16_t H2, H4, H5;
    } hum_comp;
    int32_t temp_fine;
} bme280_compensation_params_t;

// bme280 calibrated sensor readings typedef
typedef struct bme280_sensor_data_t {
    float temperature;
    float pressure;
    float humidity;
} bme280_sensor_data_t;

// global storage for bme280 compensation params, read out once in bme280_init()
extern bme280_compensation_params_t bme280_compensation_params_glob;

/**
* @brief Initialize the BME280 device.
*
* Initialize the BME20 environmental sensor device by writing any pre-configuration
* parameters and setting up the device to start polling sensor values.
*
* @param none
*
* @return 1 if initialization success, 0 if failure
*/
int bme280_init(void);

/**
* @brief Read out the factory compensation values.
*
* Read out the factory compensation values that are stored in device ROM during
* manufacture, to be applied to the raw sensor readings for calibrated results.
* This only needs to be done once, and then applied to all further sensor readings.
*
* @param compensation_params pointer to the compensation data structure to store values
*
* @return 1 if compensation data successfully read, 0 if failed to read
*/
int bme280_read_compensation_parameters(bme280_compensation_params_t *compensation_params);

/**
* @brief Read BME280 sensor data.
*
* Reads out current sensor data readings from the BME280 and applies the calibration
* parameters previously read out from the device with bme280_read_compensation_parameters.
*
* @param compensation_params pointer to the compensation values data structure
* @param sensor_data pointer to the structure to hold the calibrated sensor readings
*
* @return 1 if sensor data read successfully, 0 if failed to read
*/
int bme280_read_sensors(bme280_compensation_params_t *compensation_params, bme280_sensor_data_t *sensor_data);



/************************************************
 * Microchip MCP4725 Digital-to-Analog Converter
*************************************************/

// MCP4725 specific settings
// VDD rail voltage - defines scaling for the 12-bit DAC value
#define MCP4725_VDD 3.3
// I2C address (hex)
#define MCP4725_I2C_ADDR 0x60

/**
* @brief Initialize the MCP4725 device.
*
* Initiate communication with the MCP4725 and verify that the device is
* connected and operational.
*
* @param none
*
* @return 1 if initialization success, 0 if failure
*/
int mcp4725_init(void);

/**
* @brief Set the voltage of the DAC.
*
* Sets the output voltage of the MCP4725 by writing the 12-bit value over I2C.
* The conversion is based on the MCP4725_VDD parameter definition. The value can
* be written in "fast mode" (don't save to EEPROM), or, one can choose to store
* the value to built-in EEPROM so that the DAC setting is preserved through
* power cycles.
*
* @param voltage desired output voltage from 0 - VCC
* @param save_in_eeprom also write the DAC setting into internal NVM
*
* @return 1 if successfully set, 0 if fail
*/
int mcp4725_set_voltage(float voltage, bool save_in_eeprom);

/**
* @brief Get the current DAC voltage
*
* Gets the current value of the DAC setting in volts. This is the real-time
* setting and does not reflect what might be stored in built-in EEPROM.
*
* @param none
*
* @return floating-point voltage setting of the DAC (negative indicates error)
*/
float mcp4725_get_voltage(void);


#endif /* DEVICE_DRIVERS_H */

================================================
FILE: driver_lib/mcp4725.c
================================================
/******************************************************************************
 * @file mcp4725.c
 *
 * @brief Hardware interface driver functions for the Microchip MCP4725 12-bit
 *        Digital-to-Analog Converter (DAC) with built-in EEPROM.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdint.h>
#include "device_drivers.h"
#include "hardware_config.h"


int mcp4725_init(void) {
    uint8_t rx_byte;

    // read a single byte to check for MCP4725 device code
    if (i2c0_read(MCP4725_I2C_ADDR, &rx_byte, 1) == 1) {
        rx_byte = rx_byte >> 4;
        if (rx_byte == 0xC) { // check for 4-bit device code 1100
            return 1;
        }
        else return 0;
    }
}

int mcp4725_set_voltage(float voltage, bool save_in_eeprom) {
    uint16_t dac_setting = (uint16_t)(4095 * voltage / MCP4725_VDD);
    uint8_t dac_write_data[3];

    if (save_in_eeprom) {
        dac_write_data[0] = 0x60; // specify write to DAC register and EEPROM in 1st byte
        dac_write_data[1] = (uint8_t) (dac_setting >> 4); // upper 8 bits of DAC value goes in 2nd byte
        dac_write_data[2] = (uint8_t) (dac_setting << 4); // lower 4 bits goes in top nibble of 3rd byte

        if (i2c0_write(MCP4725_I2C_ADDR, dac_write_data, 3) == 3) {
            return 1;
        }
        else return 0;
    }
    else {
        dac_write_data[0] = (uint8_t) (dac_setting >> 8); // upper 4 bits of DAC value goes in 1st byte
        dac_write_data[1] = (uint8_t) dac_setting;        // lower 8 bits of DAC value goes into 2nd byte

        if (i2c0_write(MCP4725_I2C_ADDR, dac_write_data, 2) == 2) {
            return 1;
        }
        else return 0;
    }
}

float mcp4725_get_voltage(void) {
    uint16_t dac_setting;
    uint8_t dac_read_data[3];

    if (i2c0_read(MCP4725_I2C_ADDR, dac_read_data, 3) == 3) {
        dac_setting = (uint16_t) (dac_read_data[1] << 4);               // 2nd byte read is 8 MSBs of the current DAC setting
        dac_setting = dac_setting | (uint16_t) (dac_read_data[2] >> 4); // top nibble of 3rd byte read has 4 LSBs of DAC setting

        float voltage = (float) MCP4725_VDD * (float) dac_setting / 4095;
        return voltage;
    }
    else return -1;
}

================================================
FILE: hardware/rp2xxx/CMakeLists.txt
================================================
# add source files to the top-level project
target_sources(${PROJ_NAME} PRIVATE
    hardware_config.c
    hw_gpio.c
    hw_uart.c
    hw_i2c.c
    hw_spi.c
    hw_adc.c
    hw_usb.c
    onboard_led.c
    onboard_flash.c
    hw_watchdog.c
    hw_registers.c
    hw_reset.c
    hw_clocks.c
    hw_cores.c
    hw_versions.c
)

# optional features
if (ENABLE_WIFI)
    target_sources(${PROJ_NAME} PRIVATE
        hw_wifi.c
        hw_net.c
    )
endif()

function(hardware_build_extra)
    # initialize the Pico/RP2040 SDK
    pico_sdk_init()

    # Pass these parameters to the preprocessor if using specific board/chip types
    # since some HW config needs to change
    if(PICO_PLATFORM STREQUAL "rp2040") # the Pico SDK import will auto set PICO_PLATFORM based on PICO_BOARD
        target_compile_definitions(${PROJ_NAME} PUBLIC -DUSING_RP2040)
        target_compile_definitions(cli PUBLIC -DUSING_RP2040)
    endif()
    if(PICO_PLATFORM STREQUAL "rp2350-arm-s") # the Pico SDK import will auto set PICO_PLATFORM based on PICO_BOARD
        target_compile_definitions(${PROJ_NAME} PUBLIC -DUSING_RP2350)
        target_compile_definitions(cli PUBLIC -DUSING_RP2350)
    endif()

    # Pass optional build options to the preprocessor for various code inclusions
    if(ENABLE_WIFI)
        target_compile_definitions(${PROJ_NAME} PUBLIC -DHW_USE_WIFI
                                                       -DCYW43_HOST_NAME=\"${HOSTNAME}-${BOARD}\")
        target_compile_definitions(cli PUBLIC -DHW_USE_WIFI
                                              -DHOST_NAME=\"${HOSTNAME}\")
        if(ENABLE_HTTPD)
            # defines used for code inclusion
            target_compile_definitions(${PROJ_NAME} PUBLIC -DENABLE_HTTPD)
            target_compile_definitions(cli PUBLIC -DENABLE_HTTPD)
            # pico-sdk function for building http content into a library
            pico_add_library(lwip_httpd_content)
            pico_set_lwip_httpd_content(lwip_httpd_content INTERFACE
            ${PROJECT_SOURCE_DIR}/hardware/rp2xxx/net_inc/httpd_content/404.html
            ${PROJECT_SOURCE_DIR}/hardware/rp2xxx/net_inc/httpd_content/index.shtml
            ${PROJECT_SOURCE_DIR}/hardware/rp2xxx/net_inc/httpd_content/test.shtml
            )
        endif()
    endif()

    # disable Pico STDIO - interacting with CLI will be done via RTOS task queue only, no printf
    pico_enable_stdio_usb(${PROJ_NAME} 0)
    pico_enable_stdio_uart(${PROJ_NAME} 0)

    # generate UF2, dissassembly and map files, etc
    pico_add_extra_outputs(${PROJ_NAME})
endfunction()


================================================
FILE: hardware/rp2xxx/hardware_config.c
================================================
/******************************************************************************
 * @file hardware_config.c
 *
 * @brief Monolithic function to initialize all hardware on the device. Calls
 *        individual hardware initialization functions from the other source
 *        files.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include <stdint.h>
#include "hardware_config.h"
#ifdef HW_USE_WIFI
#include "hw_wifi.h"
#endif

#ifdef USING_RP2040
#include "hardware/regs/vreg_and_chip_reset.h"
#elif USING_RP2350
#include "hardware/structs/powman.h"
#include "hardware/regs/powman.h"
#endif

#ifdef HAS_CYW43
#include "pico/cyw43_arch.h"
#endif


// get the last reset reason and store it to global variable
reset_reason_t last_reset_reason;

void hardware_init(void) {
    // mutexes for accessing hardware peripherals (created within each hw init function)
    SemaphoreHandle_t gpio_mutex = NULL;
    SemaphoreHandle_t cli_uart_mutex = NULL;
    SemaphoreHandle_t aux_uart_mutex = NULL;
    SemaphoreHandle_t i2c0_mutex = NULL;
    SemaphoreHandle_t spi0_mutex = NULL;
    SemaphoreHandle_t onboard_flash_mutex = NULL;
    SemaphoreHandle_t adc_mutex = NULL;
    SemaphoreHandle_t usb_mutex = NULL;

    // initialize the uart for cli/microshell first for status prints
    cli_uart_init();

    // get the last reset reason type & string
    last_reset_reason = get_reset_reason(); // stored in global variable
    char *reset_reason_string = get_reset_reason_string(last_reset_reason);
    // get the last reset reason raw register value
#ifdef USING_RP2040
    uint32_t reset_state_reg_addr = VREG_AND_CHIP_RESET_BASE + VREG_AND_CHIP_RESET_CHIP_RESET_OFFSET;
#elif USING_RP2350
    uint32_t reset_state_reg_addr = POWMAN_BASE + POWMAN_CHIP_RESET_OFFSET;
#endif
    char reset_reason_raw_reg[30];
    sprintf(reset_reason_raw_reg, "Reset Register: 0x%08X\r\n", (unsigned int)read_chip_register(reset_state_reg_addr));

    // print the last reset reason
    uart_puts(UART_ID_CLI, timestamp());
    uart_puts(UART_ID_CLI, reset_reason_string);
    uart_puts(UART_ID_CLI, timestamp());
    uart_puts(UART_ID_CLI, reset_reason_raw_reg);

    // print hardware initialization status text. This will only be visible if
    // connected to the CLI UART; if CLI over USB is used it will not show initial
    // boot prints because all CLI output is via FreeRTOS tasks/queues, and the
    // OS kernel is not running yet.
    uart_puts(UART_ID_CLI, timestamp());
    uart_puts(UART_ID_CLI, "Configuring MCU peripherals:\r\n\t  { ");

    // initialize the auxilliary uart
    if (HW_USE_AUX_UART) {
        aux_uart_init();
        uart_puts(UART_ID_CLI, "aux_uart ");
    }

    // initialize i2c peripheral(s)
    if (HW_USE_I2C0) {
        i2c0_init();
        uart_puts(UART_ID_CLI, "i2c0 ");
    }

    // initialize spi peripheral(s)
    if (HW_USE_SPI0) {
        spi0_init();
        uart_puts(UART_ID_CLI, "spi0 ");
    }

    // initialize the onboard LED gpio (if not a Pico W board)
    if (HW_USE_ONBOARD_LED) {
        onboard_led_init();
        uart_puts(UART_ID_CLI, "led ");
    }

    // initialize the ADC(s)
    if (HW_USE_ADC) {
        adcs_init();
        uart_puts(UART_ID_CLI, "adc ");
    }

    // initialize USB hardware
    if (HW_USE_USB | CLI_USE_USB) {
        usb_device_init();
        uart_puts(UART_ID_CLI, "usb ");
    }

    // initialize GPIO
    if (HW_USE_GPIO) {
        gpio_init_all();
        uart_puts(UART_ID_CLI, "gpio ");
    }

    // initialize onboard flash
    if (HW_USE_ONBOARD_FLASH) {
        onboard_flash_init();
        uart_puts(UART_ID_CLI, "flash ");
    }

    uart_puts(UART_ID_CLI, "}\r\n");

    // initialize the wireless module
    // if wifi is enabled, this is done in the FreeRTOS task instead, since
    // the init function uses the pico-sdk lwip/FreeRTOS port in this case
#ifdef HW_USE_WIFI
    if (HW_USE_WIRELESS && HAS_CYW43 && !HW_USE_WIFI) {
        if(hw_wifi_init()) {
            uart_puts(UART_ID_CLI, timestamp());
            uart_puts(UART_ID_CLI, "Failed to initialize CYW43 hardware.\r\n");
        } else {
            uart_puts(UART_ID_CLI, timestamp());
            uart_puts(UART_ID_CLI, "Initialized onboard wireless module\r\n");
        }
    }
#endif /* HW_USE_WIFI */

}


================================================
FILE: hardware/rp2xxx/hardware_config.h
================================================
/******************************************************************************
 * @file hardware_config.h
 *
 * @brief Settings and function prototypes for all interaction with MCU
 *        hardware. Hardware-specific implementation resides within these
 *        functions - it is intended that porting to a new MCU will require
 *        changes only to the function implementations that reside in the
 *        /hardware_<mcu> folder.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#ifndef HARDWARE_CONFIG_H
#define HARDWARE_CONFIG_H

#include <stdbool.h>
#include <stdint.h>
#include "shell.h"
#include "lfs.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "FreeRTOS.h"
#include "semphr.h"


// global MCU name - used throughout the codebase to differentiate architectures
#ifdef USING_RP2350
#define MCU_NAME RP2350_Cortex_M33
#else
#define MCU_NAME RP2040_Cortex_M0
#endif

// stringify helper - use xstr() to convert #define to a usable string
#define str(s) # s
#define xstr(s) str(s)


/************************
 * TABLE OF CONTENTS
*************************
search file for these keywords including the leading '*' to find section
 * GPIO
 * CLI UART
 * Auxilliary UART
 * I2C0 Master
 * SPI0 Master
 * On-board LED
 * Watchdog Timer
 * Chip Reset
 * System Clocks/Timers
 * System MCU Cores
 * Chip Versions
 * Chip Registers
 * Onboard Flash
 * ADC - Analog-to-Digital Coverters
 * USB (TinyUSB) CDC
 * Wireless (CYW43)


/**
* @brief Initialize all used hardware peripherals.
*
* Single function to initialize & configure hardware & peripherals on the device,
* to be run once at boot.
*
* @param none
*
* @return nothing
*/
void hardware_init(void);


/************************
 * GPIO
************************/

/*GPIO Settings
*
* Each GPIO pin should have its own entry as `GPIO_x_` in
* the following parameter lists:
*     MCU_ID
*     DIRECTION
*     PULL
*     IRQ_EN
* After each set of defines, an ordered list is created with each of the GPIO
* indexes, using the following example:
*     #define GPIO_MCU_IDS GPIO_0_MCU_ID, \   <-- note the backslash for preprocessor line break
*                          GPIO_1_MCU_ID, \
*                          GPIO_2_MCU_ID, \
*                          GPIO_3_MCU_ID
* This allows population in the array struct definition in hw_gpio.c without
* needing to update that source file. Changes to add remove GPIO only need be
* made here.
*
* Please note: GPIO index in all functions use the `GPIO_x` index number and not
* physcal MCU index or pin # for abstraction from the chip.
*******************************************************************************/

// Enable GPIO peripheral - setting to false will disable (not initialized at boot)
#define HW_USE_GPIO true

// define the total number of GPIO pins used here
// this should match the highest `GPIO_x` defines below plus one
#define GPIO_COUNT 4

// GPIO pin IDs - note that 'GPIO_x' can correspond to any unused GPIO# on the MCU
#define GPIO_0_MCU_ID 10
#define GPIO_1_MCU_ID 11
#define GPIO_2_MCU_ID 12
#define GPIO_3_MCU_ID 13
// add the above pin IDs to this ordered list for expansion in the settings array structure
#define GPIO_MCU_IDS    GPIO_0_MCU_ID, \
                        GPIO_1_MCU_ID, \
                        GPIO_2_MCU_ID, \
                        GPIO_3_MCU_ID

// set mode as input (GPIO_IN) or output (GPIO_OUT)
#define GPIO_0_DIRECTION GPIO_OUT
#define GPIO_1_DIRECTION GPIO_OUT
#define GPIO_2_DIRECTION GPIO_IN
#define GPIO_3_DIRECTION GPIO_IN
// add the above pin directions to this ordered list for expansion in the settings array structure
#define GPIO_DIRECTIONS GPIO_0_DIRECTION, \
                        GPIO_1_DIRECTION, \
                        GPIO_2_DIRECTION, \
                        GPIO_3_DIRECTION

// define GPIO pull state possible values, don't touch these!!
#define GPIO_PULL_DISABLED 0 // don't touch
#define GPIO_PULL_UP       1 // don't touch
#define GPIO_PULL_DOWN     2 // don't touch
// set pull mode of pins (GPIO_PULL_UP, GPIO_PULL_DOWN, GPIO_PULL_DISABLED)
// note this only applies to inputs, outputs on RP2040 are push/pull
#define GPIO_0_PULL GPIO_PULL_DISABLED
#define GPIO_1_PULL GPIO_PULL_DISABLED
#define GPIO_2_PULL GPIO_PULL_DISABLED
#define GPIO_3_PULL GPIO_PULL_DISABLED
// add the above GPIO pulls to this ordered list for expansion in the settings array structure
#define GPIO_PULLS  GPIO_0_PULL, \
                    GPIO_1_PULL, \
                    GPIO_2_PULL, \
                    GPIO_3_PULL

// for inputs, should an ISR be associated to a state change (true/false)
// note that only one ISR can be configured per processor core
// currently the only ISR defined is 'gpio_process' - see function prototype below (can be customized)
#define GPIO_0_IRQ_EN false
#define GPIO_1_IRQ_EN false
#define GPIO_2_IRQ_EN false
#define GPIO_3_IRQ_EN false
// add the above IRQ enables to this ordered list for expansion in the settings array structure
#define GPIO_IRQS   GPIO_0_IRQ_EN, \
                    GPIO_1_IRQ_EN, \
                    GPIO_2_IRQ_EN, \
                    GPIO_3_IRQ_EN

// GPIO settings structure, used to initialize all GPIO pins and interract with
// them at runtime.
typedef struct gpio_settings_t {
    uint    gpio_mcu_id[GPIO_COUNT];
    bool    gpio_direction[GPIO_COUNT];
    uint8_t gpio_pull[GPIO_COUNT];
    bool    gpio_irq_en[GPIO_COUNT];
} gpio_settings_t;

// global GPIO settings stored here to be accessed in other files
extern struct gpio_settings_t gpio_settings;

// GPIO event structure, used to hold GPIO IRQ events
typedef struct gpio_event_t {
    uint gpio;
    uint32_t event_mask;
    uint64_t timestamp;
} gpio_event_t;

// global GPIO event holder for last IRQ event that occured
extern struct gpio_event_t gpio_event;

// global GPIO mutex
extern SemaphoreHandle_t gpio_mutex;

/**
* @brief GPIO process ISR callback.
*
* Generic Interrupt Service Routine to process a GPIO state change IRQ.
* This is a simple routine that identifies the change that happened and updates
* a global variable. Note that on RP2040, only one ISR can be associated to a
* GPIO IRQ event per core.
*
* @param gpio which GPIO caused this interrupt
* @param event_mask which events caused this interrupt. See pico SDK gpio.h 'gpio_irq_level' for details
*
* @return nothing
*/
void gpio_process(uint gpio, uint32_t event_mask);

/**
* @brief Initialize all used GPIO.
*
* Initialize all of the GPIO pins defined in the gpio_settings configuration
* structure, based on the configuration definitions above.
*
* @param none
*
* @return nothing
*/
void gpio_init_all(void);

/**
* @brief Get the value of a single GPIO pin.
*
* Gets the value (0, 1) of a single GPIO pin defined by its ID - GPIO_x_MCU_ID
*
* @param gpio_id GPIO ID x given by GPIO_x_MCU_ID definition
*
* @return GPIO pin state (0, 1)
*/
bool gpio_read_single(uint gpio_id);

/**
* @brief Set the value of a single GPIO pin.
*
* Sets the value (0, 1) of a single GPIO pin defined by its ID - GPIO_x_MCU_ID
*
* @param gpio_id GPIO ID x given by GPIO_x_MCU_ID definition
*
* @return nothing
*/
void gpio_write_single(uint gpio_id, bool value);

/**
* @brief Read values of all used GPIO pins.
*
* Reads the current state of all GPIO pins that are defined as GPIO_IN.
* The return value is a 32-bit number with LSB representing GPIO_0 as defined
* in the settings above, and MSB representing GPIO_31 (uint_32 return value
* supports up to 32 GPIO pins). Any bits representing pins that are set to
* GPIO_OUT are ignored. The global gpio_settings structure can be used to
* iterate through used GPIO inputs and associate them with physical pin number.
*
* @param none
*
* @return 32-bit number containing up to 32 GPIO pin state values
*/
uint32_t gpio_read_all(void);

/**
* @brief Write the values of all used GPIO pins.
*
* Writes the value of all GPIO pins that are defined as GPIO_OUT, given a 32-bit
* number representing up to 32 individual GPIO pins. Individual bits representing
* single GPIO outputs can be shifted in based on the pin indexes defined in
* the global gpio_settings structure. Any bits that correspond to pins that are
* set to GPIO_IN are ignored.
*
* @param gpio_states 32-bit number containing up to 32 GPIO values to write
*
* @return nothing
*/
void gpio_write_all(uint32_t gpio_states);


/************************
 * CLI UART
*************************/

// CLI UART Settings
#define UART_ID_CLI         uart0
#define UART_BAUD_RATE_CLI  115200
#define UART_DATA_BITS_CLI  8
#define UART_STOP_BITS_CLI  1
#define UART_PARITY_CLI     UART_PARITY_NONE
#define UART_TX_PIN_CLI     0
#define UART_RX_PIN_CLI     1

// global CLI UART mutex
extern SemaphoreHandle_t cli_uart_mutex;

/**
* @brief Initialize the CLI UART.
*
* Initialization routine for the UART peripheral used for the
* Command Line Interface using the settings defined above.
*
* @param none
*
* @return nothing
*/
void cli_uart_init(void);

/**
* @brief Write character to CLI UART.
*
* Write a single character to the UART used for the Command Line Interface.
* This function is used by Microshell for all CLI output.
*
* @param tx_char character to write
*
* @return 1 upon success, 0 upon failure
*/
int cli_uart_putc(char tx_char);

/**
* @brief Read character from CLI UART.
*
* Read a single character from the UART used for the Command Line Interface in
* a non-blocking fashion. This function is used by Microshell for all CLI input.
*
* @param none
*
* @return Character read from UART
*/
char cli_uart_getc(void);

/**
* @brief Print a string to the CLI UART.
*
* Print an entire string to the UART used for the Command Line Interface. This
* function can be used in the CLI outside of Microshell to print debug & status
* messages. The function will block until the entire string is written.
*
* @param print_string pointer to the string to print
*
* @return nothing
*/
void cli_uart_puts(const char *print_string);


/************************
 * Auxilliary UART
*************************/

// Enable Aux UART - setting to false will disable (not initialized at boot)
#define HW_USE_AUX_UART true

// AUX UART Settings
#define UART_ID_AUX             uart1
#define UART_BAUD_RATE_AUX      115200
#define UART_DATA_BITS_AUX      8
#define UART_STOP_BITS_AUX      1
#define UART_PARITY_AUX         UART_PARITY_NONE
#define UART_TX_PIN_AUX         8
#define UART_RX_PIN_AUX         9
#define UART_RX_FIFO_SIZE_AUX   32 // from RP2040 datasheet (hardware limited)

// global Aux UART mutex
extern SemaphoreHandle_t aux_uart_mutex;

/**
* @brief Initialize the AUX UART.
*
* Initialization routine for the UART peripheral used for the
* auxilliary serial interface using the settings defined above.
*
* @param none
*
* @return nothing
*/
void aux_uart_init(void);

/**
* @brief Write bytes to AUX UART.
*
* Write one or more bytes to the UART used for the auxilliary serial interface.
*
* @param tx_data pointer to the data to write
* @param tx_len  length in bytes of data to write
*
* @return 1 upon success, 0 upon failure
*/
int aux_uart_write(uint8_t *tx_data, size_t tx_len);

/**
* @brief Read bytes from AUX UART.
*
* Read any bytes available on the auxilliary UART in a non-blocking fashion,
* until no more bytes are immediately available or rx_len has been reached.
*
* @param rx_data pointer to the data buffer to hold the bytes received
* @param rx_len  maximum number of bytes to read (buffer size)
*
* @return number of bytes received
*/
int aux_uart_read(uint8_t *rx_data, size_t rx_len);


/************************
 * I2C0 Master
*************************/

// Enable I2C0 peripheral - setting to false will disable (not initialized at boot)
#define HW_USE_I2C0 true

// I2C0 Settings
#define I2C0_ID            i2c0
#define I2C0_FREQ_KHZ      100
#define I2C0_SDA_PIN       20
#define I2C0_SCL_PIN       21

// global I2C0 mutex
extern SemaphoreHandle_t i2c0_mutex;

/**
* @brief Initialize I2C bus 0.
*
* Initialization routine for the I2C0 peripheral using the settings defined
* above.
*
* @param none
*
* @return nothing
*/
void i2c0_init(void);

/**
* @brief Write bytes onto I2C0 bus.
*
* Put byte(s) onto I2C bus 0 given a specific device address. It is assumed this
* is a blocking function.
*
* @param addr 7-bit address (0-127) of target device on I2C bus
* @param write_data pointer to the byte array to write onto the bus
* @param length number of bytes to write onto the bus
*
* @return number of bytes written, or -1 if no response
*/
int i2c0_write(uint8_t addr, const uint8_t *write_data, size_t length);

/**
* @brief Read bytes off of I2C0 bus.
*
* Read byte(s) from a given device adress on I2C bus 0. It is assumed this is a
* blocking function.
*
* @param addr 7-bit address (0-127) of target device on I2C bus
* @param read_data pointer to the byte array to store the data read from the bus
* @param length number of bytes to read from the bus
*
* @return number of bytes read, or -1 if no response
*/
int i2c0_read(uint8_t addr, uint8_t *read_data, size_t length);


/************************
 * SPI0 Master
*************************/

// Enable SPI0 peripheral - setting to false will disable (not initialized at boot)
#define HW_USE_SPI0 true

// SPI0 Settings
#define SPI0_ID             spi0
#define SPI0_FREQ_KHZ       500
#define SPI0_MISO_PIN       4
#define SPI0_MOSI_PIN       3
#define SPI0_CLK_PIN        2
#define SPI0_CS_PIN_DEFAULT 5

// Target/peripheral device settings -
// used to define a target device by the chip select it is attached to, and the
// register address that can be interrogated for the expected device ID to
// validate communication
#define SPI0_TARGET_DEV_0_CS     SPI0_CS_PIN_DEFAULT
#define SPI0_TARGET_DEV_0_ID     0x60
#define SPI0_TARGET_DEV_0_ID_REG 0xD0
// more TARGET_DEV entries and associated chip selects can be added to expand this list

// global SPI0 mutex
extern SemaphoreHandle_t spi0_mutex;

/**
* @brief Initialize SPI bus 0.
*
* Initialization routine for the SPI0 peripheral using the settings defined
* above.
*
* @param none
*
* @return nothing
*/
void spi0_init(void);

/**
* @brief Write a byte into a SPI0 register address.
*
* Writes a single byte to a target device identified by the chip select pin, into
* the register at the given address.
*
* @param cs_pin    master pin number for the chip select line connected to target device
* @param reg_addr  8-bit register address to write to
* @param data_byte single-byte of data to write into target register    
*
* @return 1 if success, 0 if failure
*/
int spi0_write_register(uint8_t cs_pin, uint8_t reg_addr, uint8_t data_byte);

/**
* @brief Read byte(s) from a SPI0 register address.
*
* Reads one or more bytes from a target device identified by the chip select pin,
* from the given register address. Multiple successive registers can be read in
* series by specifying the read length. This is a blocking read.
*
* @param cs_pin   master pin number for the chip select line connected to target device
* @param reg_addr 8-bit register address to read from
* @param read_buf pointer to the byte array to store the data read from target
* @param len      number of successive register locations to read from
*
* @return number of bytes read
*/
int spi0_read_registers(uint8_t cs_pin, uint8_t reg_addr, uint8_t *read_buf, uint16_t len);


/************************
 * On-board LED
*************************/

// Enable onboard LED - setting to false will disable (not initialized at boot)
// note - if using a Pico W board, the onboard LED is connected to the CYW43 radio -
// this would need the WiFi chip initialized, which we do not currently do. Thus
// the onboard LED will be default disabled in that case.
#define HW_USE_ONBOARD_LED true

// On-board LED Settings
#define PIN_NO_ONBOARD_LED 25 // does not apply to Pico W board (connected to CYW43)

/**
* @brief Initialization routine for the onboard LED.
*
* This function initializes the GPIO used as the driver for the onboard LED.
*
* @param none
*
* @return nothing
*/
void onboard_led_init(void);

/**
* @brief Sets the state of the onboard LED.
*
* This function sets the value of the onboard LED GPIO to enable (turn ON) or
* disable (turn OFF) the LED.
*
* @param led_state 0 (disabled) or 1 (enabled)
*
* @return nothing
*/
void onboard_led_set(bool led_state);

/**
* @brief Gets the state of the onboard LED.
*
* This function gets the value of the onboard LED GPIO to determine if the LED
* is enabled (ON) or disabled (OFF).
*
* @param none
*
* @return 0 (disabled) or 1 (enabled)
*/
bool onboard_led_get(void);


/************************
 * Watchdog Timer
*************************/

// Watchdog Timer Settings
#define WATCHDOG_DELAY_MS        5000 // default watchdog timer delay
#define WATCHDOG_DELAY_REBOOT_MS 100  // delay for reboot function

/**
* @brief Enables the watchdog timer.
*
* This function enables the hardware watchdog timer at the specified interval.
* On RP2040, there will be a standard soft reboot when watchdog timer expires.
*
* @param delay_ms millisecond interval for the watchdog before reboot
*
* @return nothing
*/
void watchdog_en(uint32_t delay_ms);

/**
* @brief Disables the watchdog timer.
*
* This function disables the hardware watchdog timer.
*
* @param none
*
* @return nothing
*/
void watchdog_dis(void);

/**
* @brief Reset the watchdog timer.
*
* Resets the watchdog timer to the start value given when the timer was started
* with watchdog_en(). One must kick the dog before this timer expires to prevent
* the watchdog expiration action (default to soft reboot). Be assured that this
* source code in no way condones kicking dogs or any other life form for that
* matter.
*
* @param none
*
* @return nothing `
*/
void watchdog_kick(void);

/**
* @brief Force a reboot.
*
* Forces a reboot of the MCU by waiting for watchdog timeout.
*
* @param none
*
* @return nothing
*/
void force_watchdog_reboot(void);


/************************
 * Chip Reset
*************************/

// Reset reason types
typedef enum {POWERON,   // normal power-on reset
              GLITCH,    // power supply glitch reset
              BROWNOUT,  // brownout reset
              WATCHDOG,  // watchdog timeout reset
              FORCED,    // application-requested reset
              PIN,       // external pin-toggled reset
              DOUBLETAP, // double-tap external pin reset
              DEBUGGER,  // attached debugger reset
              UNKNOWN    // reset reason could not be detected
             } reset_reason_t;

// Global reset reason type - set at boot
extern reset_reason_t last_reset_reason;

/**
* @brief Get the reset reason.
*
* This function will return the reason for the last system reset, i.e. POR,
* watchdog, program-requested, reset pin, or debugger.
*
* @param none
*
* @return reset reason given by reset_reason_t enum
*/
reset_reason_t get_reset_reason(void);

/**
* @brief Get the reset reason as a string.
*
* This function is a wrapper for get_reset_reason() which returns a human-readable
* string that can be used for printing to the CLI.
*
* @param reset_reason reset reason given by reset_reason_t enum, provided by get_reset_reason()
*
* @return pointer to the reset reason string
*/
char* get_reset_reason_string(reset_reason_t reset_reason);

/**
* @brief Reset chip to the bootloader.
*
* Forces a reset of the MCU into the bootloader so that firmware can be updated.
* On RP2040 this is the built-in DFU mode.
*
* @param none
*
* @return nothing
*/
void reset_to_bootloader(void);


/************************
 * System Clocks/Timers
*************************/

/**
* @brief Get the system clock frequency.
*
* Returns the current frequency of the internal system clock that the MCU has
* determined it is using, in Hz.
*
* @param none
*
* @return system clock frequency, Hz
*/
uint32_t get_sys_clk_hz(void);

/**
* @brief Get system timer value.
*
* Returns the current system timer value in microseconds. Size uint64 means the
* timer will not roll over for 584,942 years. When using the function to print
* the time, use snprintf with 14 bytes reserved for ASCII timer value to get ~3 years
* before rollover (13 bytes for ~0.3 years, 15 bytes for ~30 years, etc.)
*
* @param none
*
* @return timer value, microseconds
*/
uint64_t get_time_us(void);

/**
* @brief Wait for the specified amount of microseconds.
*
* Waits (delays) execution of further code for the specified number of microseconds.
* Similar to running many NOOPs to waste cycles for a specified amount of time.
*
* @param delay_us number of microseconds to delay
*
* @return nothing
*/
void wait_here_us(uint64_t delay_us);


/************************
 * System MCU Cores
*************************/

/**
* @brief Get the core that is executing.
*
* Returns the current core number that this function was executed on when
* running on a multi-core MCU.
*
* @param none
*
* @return core number
*/
uint8_t get_core(void);


/************************
 * Chip Versions
*************************/

/**
* @brief Get the silicon version number.
*
* Returns the integer value version number of the MCU silicon.
*
* @param none
*
* @return version number
*/
uint8_t get_chip_version(void);

/**
* @brief Get the ROM version number.
*
* Returns the integer value version number of the MCU's ROM.
*
* @param none
*
* @return version number
*/
uint8_t get_rom_version(void);


/************************
 * Chip Registers
*************************/

/**
* @brief Read a chip register.
*
* Reads and returns a single 32-bit register from the MCU's memory-mapped
* register space.
*
* @param reg_addr address of the register to read
*
* @return 32-bit value of the register
*/
uint32_t read_chip_register(uint32_t reg_addr);


/************************
 * Onboard Flash
*************************/

// Enable onboard flash - setting to false will disable program access to flash
#define HW_USE_ONBOARD_FLASH true

// Onboard Flash Settings
#define FLASH0_FS_SIZE       (256 * 1024)       // size of the 'flash0' filesystem (intended for littlefs to manage)
#define PATHNAME_MAX_LEN      32                // maximum string length of path+filename on the filesystem
#define FILE_SIZE_MAX         BUF_OUT_SIZE      // maximum size of a single file in bytes - set to shell output buffer size so entire files can be dumped
#define FLASH0_BLOCK_SIZE     FLASH_SECTOR_SIZE // "block" size in littlefs terms is "sector" size in RP2040 terms
#define FLASH0_PAGE_SIZE      FLASH_PAGE_SIZE   // littlefs page size is equal to flash page size
#define FLASH0_CACHE_SIZE     FLASH_PAGE_SIZE   // read/write cache sizes are equal to a page
#define FLASH0_LOOKAHEAD_SIZE 32                // lookahead buffer size for tracking block allocation
#define FLASH0_BLOCK_CYCLES   500               // max number of erase cycles for a block (for wear leveling)

// Onboard flash usage detail structure
typedef struct flash_usage_t {
    int flash_total_size;
    int program_used_size;
    int fs_reserved_size;
    int flash_free_size;
} flash_usage_t;

// global onboard flash mutex
extern SemaphoreHandle_t onboard_flash_mutex;

/**
* @brief Initialize onboard flash memory.
*
* This function enables usage of the onboard flash memory (this is the same NVM
* used for program storage). Note that all this function does at this time is
* create the mutex used for onboard flash access, since flash I/O is already
* technically available at runtime.
*
* @param none
*
* @return nothing
*/
void onboard_flash_init(void);

/**
* @brief Read from onboard flash.
*
* This function reads a given address range of "onboard" flash (the same NVM
* used for program storage). Argument structure conforms to littlefs, however
* the function may also be used standalone.
*
* @param c littlefs configuration, can populate with a dummy if using outside of littlefs
* @param block address of the flash block (sector) to read from
* @param offset byte offset within the block of read start
* @param buffer pointer to the read buffer
* @param size size of data to read
*
* @return 0 upon success
*/
int onboard_flash_read(const struct lfs_config *c, uint32_t block, uint32_t offset, void* buffer, uint32_t size);

/**
* @brief Write to onboard flash.
*
* This function writes data to "onboard" flash (the same NVM used for program
* storage). Note that the block of flash written to must have been previously
* erased. Argument structure conforms to littlefs, however the function may also
* be used standalone.
*
* @param c littlefs configuration, can populate with a dummy if using outside of littlefs
* @param block address of the flash block (sector) to write to
* @param offset byte offset within the block of write start
* @param buffer pointer to the buffer containing data to write
* @param size size of data to write
*
* @return 0 upon success
*/
int onboard_flash_write(const struct lfs_config *c, uint32_t block, uint32_t offset, const void* buffer, uint32_t size);

/**
* @brief Erase onboard flash.
*
* This function erases a block (sector) of "onboard" flash (the same NVM used
* for program storage). Argument structure conforms to littlefs, however the
* function may also be used standalone.
*
* @param c littlefs configuration, can populate with a dummy if using outside of littlefs
* @param block address of the flash block (sector) to erase
*
* @return 0 upon success
*/
int onboard_flash_erase(const struct lfs_config *c, uint32_t block);

/**
* @brief Sync the state of onboard flash.
*
* This is a dummy sync function since littlefs will hardfault if the sync
* function pointer is null.
*
* @param c littlefs configuration
*
* @return 0 upon success
*/
int onboard_flash_sync(const struct lfs_config *c);

/**
* @brief Get flash usage detail.
*
* Determine the application binary flash memory usage, total flash size, and
* remaining "free" space available (minus any other NVM consumers such as
* filesystem, etc)
*
* @param none
*
* @return structure containing flash usage detail
*/
flash_usage_t onboard_flash_usage(void);


/*************************************
 * ADC - Analog-to-Digital Coverters
**************************************/

// Enable ADC peripheral - setting to false will disable all ADCs (not initialized at boot)
#define HW_USE_ADC true

// ADC channel settings
#define ADC0_GPIO 26 // this is ADC0
#define ADC1_GPIO 27 // this is ADC1
#define ADC2_GPIO 28 // this is ADC2

// individual ADC initialization
#define ADC0_INIT true
#define ADC1_INIT false
#define ADC2_INIT false

// ACD conversion
#define ADC_CONV_FACT (3.3f / (1 << 12)) // 3.3V reference, 12-bit resolution

// global ADC mutex
extern SemaphoreHandle_t adc_mutex;

/**
* @brief Initialize the ADC peripheral(s).
*
* Initializes the ADC peripheral. On chips with multiple ADC peripherals, each
* will be initialized seperately within this function. Note that even if an MCU
* has multiple ADC input pins, they may share a single ADC block (as is the case
* on RP2040).
*
* @param none
*
* @return nothing
*/
void adcs_init(void);

/**
* @brief Read ADC value.
*
* Read the result of the analog-to-digital conversion on the given analog input
* channel, converted to a floating-point voltage value. Note that these channels
* may share the same ADC block internal to the MCU. Here, "channel" refers to
* a specific GPIO/pin given in the settings above.
*
* @param adc_channel channel number of the ADC to read
*
* @return value of the analog-to-digital conversion (voltage reading)
*/
float read_adc(int adc_channel);


/************************
 * USB (TinyUSB) CDC
*************************/

// Enable USB controller - setting to false will disable (not initialized at boot)
// note that if CLI uses USB, it will override this
#define HW_USE_USB true

// set USB mode (device)
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE

// number of composite CDC interfaces and data buffer sizes
#define CFG_TUD_CDC 1
#define CFG_TUD_CDC_RX_BUFSIZE 64
#define CFG_TUD_CDC_TX_BUFSIZE 64
// make sure buffer size is within bounds of uint16_t
#if CFG_TUD_CDC_RX_BUFSIZE > 65535 || CFG_TUD_CDC_TX_BUFSIZE > 65535
    #error "USB buffer size exceeds bounds"
#endif

// CDC interface ID for CLI over USB (if used)
#define CDC_ID_CLI 0

// USB interface data structure
typedef struct usb_iface_t {
    uint8_t  iface_id;                          // composite interface ID
    bool     is_conn;                           // connection flag
    uint8_t  rx_buffer[CFG_TUD_CDC_RX_BUFSIZE]; // receive buffer (bytes from host)
    uint8_t  tx_buffer[CFG_TUD_CDC_TX_BUFSIZE]; // transmit buffer (bytes to host)
    uint16_t rx_pos;                            // current position in rx buffer
    uint16_t tx_pos;                            // current position in tx buffer
} usb_iface_t;

// global USB mutex
extern SemaphoreHandle_t usb_mutex;

/**
* @brief Initialize the serial number used for USB device ID.
*
* Retrieves the unique ID (serial number) of the device and sets it as the USB
* device serial no. reported to the bus.
*
* @param none
*
* @return nothing
*/
void usb_serialno_init(void);

/**
* @brief Initialize the USB device.
*
* Initializes the USB controller in device mode using TinyUSB.
*
* @param none
*
* @return nothing
*/
void usb_device_init(void);

/**
* @brief Read bytes out of the USB device buffer.
*
* This function will check for an active connection on the given interface,
* check if there are bytes available in the device buffer, and then read out
* (copy) any available bytes into the USB interface structure instance. The
* rx_pos value in usb_iface will then be updated to reflect how many bytes are
* currently available in the rx buffer.
*
* @param usb_iface USB interface structure for a given composite device ID
*
* @return nothing
*/
void usb_read_bytes(struct usb_iface_t *usb_iface);

/**
* @brief Write bytes to the USB device buffer.
*
* This function will copy bytes from the given USB interface structure into the
* device buffer (if tx_pos > 0 indicating bytes available to be written),
* and send the bytes to the host.
*
* @param usb_iface USB interface structure for a given composite device ID
*
* @return nothing
*/
void usb_write_bytes(struct usb_iface_t *usb_iface);

/**
* @brief Write character to CLI over USB.
*
* Write a single character to the USB CDC ID used for the Command Line Interface.
* This function is used by Microshell for all CLI output if CLI_USE_USB=1 at
* compile time.
*
* @param tx_char character to write to USB
*
* @return 1 upon success, 0 upon failure
*/
int cli_usb_putc(char tx_char);

/**
* @brief Read character from CLI over USB.
*
* Read a single character from the USB CDC ID used for the Command Line Interface
* in a non-blocking fashion. This function is used by Microshell for all CLI
* input if CLI_USE_USB=1 at compile time.
*
* @param none
*
* @return Character read from USB
*/
char cli_usb_getc(void);


/**************************
 * Wireless (CYW43 WiFi/BT)
***************************/

// Enable CYW43 wireless module - setting to false will disable (not initialized at boot)
// This only applies to boards with the wireless module, otherwise it is ignored.
// note that if using the onboard LED, the CYW43 must be enabled (LED is controlled by CYW43).
// Individual wireless features (WiFi/BT/etc) are individually enabled in project.cmake.
#define HW_USE_WIRELESS true

// Make sure CYW43 is enabled if using onboard LED
#if HAS_CYW43 == true && HW_USE_WIRELESS == false && HW_USE_ONBOARD_LED == true
#error "CYW43 must be enabled in hardware_config.h if using onboard LED"
#endif

// Note: due to complexity, all WiFi-specific typedefs, options, function
// declarations and documentation are contained in the hw_wifi.h file.


#endif /* HARDWARE_CONFIG_H */

================================================
FILE: hardware/rp2xxx/hw_adc.c
================================================
/******************************************************************************
 * @file hw_adc.c
 *
 * @brief Functions for interacting with the analog-to-digital converter
 *        peripheral(s). The implementation of these functions is MCU-specific
 *        and will need to be changed if ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "semphr.h"

// global ADC mutex
SemaphoreHandle_t adc_mutex;


void adcs_init(void) {
    // create ADC mutex
    adc_mutex = xSemaphoreCreateMutex();

    adc_init();
    if (ADC0_INIT)
        adc_gpio_init(ADC0_GPIO);
    if (ADC1_INIT)
        adc_gpio_init(ADC1_GPIO);
    if (ADC2_INIT)
        adc_gpio_init(ADC2_GPIO);
}

float read_adc(int adc_channel) {
    uint16_t result = 0;
    if(xSemaphoreTake(adc_mutex, 10) == pdTRUE) {
        // select ADC input pin/channel (0-2)
        adc_select_input(adc_channel);
        // read raw value
        result = adc_read();
        xSemaphoreGive(adc_mutex);
    }
    // return floating-point voltage
    return result * ADC_CONV_FACT;
}

================================================
FILE: hardware/rp2xxx/hw_clocks.c
================================================
/******************************************************************************
 * @file hw_clocks.c
 *
 * @brief Functions for interacting with clocks and timers. The implementation
 *        of these functions is MCU-specific and will need to be changed if
 *        ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "hardware_config.h"
#include "hardware/clocks.h"
#include "hardware/timer.h"


uint32_t get_sys_clk_hz(void){
    return clock_get_hz(clk_sys);
}

uint64_t get_time_us(void){
    return time_us_64();
}

void wait_here_us(uint64_t delay_us) {
    busy_wait_us(delay_us);
}

================================================
FILE: hardware/rp2xxx/hw_cores.c
================================================
/******************************************************************************
 * @file hw_cores.c
 *
 * @brief Functions for querying information about MCU cores. The implementation
 *        of these functions is MCU-specific and will need to be changed if
 *        ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdint.h>
#include "hardware_config.h"
#include "pico/platform.h"


uint8_t get_core(void){
    return get_core_num();
}

================================================
FILE: hardware/rp2xxx/hw_gpio.c
================================================
/******************************************************************************
 * @file hw_gpio.c
 *
 * @brief Functions for interacting with the GPIO pins.
 *        The implementation of these functions is MCU-specific and will need
 *        to be changed if ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "FreeRTOS.h"
#include "semphr.h"


// global GPIO settings structure, declared in hardware_config.h
// structure arrays are expanded using the GPIO list defines, so they better
// not be jacked up in that file
struct gpio_settings_t gpio_settings = {
    .gpio_mcu_id = {GPIO_MCU_IDS},
    .gpio_direction = {GPIO_DIRECTIONS},
    .gpio_pull = {GPIO_PULLS},
    .gpio_irq_en = {GPIO_IRQS}
};

// global GPIO last IRQ event storage location
struct gpio_event_t gpio_event;

// global GPIO mutex
SemaphoreHandle_t gpio_mutex;

void gpio_process(uint gpio, uint32_t event_mask) {
    // populate the IRQ event structure
    gpio_event.gpio = gpio;
    gpio_event.event_mask = event_mask;
    gpio_event.timestamp = get_time_us();
}

void gpio_init_all(void) {
    // create GPIO mutex
    gpio_mutex = xSemaphoreCreateMutex();
    
    for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
        // enable the GPIO pin
        gpio_init(gpio_settings.gpio_mcu_id[gpio_num]);

        // set mode to input or output
        gpio_set_dir(gpio_settings.gpio_mcu_id[gpio_num], gpio_settings.gpio_direction[gpio_num]);

        // if GPIO is an input, set pull direction and IRQ if applicable
        if (gpio_settings.gpio_direction[gpio_num] == GPIO_IN) {
            // set pull direction
            if (gpio_settings.gpio_pull[gpio_num] == GPIO_PULL_UP) {
                gpio_pull_up(gpio_settings.gpio_mcu_id[gpio_num]);
            }
            else if (gpio_settings.gpio_pull[gpio_num] == GPIO_PULL_DOWN) {
                gpio_pull_down(gpio_settings.gpio_mcu_id[gpio_num]);
            }
            else {
                gpio_disable_pulls(gpio_settings.gpio_mcu_id[gpio_num]);
            }

            // set up IRQ if enabled - IRQ is set to happen on both edges
            // ISR function is gpio_process()
            if (gpio_settings.gpio_irq_en[gpio_num] == true) {
                gpio_set_irq_enabled_with_callback(gpio_settings.gpio_mcu_id[gpio_num], GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_process);
            }
        }
    }
}

bool gpio_read_single(uint gpio_id) {
    bool gpio_value = 0;
    if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
        gpio_value = gpio_get(gpio_settings.gpio_mcu_id[gpio_id]);
        xSemaphoreGive(gpio_mutex);
    }
    return gpio_value;
}

void gpio_write_single(uint gpio_id, bool value) {
    // only write to a pin that has been configured as output
    if (gpio_settings.gpio_direction[gpio_id] == GPIO_OUT) {
        if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
            gpio_put(gpio_settings.gpio_mcu_id[gpio_id], value);
            xSemaphoreGive(gpio_mutex);
        }
    }
}

uint32_t gpio_read_all(void) {
    uint32_t gpio_values = 0;

    // cycle through all the configured GPIOs
    for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
        if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
            // move the read pin state into the correct bit position corresponding to current GPIO index
            gpio_values |= gpio_get(gpio_settings.gpio_mcu_id[gpio_num]) << gpio_num;
            xSemaphoreGive(gpio_mutex);
        }
    }

    return gpio_values;
}

void gpio_write_all(uint32_t gpio_states) {
    bool gpio_value;
    for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
        // write GPIO if it is set to an output
        if (gpio_settings.gpio_direction[gpio_num] == GPIO_OUT) {
            if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
                // shift out the bit corresponding to the current GPIO index into a single bool
                gpio_value = (bool) ((gpio_states >> gpio_num) & (uint32_t) 1);
                gpio_put(gpio_settings.gpio_mcu_id[gpio_num], gpio_value);
                xSemaphoreGive(gpio_mutex);
            }
        }
    }
}

================================================
FILE: hardware/rp2xxx/hw_i2c.c
================================================
/******************************************************************************
 * @file hw_i2c.c
 *
 * @brief Functions for configuring and interacting with the I2C peripherals.
 *        The implementation of these functions is MCU-specific and will need
 *        to be changed if ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/i2c.h"
#include "semphr.h"

// global I2C0 mutex
SemaphoreHandle_t i2c0_mutex;


void i2c0_init(void) {
    // create I2C0 mutex
    i2c0_mutex = xSemaphoreCreateMutex();

    i2c_init(I2C0_ID, I2C0_FREQ_KHZ * 1000);
    gpio_set_function(I2C0_SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(I2C0_SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(I2C0_SDA_PIN);
    gpio_pull_up(I2C0_SCL_PIN);
}

int i2c0_write(uint8_t addr, const uint8_t *write_data, size_t length) {
    i2c_inst_t *i2c = I2C0_ID;
    int bytes_written = 0;

    // write bytes if mutex is obtained
    if(xSemaphoreTake(i2c0_mutex, 10) == pdTRUE) {
        bytes_written = i2c_write_blocking(i2c, addr, write_data, length, false);
        xSemaphoreGive(i2c0_mutex);
    }

    // check for Pico SDK-specific error and replace with -1
    if(bytes_written == PICO_ERROR_GENERIC) {
        bytes_written = -1;
    }

    return bytes_written;
}

int i2c0_read(uint8_t addr, uint8_t *read_data, size_t length) {
    i2c_inst_t *i2c = I2C0_ID;
    int bytes_read = 0;

    // read bytes if mutex is obtained
    if(xSemaphoreTake(i2c0_mutex, 10) == pdTRUE) {
        bytes_read = i2c_read_blocking(i2c, addr, read_data, length, false);
        xSemaphoreGive(i2c0_mutex);
    }

    // check for Pico SDK-specific error and replace with -1
    if(bytes_read == PICO_ERROR_GENERIC) {
        bytes_read = -1;
    }

    return bytes_read;
}

================================================
FILE: hardware/rp2xxx/hw_net.c
================================================
/******************************************************************************
 * @file hw_net.c
 *
 * @brief Settings, definitions, and function prototypes for various network
 *        stacks, much of it tied to lwIP. Hardware and SDK-specific
 *        implementation resides within these functions, hence its location
 *        within the hardware/[platform] directory.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-06-2025
 *
 * @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 *
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <string.h>
#include "hw_net.h"
#include "hardware_config.h"
#include "version.h"
#include "FreeRTOS.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/mdns.h"
#include "lwip/apps/httpd.h"
#include "lwip/arch.h"


/************************
 * lwIP mDNS responder
*************************/

// mDNS responder callback function for adding service text records
static void srv_txt(struct mdns_service *service, void *txt_userdata) {
    err_t res;
    LWIP_UNUSED_ARG(txt_userdata);

    res = mdns_resp_add_service_txtitem(service, "path=/", 6);
    LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
}

void net_mdns_init(void) {
    // initialize lwIP mDNS
    mdns_resp_init();
    // bind mDNS to the network interface and responder text record service
    mdns_resp_add_netif(&cyw43_state.netif[CYW43_ITF_STA], CYW43_HOST_NAME);
    mdns_resp_add_service(&cyw43_state.netif[CYW43_ITF_STA], "bbos-httpd", "_http", DNSSD_PROTO_TCP, 80, srv_txt, NULL);
}


/**************************
 * lwIP HTTPD (http server)
***************************/

#ifdef ENABLE_HTTPD

// cgi handler function example - refer to tCGIHandler typedef in lwIP httpd.h for prototype description
static const char *httpd_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
    // check for 
    if (!strcmp(pcParam[0], "led-state")) {
        if (!strcmp(pcValue[0], "on")) {
            onboard_led_set(1);
        } else if (!strcmp(pcValue[0], "off")) {
            onboard_led_set(0);
        } else {
            return "/404.html";
        }
        return "/test.shtml";
    } else {
        return "/404.html";
    }
}

// cgi handler path assignments for any functions above
static tCGI httpd_cgi_paths[] = {
    { "/test.shtml", httpd_cgi_handler }
};

// ssi tags, handled by index # in the ssi handler function
// Note that maximum tag length is 8 characters (LWIP_HTTPD_MAX_TAG_NAME_LEN)
// There will be no warning if exceeded! The tag will simply not be processed!
static const char * httpd_ssi_tags[] = {
    "bbosver",
    "projinfo",
    "platform",
    "uptime",
    "freeram",
    "freeflsh",
    "ledstate"
};

// ssi handler function - refer to tSSIHandler typedef in lwIP httpd.h for prototype description
// note that the integer case values in the switch need to match the index of the tag in the httpd_ssi_tags array
u16_t httpd_ssi_handler(int iIndex, char *pcInsert, int iInsertLen, uint16_t current_tag_part, uint16_t *next_tag_part) {
    size_t tag_print;
    switch (iIndex) {
        case 0: { /* "version" */
            tag_print = snprintf(pcInsert, iInsertLen, xstr(BBOS_VERSION_MAJOR) "." xstr(BBOS_VERSION_MINOR) "%c", BBOS_VERSION_MOD);
            break;
        }
        case 1: {
            tag_print = snprintf(pcInsert, iInsertLen, xstr(PROJECT_NAME) " v" xstr(PROJECT_VERSION));
            break;
        }
        case 2: {
            tag_print = snprintf(pcInsert, iInsertLen, xstr(BOARD) " - " xstr(MCU_NAME));
            break;
        }
        case 3: { /* uptime */
            // get current microsecond timer value
            uint64_t current_time_us = get_time_us();
            // calculate time divisions, i.e. maths
            uint16_t num_seconds = (uint16_t) (current_time_us / 1E6) % 60;
            uint16_t num_minutes = (uint16_t) (((current_time_us / 1E6) - num_seconds) / 60) % 60;
            uint16_t num_hours   = (uint16_t) (((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) % 24;
            uint16_t num_days    = (uint16_t) ((((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) - num_hours) / 24;
            tag_print = snprintf(pcInsert, iInsertLen, "%dd %dh %dm %ds", num_days, num_hours, num_minutes, num_seconds);
            break;
        }
        case 4: { /* freeram */
            HeapStats_t *heap_stats = pvPortMalloc(sizeof(HeapStats_t)); // structure to hold heap stats results
            vPortGetHeapStats(heap_stats);  // get the heap stats
            tag_print = snprintf(pcInsert, iInsertLen, "%.1f KB / %.1f KB",
                                                        ( (float)heap_stats->xAvailableHeapSpaceInBytes / 1024 ),
                                                        ( (float)(configTOTAL_HEAP_SIZE) / 1024 ));
            vPortFree(heap_stats);
            break;
        }
        case 5: { /* freeflsh <-- notice no 'a' to fit into 8 characters */
            flash_usage_t flash_usage;
            // get the flash usage data struct
            flash_usage = onboard_flash_usage();
            tag_print = snprintf(pcInsert, iInsertLen, "%.1f KB / %.1f KB",
                                                        ( (float)flash_usage.flash_free_size / 1024 ),
                                                        ( (float)flash_usage.flash_total_size / 1024 ));
            break;
        }
        case 6: { /* ledstate */
            tag_print = snprintf(pcInsert, iInsertLen, onboard_led_get() ? "ON" : "OFF");
            break;
        }
        default: { /* unknown tag */
            tag_print = 0;
            break;
        }
    }
    return (u16_t)tag_print;
}

void net_httpd_stack_init(void) {
    // set hostname for lwIP
    char hostname[sizeof(CYW43_HOST_NAME)];
    memcpy(&hostname[0], CYW43_HOST_NAME, sizeof(CYW43_HOST_NAME));
    netif_set_hostname(&cyw43_state.netif[CYW43_ITF_STA], hostname);
    
    // initialize lwIP httpd stack
    httpd_init();

    // register cgi and ssi handlers with lwIP
    http_set_cgi_handlers(httpd_cgi_paths, LWIP_ARRAYSIZE(httpd_cgi_paths));
    http_set_ssi_handler(httpd_ssi_handler, httpd_ssi_tags, LWIP_ARRAYSIZE(httpd_ssi_tags));
}

#endif /* ENABLE_HTTPD */

================================================
FILE: hardware/rp2xxx/hw_registers.c
================================================
/******************************************************************************
 * @file hw_registers.c
 *
 * @brief Functions for interacting directly with MCU hardware registers. The
 *        implementation of these functions is MCU-specific and will need to be
 *        changed if ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 01-15-2025
 *
 * @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 *
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdint.h>
#include "hardware_config.h"


uint32_t read_chip_register(uint32_t reg_addr) {
    return *(volatile uint32_t *)reg_addr;
}

================================================
FILE: hardware/rp2xxx/hw_reset.c
================================================
/******************************************************************************
 * @file hw_reset.c
 *
 * @brief Functions related to resetting the chip. The implementation of these
 *        functions is MCU-specific and will need to be changed if ported to a
 *        new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include <stdint.h>
#include <string.h>
#include "hardware_config.h"
#include "hardware/watchdog.h"
#include "pico/bootrom.h"

#ifdef USING_RP2040
#include "hardware/regs/vreg_and_chip_reset.h"
#elif USING_RP2350
#include "hardware/structs/powman.h"
#include "hardware/regs/powman.h"
#endif


reset_reason_t get_reset_reason(void) {
    reset_reason_t reset_reason;

#ifdef USING_RP2040
    // RP2040 CHIP_RESET register is stored in LDO_POR block -
    // it can tell us POR, RUN pin, or debugger reset
    io_rw_32 *chip_reset_reg = (io_rw_32 *)(VREG_AND_CHIP_RESET_BASE + VREG_AND_CHIP_RESET_CHIP_RESET_OFFSET);
    // first check if it was a watchdog reboot before interrogating the reset register
    if (watchdog_caused_reboot()) {
        if (watchdog_enable_caused_reboot()) {
            reset_reason = WATCHDOG; // expiration of watchdog timer
        }
        else reset_reason = FORCED;  // program-forced watchdog reboot
    }
    else if (*chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_PSM_RESTART_BITS) {
        reset_reason = DEBUGGER; // reset from debugger -- currently this is not detecting correctly
    }
    else if (*chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_POR_BITS) {
        reset_reason = POWERON;  // power-on or brownout
    }
    else if (*chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_RUN_BITS) {
        reset_reason = PIN;      // reset pin ("run" pin) toggled
    }
    else reset_reason = UNKNOWN; // can't determine reset reason
#elif USING_RP2350
    // RP2350 CHIP_RESET register is stored in POWMAN block -
    // it can tell us POR, RUN pin, or debugger reset, plus glitch & brownout
    powman_hw_t *powman_reg = powman_hw;
    // first check if it was a watchdog reboot before interrogating the reset register
    if (watchdog_caused_reboot()) {
        if (watchdog_enable_caused_reboot()) {
            reset_reason = WATCHDOG; // expiration of watchdog timer
        }
        else reset_reason = FORCED;  // program-forced watchdog reboot
    }
    else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_DP_RESET_REQ_BITS) {
        reset_reason = DEBUGGER;  // reset from ARM debugger
    }
    else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_POR_BITS) {
        reset_reason = POWERON;   // normal power-on
    }
    else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_GLITCH_DETECT_BITS) {
        reset_reason = GLITCH;    // reset from power glitch detect
    }
    else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_BOR_BITS) {
        reset_reason = BROWNOUT;  // reset from brown-out detect
    }
    else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_RUN_LOW_BITS) {
        reset_reason = PIN;       // reset pin ("run" pin) toggled
    }
    else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_DOUBLE_TAP_BITS) {
        reset_reason = DOUBLETAP; // reset pin ("run" pin) double-tapped
    }
    else reset_reason = UNKNOWN;  // can't determine reset reason
#else
    reset_reason = UNKNOWN;
#endif
    return reset_reason;
    }

char* get_reset_reason_string(reset_reason_t reset_reason) {
    static char reset_reason_string[50];
    
    strcpy(reset_reason_string, "Last reset reason: ");

    switch (reset_reason)
    {
    case POWERON:
        strcpy(reset_reason_string + strlen(reset_reason_string), "power-on\r\n");
        break;
    case GLITCH:
        strcpy(reset_reason_string + strlen(reset_reason_string), "power supply glitch\r\n");
        break;
    case BROWNOUT:
        strcpy(reset_reason_string + strlen(reset_reason_string), "power supply brown-out\r\n");
        break;
    case WATCHDOG:
        strcpy(reset_reason_string + strlen(reset_reason_string), "watchdog\r\n");
        break;
    case FORCED:
        strcpy(reset_reason_string + strlen(reset_reason_string), "program-requested\r\n");
        break;
    case PIN:
        strcpy(reset_reason_string + strlen(reset_reason_string), "reset pin asserted\r\n");
        break;
    case DOUBLETAP:
        strcpy(reset_reason_string + strlen(reset_reason_string), "reset pin double-tap\r\n");
        break;
    case DEBUGGER:
        strcpy(reset_reason_string + strlen(reset_reason_string), "debugger\r\n");
        break;
    case UNKNOWN:
        strcpy(reset_reason_string + strlen(reset_reason_string), "unknown\r\n");
        break;
    default:
        break;
    }

    return reset_reason_string;
}

void reset_to_bootloader(void) {
    // reset to the USB bootloader (i.e. BOOTSEL for UF2 mode)
    reset_usb_boot(0, 0);
}

================================================
FILE: hardware/rp2xxx/hw_spi.c
================================================
/******************************************************************************
 * @file hw_spi.c
 *
 * @brief Functions for configuring and interacting with the SPI peripherals.
 *        The implementation of these functions is MCU-specific and will need
 *        to be changed if ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/spi.h"
#include "semphr.h"

// global SPI0 mutex
SemaphoreHandle_t spi0_mutex;


// chip select assertion routine to use in spi reads/writes
static inline void cs_assert(uint8_t cs_pin) {
    asm volatile("nop \n nop \n nop");
    gpio_put(cs_pin, 0);
    asm volatile("nop \n nop \n nop");
}

// chip select de-assertion routine to use in spi reads/writes
static inline void cs_deassert(uint8_t cs_pin) {
    asm volatile("nop \n nop \n nop");
    gpio_put(cs_pin, 1);
    asm volatile("nop \n nop \n nop");
}

void spi0_init(void) {
    // create SPI0 mutex
    spi0_mutex = xSemaphoreCreateMutex();

    // initialize SPI0 and set pins
    spi_init(SPI0_ID, SPI0_FREQ_KHZ * 1000);
    gpio_set_function(SPI0_MISO_PIN, GPIO_FUNC_SPI);
    gpio_set_function(SPI0_MOSI_PIN, GPIO_FUNC_SPI);
    gpio_set_function(SPI0_CLK_PIN, GPIO_FUNC_SPI);

    // initialize chip select and drive high (it is active low)
    // for multiple devices on the bus, more chip selects would need to be added
    gpio_init(SPI0_CS_PIN_DEFAULT);
    gpio_set_dir(SPI0_CS_PIN_DEFAULT, GPIO_OUT);
    gpio_put(SPI0_CS_PIN_DEFAULT, 1);
}

int spi0_write_register(uint8_t cs_pin, uint8_t reg_addr, uint8_t data_byte) {
    int bytes_written;
    uint8_t write_buf[2];
    write_buf[0] = reg_addr & 0x7F; // write bit mask (MSB signifies read)
    write_buf[1] = data_byte;
    // obtain mutex on hw and then write to spi
    if(xSemaphoreTake(spi0_mutex, 10) == pdTRUE) {
        cs_assert(cs_pin);
        bytes_written = spi_write_blocking(SPI0_ID, write_buf, 2);
        cs_deassert(cs_pin);
        xSemaphoreGive(spi0_mutex);
    }
    if (bytes_written == 2) {
        return 1;
    }
    else return 0;
}

int spi0_read_registers(uint8_t cs_pin, uint8_t reg_addr, uint8_t *read_buf, uint16_t len) {
    int bytes_read;
    reg_addr |= 0x80; // make sure read bit is set (MSB)
    // obtain mutex on hw and then read from spi
    if(xSemaphoreTake(spi0_mutex, 10) == pdTRUE) {
        cs_assert(cs_pin);
        spi_write_blocking(SPI0_ID, &reg_addr, 1); // write the address to read
        wait_here_us(10 * 1000);                   // wait 10ms before read
        bytes_read = spi_read_blocking(SPI0_ID, 0, read_buf, len); // read the result
        cs_deassert(cs_pin);
        xSemaphoreGive(spi0_mutex);
    }
    return bytes_read;
}

================================================
FILE: hardware/rp2xxx/hw_uart.c
================================================
/******************************************************************************
 * @file hw_uart.c
 *
 * @brief Functions for configuring and accessing the UART peripheral used for
 *        the Command Line Interface (CLI), or auxiliary UART for other uses.
 *        The implementation of these functions is MCU-specific and will need
 *        to be changed if ported to a new hardware family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "hardware_config.h"
#include "shell.h"
#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/irq.h"
#include "semphr.h"

#define NOCHAR 255 // return value of uart read if there is nothing to read


/************************
 * CLI UART functions
*************************/

static char cli_uart_rx_char = NOCHAR;

// global CLI UART mutex
SemaphoreHandle_t cli_uart_mutex;

// cli uart rx interrupt handler
static void on_cli_uart_rx() {
    if (uart_is_readable(UART_ID_CLI)) {
        cli_uart_rx_char = uart_getc(UART_ID_CLI);
    }
}

void cli_uart_init(void) {
    // The CLI UART is intended to be accessed character by character at low-ish
    // speed (human typing), with reads/writes regularly serviced by the CLI
    // task. As such it is configured without FIFO, and a simple rx interrupt
    // routine that puts the rx char into a global variable. It is assumed that
    // microshell will pull the byte before a new incoming byte overwrites it.

    // create CLI UART mutex
    cli_uart_mutex = xSemaphoreCreateMutex();
    
    // initialize uart at defined speed
    uart_init(UART_ID_CLI, UART_BAUD_RATE_CLI);

    // set tx and rx pins to the appropriate gpio function
    gpio_set_function(UART_TX_PIN_CLI, GPIO_FUNC_UART);
    gpio_set_function(UART_RX_PIN_CLI, GPIO_FUNC_UART);

    // disable flow control
    uart_set_hw_flow(UART_ID_CLI, false, false);

    // set data format
    uart_set_format(UART_ID_CLI, UART_DATA_BITS_CLI, UART_STOP_BITS_CLI, UART_PARITY_CLI);

    // disable fifos - handle single characters only
    uart_set_fifo_enabled(UART_ID_CLI, false);

    // set up RX interrupt
    int UART_IRQ = UART_ID_CLI == uart0 ? UART0_IRQ : UART1_IRQ;
    irq_set_exclusive_handler(UART_IRQ, on_cli_uart_rx);
    irq_set_enabled(UART_IRQ, true);
    uart_set_irq_enables(UART_ID_CLI, true, false);

    cli_uart_rx_char = NOCHAR; // clear out RX buffer as a junk char appears upon enable

    // print out a string to indicate that uart was successfully initialized
    uart_puts(UART_ID_CLI, "\r\n\n");
    uart_puts(UART_ID_CLI, timestamp());
    uart_puts(UART_ID_CLI, "CLI UART initialized\r\n");
}

int cli_uart_putc(char tx_char) {
    int status = 0;
    if (uart_is_writable(UART_ID_CLI)) {
        if(xSemaphoreTake(cli_uart_mutex, 10) == pdTRUE) {
            // if uart is writeable and semaphore is obtained, write the char
            uart_putc_raw(UART_ID_CLI, tx_char);
            xSemaphoreGive(cli_uart_mutex);
            status = 1;
        }
    }
    return status;
}

char cli_uart_getc(void) {
    char ch;
    if (cli_uart_rx_char != NOCHAR) {
        // try to obtain the mutex to allow reading the buffered single char.
        // note that the direct UART read is inherently thread safe because it is
        // accessed thru a static ISR (on_cli_uart_rx).
        // of course this does not prevent another incoming UART char from overwriting
        // the buffered char if it is not pulled out in time.
        if(xSemaphoreTake(cli_uart_mutex, 10) == pdTRUE) {
            ch = cli_uart_rx_char;
            cli_uart_rx_char = NOCHAR;
            xSemaphoreGive(cli_uart_mutex);
        }
        return ch;
    }
}

void cli_uart_puts(const char *print_string) {
    if(xSemaphoreTake(cli_uart_mutex, 10) == pdTRUE) {
        uart_puts(UART_ID_CLI, print_string);
        xSemaphoreGive(cli_uart_mutex);
    }
}


/************************
 * AUX UART functions
*************************/

// global Aux UART mutex
SemaphoreHandle_t aux_uart_mutex;

void aux_uart_init(void) {
    // The auxilliary uart is a multi-purpose serial interface intended to send
    // and receive multiple bytes at a time, without any specific servicing
    // routine implemented. The FIFO is enabled but there is no RX ISR; on
    // the RP2040 platform this means we have 32 bytes of hw buffer before data
    // starts getting overwritten by any more incoming bytes.
    // Refer to cli_uart_init() if the need to implement an RX ISR arises.

    // create Aux UART mutex
    aux_uart_mutex = xSemaphoreCreateMutex();

    // initialize uart at defined speed
    uart_init(UART_ID_AUX, UART_BAUD_RATE_AUX);

    // set tx and rx pins to the appropriate gpio function
    gpio_set_function(UART_TX_PIN_AUX, GPIO_FUNC_UART);
    gpio_set_function(UART_RX_PIN_AUX, GPIO_FUNC_UART);

    // disable flow control
    uart_set_hw_flow(UART_ID_AUX, false, false);

    // set data format
    uart_set_format(UART_ID_AUX, UART_DATA_BITS_AUX, UART_STOP_BITS_AUX, UART_PARITY_AUX);

    // enable fifos
    uart_set_fifo_enabled(UART_ID_AUX, true);
}

int aux_uart_write(uint8_t *tx_data, size_t tx_len) {
    int status = 0;
    if (uart_is_writable(UART_ID_AUX)) {
        if(xSemaphoreTake(aux_uart_mutex, 10) == pdTRUE) {
            // if uart is writeable and semaphore is obtained, write the data
            uart_write_blocking(UART_ID_AUX, tx_data, tx_len);
            xSemaphoreGive(aux_uart_mutex);
            status = 1;
        }
    }
    return status;
}

int aux_uart_read(uint8_t *rx_data, size_t rx_len) {
    int rx_pos = 0;

    // read from uart if there is data in the fifo, up to rx_len times
    while(uart_is_readable(UART_ID_AUX) && rx_pos < rx_len) {
        // first try to obtain mutex
        if(xSemaphoreTake(aux_uart_mutex, 10) == pdTRUE) {
            rx_data[rx_pos] = (uint8_t) uart_getc(UART_ID_AUX);
            xSemaphoreGive(aux_uart_mutex);
            rx_pos++;
        }
    }

    return rx_pos + 1;
}

================================================
FILE: hardware/rp2xxx/hw_usb.c
================================================
/******************************************************************************
 * @file hw_usb.c
 *
 * @brief Functions for configuring and implementing a USB device using the
 *        TinyUSB library. The implementation of these functions may be
 *        MCU-specific and will need to be updated if ported to a new hardware
 *        family.
 *
 * @author Cavin McKinley (MCKNLY LLC)
 *
 * @date 02-14-2024
 * 
 * @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
 *            Released under the MIT License
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/flash.h"
#include "tusb.h"
#include "semphr.h"

// global USB mutex
SemaphoreHandle_t usb_mutex;


/************************
 * USB Descriptor Setup
*************************/

#define DESC_STR_MAX 20

#define USBD_VID 0x2E8A // Vendor: Raspberry Pi
#define USBD_PID 0x000A // Product: Raspberry Pi Pico CDC

#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN * CFG_TUD_CDC)
#define USBD_MAX_POWER_MA 500

#define USBD_ITF_CDC_0 0
#define USBD_ITF_MAX 2

#define USBD_CDC_0_EP_CMD 0x81
#define USBD_CDC_0_EP_OUT 0x01
#define USBD_CDC_0_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_SERIAL_LEN 17
#define USBD_STR_CDC 0x04

static const tusb_desc_device_t usbd_desc_device = {
	.bLength = sizeof(tusb_desc_device_t),
	.bDescriptorType = TUSB_DESC_DEVICE,
	.bcdUSB = 0x0200,
	.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,
};

// endpoint descriptors, can add additional TUD_CDC_DESCRIPTOR to array for more
// endpoints in the composite device
static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = {
	TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_0, USBD_DESC_LEN,
		TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA),

	TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_0, USBD_STR_CDC, USBD_CDC_0_EP_CMD,
		USBD_CDC_CMD_MAX_SIZE, USBD_CDC_0_EP_OUT, USBD_CDC_0_EP_IN,
		USBD_CDC_IN_OUT_MAX_SIZE),
};

static char usb_serialno[USBD_STR_SERIAL_LEN] = "000000000000";

static const char *const usbd_desc_str[] = {
	[USBD_STR_MANUF] = "Raspberry Pi",
	[USBD_STR_PRODUCT] = "Pico",
	[USBD_STR_SERIAL] = usb_serialno,
	[USBD_STR_CDC] = "Board CDC",
};


/*******************************
 * TinyUSB Descriptor Callbacks
********************************/

// TinyUSB callback to provide the device profile
const uint8_t *tud_descriptor_device_cb(void) {
	return (const uint8_t *) &usbd_desc_device;
}

// TinyUSB callback to provide configuration, interface, and endpoint profiles
const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
	return usbd_desc_cfg;
}

// TinyUSB callback to provide the device strings in UTF8
const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
	static uint16_t desc_str[DESC_STR_MAX];
	uint8_t len;

	if (index == 0) {
		desc_str[1] = 0x0409;
		len = 1;
	} else {
		const char *str;
		char serial[USBD_STR_SERIAL_LEN];

		if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0]))
			return NULL;

		str = usbd_desc_str[index];
		for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len)
			desc_str[1 + len] = str[len];
	}

	desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * len + 2);

	return desc_str;
}

void usb_serialno_init(void) {
	uint8_t id[8];

    // Get unique board ID from the Pico flash (programmed during manufacturing)
	flash_get_unique_id(id);

	snprintf(usb_serialno,
	         USBD_STR_SERIAL_LEN,
			 "%02X%02X%02X%02X%02X%02X%02X%02X",
		     id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7]);
}

void usb_device_init(void) {
	// create USB mutex
    usb_mutex = xSemaphoreCreateMutex();

	// generate unique USB device serial no. from RPi Pico unique flash ID
	usb_serialno_init();
	
	// initialize the Ti
Download .txt
gitextract_w576021d/

├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── README.md
├── cli/
│   ├── CMakeLists.txt
│   ├── cli_utils.c
│   ├── cli_utils.h
│   ├── motd.h
│   ├── node_bin.c
│   ├── node_dev.c
│   ├── node_etc.c
│   ├── node_lib.c
│   ├── node_mnt.c
│   ├── node_net.c
│   ├── node_proc.c
│   ├── node_root.c
│   ├── shell.c
│   ├── shell.h
│   └── shell_cmd.c
├── driver_lib/
│   ├── CMakeLists.txt
│   ├── bme280.c
│   ├── device_drivers.c
│   ├── device_drivers.h
│   └── mcp4725.c
├── hardware/
│   └── rp2xxx/
│       ├── CMakeLists.txt
│       ├── hardware_config.c
│       ├── hardware_config.h
│       ├── hw_adc.c
│       ├── hw_clocks.c
│       ├── hw_cores.c
│       ├── hw_gpio.c
│       ├── hw_i2c.c
│       ├── hw_net.c
│       ├── hw_registers.c
│       ├── hw_reset.c
│       ├── hw_spi.c
│       ├── hw_uart.c
│       ├── hw_usb.c
│       ├── hw_versions.c
│       ├── hw_watchdog.c
│       ├── hw_wifi.c
│       ├── net_inc/
│       │   ├── httpd_content/
│       │   │   ├── 404.html
│       │   │   ├── index.shtml
│       │   │   └── test.shtml
│       │   ├── hw_net.h
│       │   ├── hw_wifi.h
│       │   └── lwipopts.h
│       ├── onboard_flash.c
│       ├── onboard_led.c
│       ├── pico_sdk_import.cmake
│       ├── prebuild.cmake
│       └── rtos_config.h
├── littlefs/
│   └── CMakeLists.txt
├── main.c
├── project.cmake
├── rtos/
│   ├── CMakeLists.txt
│   ├── FreeRTOSConfig.h
│   ├── rtos_utils.c
│   └── rtos_utils.h
├── services/
│   ├── CMakeLists.txt
│   ├── cli_service.c
│   ├── heartbeat_service.c
│   ├── netman_service.c
│   ├── service_queues.c
│   ├── service_queues.h
│   ├── services.c
│   ├── services.h
│   ├── storman_service.c
│   ├── taskman_service.c
│   ├── usb_service.c
│   └── watchdog_service.c
└── version.h
Download .txt
SYMBOL INDEX (223 symbols across 47 files)

FILE: cli/cli_utils.c
  function hex_string_to_byte_array (line 23) | size_t hex_string_to_byte_array(char *hex_string, uint8_t *byte_array) {
  function print_motd (line 53) | void print_motd(void) {

FILE: cli/node_bin.c
  function ps_exec_callback (line 37) | static void ps_exec_callback(struct ush_object *self, struct ush_file_de...
  function top_exec_callback (line 66) | static void top_exec_callback(struct ush_object *self, struct ush_file_d...
  function free_exec_callback (line 94) | static void free_exec_callback(struct ush_object *self, struct ush_file_...
  function df_exec_callback (line 143) | static void df_exec_callback(struct ush_object *self, struct ush_file_de...
  function kill_exec_callback (line 182) | static void kill_exec_callback(struct ush_object *self, struct ush_file_...
  function service_exec_callback (line 215) | static void service_exec_callback(struct ush_object *self, struct ush_fi...
  function reboot_exec_callback (line 341) | static void reboot_exec_callback(struct ush_object *self, struct ush_fil...
  type ush_file_descriptor (line 369) | struct ush_file_descriptor
  type ush_node_object (line 430) | struct ush_node_object
  function shell_bin_mount (line 432) | void shell_bin_mount(void)

FILE: cli/node_dev.c
  function led_get_data_callback (line 41) | size_t led_get_data_callback(struct ush_object *self, struct ush_file_de...
  function led_set_data_callback (line 67) | void led_set_data_callback(struct ush_object *self, struct ush_file_desc...
  function time_get_data_callback (line 87) | size_t time_get_data_callback(struct ush_object *self, struct ush_file_d...
  function gpio_exec_callback (line 111) | static void gpio_exec_callback(struct ush_object *self, struct ush_file_...
  function gpio_get_data_callback (line 168) | size_t gpio_get_data_callback(struct ush_object *self, struct ush_file_d...
  function i2c0_exec_callback (line 214) | static void i2c0_exec_callback(struct ush_object *self, struct ush_file_...
  function i2c0_get_data_callback (line 304) | size_t i2c0_get_data_callback(struct ush_object *self, struct ush_file_d...
  function spi0_exec_callback (line 353) | static void spi0_exec_callback(struct ush_object *self, struct ush_file_...
  function spi0_get_data_callback (line 427) | size_t spi0_get_data_callback(struct ush_object *self, struct ush_file_d...
  function adc0_get_data_callback (line 462) | size_t adc0_get_data_callback(struct ush_object *self, struct ush_file_d...
  function usb0_get_data_callback (line 485) | size_t usb0_get_data_callback(struct ush_object *self, struct ush_file_d...
  function usb0_set_data_callback (line 511) | void usb0_set_data_callback(struct ush_object *self, struct ush_file_des...
  function uart1_get_data_callback (line 526) | size_t uart1_get_data_callback(struct ush_object *self, struct ush_file_...
  function uart1_set_data_callback (line 552) | void uart1_set_data_callback(struct ush_object *self, struct ush_file_de...
  type ush_file_descriptor (line 558) | struct ush_file_descriptor
  type ush_node_object (line 649) | struct ush_node_object
  function shell_dev_mount (line 651) | void shell_dev_mount(void)

FILE: cli/node_etc.c
  function motd_get_data_callback (line 33) | size_t motd_get_data_callback(struct ush_object *self, struct ush_file_d...
  type ush_file_descriptor (line 47) | struct ush_file_descriptor
  type ush_node_object (line 59) | struct ush_node_object
  function shell_etc_mount (line 61) | void shell_etc_mount(void)

FILE: cli/node_lib.c
  function bme280_get_data_callback (line 35) | size_t bme280_get_data_callback(struct ush_object *self, struct ush_file...
  function mcp4725_get_data_callback (line 69) | size_t mcp4725_get_data_callback(struct ush_object *self, struct ush_fil...
  function mcp4725_set_data_callback (line 97) | void mcp4725_set_data_callback(struct ush_object *self, struct ush_file_...
  type ush_file_descriptor (line 106) | struct ush_file_descriptor
  type ush_node_object (line 131) | struct ush_node_object
  function shell_lib_mount (line 133) | void shell_lib_mount(void)

FILE: cli/node_mnt.c
  function flash0_exec_callback (line 39) | static void flash0_exec_callback(struct ush_object *self, struct ush_fil...
  type ush_file_descriptor (line 150) | struct ush_file_descriptor
  type ush_node_object (line 168) | struct ush_node_object
  function shell_mnt_mount (line 170) | void shell_mnt_mount(void)
  function shell_mnt_unmount (line 176) | void shell_mnt_unmount(void)

FILE: cli/node_net.c
  function wifi_exec_callback (line 33) | static void wifi_exec_callback(struct ush_object *self, struct ush_file_...
  type ush_file_descriptor (line 70) | struct ush_file_descriptor
  type ush_node_object (line 86) | struct ush_node_object
  function shell_net_mount (line 88) | void shell_net_mount(void)

FILE: cli/node_proc.c
  function mcuinfo_get_data_callback (line 37) | size_t mcuinfo_get_data_callback(struct ush_object *self, struct ush_fil...
  function version_get_data_callback (line 70) | size_t version_get_data_callback(struct ush_object *self, struct ush_fil...
  function resetreason_get_data_callback (line 108) | size_t resetreason_get_data_callback(struct ush_object *self, struct ush...
  function uptime_get_data_callback (line 127) | size_t uptime_get_data_callback(struct ush_object *self, struct ush_file...
  type ush_file_descriptor (line 151) | struct ush_file_descriptor
  type ush_node_object (line 187) | struct ush_node_object
  function shell_proc_mount (line 189) | void shell_proc_mount(void)

FILE: cli/node_root.c
  function info_get_data_callback (line 29) | size_t info_get_data_callback(struct ush_object *self, struct ush_file_d...
  type ush_file_descriptor (line 40) | struct ush_file_descriptor
  type ush_node_object (line 52) | struct ush_node_object
  function shell_root_mount (line 54) | void shell_root_mount(void)

FILE: cli/shell.c
  function ush_read (line 30) | static int ush_read(struct ush_object *self, char *ch)
  function ush_write (line 47) | static int ush_write(struct ush_object *self, char ch)
  type ush_io_interface (line 58) | struct ush_io_interface
  type ush_object (line 68) | struct ush_object
  type ush_prompt_format (line 71) | struct ush_prompt_format
  type ush_descriptor (line 85) | struct ush_descriptor
  function shell_init (line 107) | void shell_init(void)
  function shell_service (line 128) | void shell_service(void)
  function shell_print (line 133) | void shell_print(char *buf)
  function shell_print_slow (line 141) | void shell_print_slow(const char *buf) {
  function shell_is_printing (line 168) | bool shell_is_printing(void)

FILE: cli/shell.h
  type ush_object (line 69) | struct ush_object
  type ush_object (line 154) | struct ush_object
  type ush_object (line 166) | struct ush_object

FILE: cli/shell_cmd.c
  function clear_exec_callback (line 29) | static void clear_exec_callback(struct ush_object *self, struct ush_file...
  type ush_file_descriptor (line 37) | struct ush_file_descriptor
  type ush_node_object (line 47) | struct ush_node_object
  function shell_commands_add (line 49) | void shell_commands_add(void)

FILE: driver_lib/bme280.c
  function bme280_compensate_temperature (line 26) | static int32_t bme280_compensate_temperature(bme280_compensation_params_...
  function bme280_compensate_pressure (line 41) | static uint32_t bme280_compensate_pressure(bme280_compensation_params_t ...
  function bme280_compensate_humidity (line 67) | static uint32_t bme280_compensate_humidity(bme280_compensation_params_t ...
  function bme280_read_compensation_parameters (line 83) | int bme280_read_compensation_parameters(bme280_compensation_params_t *co...
  function bme280_init (line 125) | int bme280_init(void) {
  function bme280_read_sensors (line 142) | int bme280_read_sensors(bme280_compensation_params_t *compensation_param...

FILE: driver_lib/device_drivers.c
  function driver_init (line 23) | void driver_init(void) {

FILE: driver_lib/device_drivers.h
  type bme280_compensation_params_t (line 52) | typedef struct bme280_compensation_params_t {
  type bme280_sensor_data_t (line 70) | typedef struct bme280_sensor_data_t {

FILE: driver_lib/mcp4725.c
  function mcp4725_init (line 22) | int mcp4725_init(void) {
  function mcp4725_set_voltage (line 35) | int mcp4725_set_voltage(float voltage, bool save_in_eeprom) {
  function mcp4725_get_voltage (line 60) | float mcp4725_get_voltage(void) {

FILE: hardware/rp2xxx/hardware_config.c
  function hardware_init (line 40) | void hardware_init(void) {

FILE: hardware/rp2xxx/hardware_config.h
  type gpio_settings_t (line 166) | typedef struct gpio_settings_t {
  type gpio_settings_t (line 174) | struct gpio_settings_t
  type gpio_event_t (line 177) | typedef struct gpio_event_t {
  type gpio_event_t (line 184) | struct gpio_event_t
  type reset_reason_t (line 633) | typedef enum {POWERON,   // normal power-on reset
  type flash_usage_t (line 806) | typedef struct flash_usage_t {
  type lfs_config (line 845) | struct lfs_config
  type lfs_config (line 863) | struct lfs_config
  type lfs_config (line 877) | struct lfs_config
  type lfs_config (line 889) | struct lfs_config
  type usb_iface_t (line 981) | typedef struct usb_iface_t {
  type usb_iface_t (line 1029) | struct usb_iface_t
  type usb_iface_t (line 1042) | struct usb_iface_t

FILE: hardware/rp2xxx/hw_adc.c
  function adcs_init (line 28) | void adcs_init(void) {
  function read_adc (line 41) | float read_adc(int adc_channel) {

FILE: hardware/rp2xxx/hw_clocks.c
  function get_sys_clk_hz (line 23) | uint32_t get_sys_clk_hz(void){
  function get_time_us (line 27) | uint64_t get_time_us(void){
  function wait_here_us (line 31) | void wait_here_us(uint64_t delay_us) {

FILE: hardware/rp2xxx/hw_cores.c
  function get_core (line 23) | uint8_t get_core(void){

FILE: hardware/rp2xxx/hw_gpio.c
  type gpio_settings_t (line 28) | struct gpio_settings_t
  type gpio_event_t (line 36) | struct gpio_event_t
  function gpio_process (line 41) | void gpio_process(uint gpio, uint32_t event_mask) {
  function gpio_init_all (line 48) | void gpio_init_all(void) {
  function gpio_read_single (line 81) | bool gpio_read_single(uint gpio_id) {
  function gpio_write_single (line 90) | void gpio_write_single(uint gpio_id, bool value) {
  function gpio_read_all (line 100) | uint32_t gpio_read_all(void) {
  function gpio_write_all (line 115) | void gpio_write_all(uint32_t gpio_states) {

FILE: hardware/rp2xxx/hw_i2c.c
  function i2c0_init (line 28) | void i2c0_init(void) {
  function i2c0_write (line 39) | int i2c0_write(uint8_t addr, const uint8_t *write_data, size_t length) {
  function i2c0_read (line 57) | int i2c0_read(uint8_t addr, uint8_t *read_data, size_t length) {

FILE: hardware/rp2xxx/hw_net.c
  function srv_txt (line 35) | static void srv_txt(struct mdns_service *service, void *txt_userdata) {
  function net_mdns_init (line 43) | void net_mdns_init(void) {
  function u16_t (line 95) | u16_t httpd_ssi_handler(int iIndex, char *pcInsert, int iInsertLen, uint...
  function net_httpd_stack_init (line 151) | void net_httpd_stack_init(void) {

FILE: hardware/rp2xxx/hw_registers.c
  function read_chip_register (line 22) | uint32_t read_chip_register(uint32_t reg_addr) {

FILE: hardware/rp2xxx/hw_reset.c
  function reset_reason_t (line 32) | reset_reason_t get_reset_reason(void) {
  function reset_to_bootloader (line 133) | void reset_to_bootloader(void) {

FILE: hardware/rp2xxx/hw_spi.c
  function cs_assert (line 29) | static inline void cs_assert(uint8_t cs_pin) {
  function cs_deassert (line 36) | static inline void cs_deassert(uint8_t cs_pin) {
  function spi0_init (line 42) | void spi0_init(void) {
  function spi0_write_register (line 59) | int spi0_write_register(uint8_t cs_pin, uint8_t reg_addr, uint8_t data_b...
  function spi0_read_registers (line 77) | int spi0_read_registers(uint8_t cs_pin, uint8_t reg_addr, uint8_t *read_...

FILE: hardware/rp2xxx/hw_uart.c
  function on_cli_uart_rx (line 39) | static void on_cli_uart_rx() {
  function cli_uart_init (line 45) | void cli_uart_init(void) {
  function cli_uart_putc (line 85) | int cli_uart_putc(char tx_char) {
  function cli_uart_getc (line 98) | char cli_uart_getc(void) {
  function cli_uart_puts (line 115) | void cli_uart_puts(const char *print_string) {
  function aux_uart_init (line 130) | void aux_uart_init(void) {
  function aux_uart_write (line 158) | int aux_uart_write(uint8_t *tx_data, size_t tx_len) {
  function aux_uart_read (line 171) | int aux_uart_read(uint8_t *rx_data, size_t rx_len) {

FILE: hardware/rp2xxx/hw_usb.c
  function usb_serialno_init (line 135) | void usb_serialno_init(void) {
  function usb_device_init (line 147) | void usb_device_init(void) {
  function usb_read_bytes (line 158) | void usb_read_bytes(struct usb_iface_t *usb_iface) {
  function usb_write_bytes (line 173) | void usb_write_bytes(struct usb_iface_t *usb_iface) {
  function cli_usb_putc (line 193) | int cli_usb_putc(char tx_char) {
  function cli_usb_getc (line 211) | char cli_usb_getc(void) {

FILE: hardware/rp2xxx/hw_versions.c
  function get_chip_version (line 22) | uint8_t get_chip_version(void){
  function get_rom_version (line 30) | uint8_t get_rom_version(void){

FILE: hardware/rp2xxx/hw_watchdog.c
  function watchdog_en (line 24) | void watchdog_en(uint32_t delay_ms) {
  function watchdog_dis (line 28) | void watchdog_dis(void) {
  function watchdog_kick (line 32) | void watchdog_kick(void) {
  function force_watchdog_reboot (line 36) | void force_watchdog_reboot(void) {

FILE: hardware/rp2xxx/hw_wifi.c
  function hw_wifi_hard_reset (line 27) | void hw_wifi_hard_reset(void) {
  function hw_wifi_is_initialized (line 39) | bool hw_wifi_is_initialized() {
  function hw_wifi_init (line 43) | bool hw_wifi_init() {
  function hw_wifi_init_with_country (line 56) | bool hw_wifi_init_with_country(hw_wifi_country_t country_code) {
  function hw_wifi_deinit (line 69) | void hw_wifi_deinit() {
  function hw_wifi_auth_to_cyw43 (line 74) | static uint32_t hw_wifi_auth_to_cyw43(hw_wifi_auth_t auth) {
  function hw_wifi_enable_sta_mode (line 87) | void hw_wifi_enable_sta_mode() {
  function hw_wifi_disable_sta_mode (line 93) | void hw_wifi_disable_sta_mode() {
  function hw_wifi_enable_ap_mode (line 100) | void hw_wifi_enable_ap_mode(const char *ssid, const char *password, hw_w...
  function hw_wifi_disable_ap_mode (line 106) | void hw_wifi_disable_ap_mode() {
  function hw_wifi_connect (line 113) | bool hw_wifi_connect(const char *ssid, const char *password, hw_wifi_aut...
  function hw_wifi_connect_async (line 119) | bool hw_wifi_connect_async(const char *ssid, const char *password, hw_wi...
  function hw_wifi_reset_connection (line 125) | void hw_wifi_reset_connection(void) {
  function ip_addr_t (line 133) | const ip_addr_t *hw_wifi_get_addr() {
  function hw_wifi_status_t (line 138) | hw_wifi_status_t hw_wifi_get_status() {

FILE: hardware/rp2xxx/net_inc/hw_wifi.h
  type hw_wifi_country_t (line 27) | typedef uint32_t hw_wifi_country_t ;
  type hw_wifi_auth_t (line 88) | typedef enum{
  type hw_wifi_mode_t (line 96) | typedef enum{
  type hw_wifi_status_t (line 103) | typedef enum{
  type hw_wifi_ip_addr_t (line 115) | typedef uint32_t hw_wifi_ip_addr_t;

FILE: hardware/rp2xxx/onboard_flash.c
  function onboard_flash_init (line 34) | void onboard_flash_init(void) {
  function onboard_flash_read (line 39) | int onboard_flash_read(const struct lfs_config *c, uint32_t block, uint3...
  function onboard_flash_write (line 49) | int onboard_flash_write(const struct lfs_config *c, uint32_t block, uint...
  function onboard_flash_erase (line 61) | int onboard_flash_erase(const struct lfs_config *c, uint32_t block) {
  function onboard_flash_sync (line 73) | int onboard_flash_sync(const struct lfs_config *c) {
  function flash_usage_t (line 77) | flash_usage_t onboard_flash_usage(void) {

FILE: hardware/rp2xxx/onboard_led.c
  function onboard_led_init (line 25) | void onboard_led_init(void) {
  function onboard_led_set (line 29) | void onboard_led_set(bool led_state) {
  function onboard_led_get (line 33) | bool onboard_led_get(void) {
  function onboard_led_init (line 37) | void onboard_led_init(void) {
  function onboard_led_set (line 42) | void onboard_led_set(bool led_state) {
  function onboard_led_get (line 46) | bool onboard_led_get(void) {

FILE: main.c
  function main (line 26) | void main()

FILE: rtos/rtos_utils.c
  function task_sched_update (line 24) | void task_sched_update(uint32_t repeat, const TickType_t delay) {
  function task_delay_ms (line 38) | void task_delay_ms(uint32_t delay_ms) {

FILE: services/cli_service.c
  function BaseType_t (line 40) | BaseType_t cli_service(void)
  function prvCliTask (line 70) | static void prvCliTask(void *pvParameters)

FILE: services/heartbeat_service.c
  function BaseType_t (line 37) | BaseType_t heartbeat_service(void)
  function prvHeartbeatTask (line 61) | static void prvHeartbeatTask(void *pvParameters)

FILE: services/netman_service.c
  type netman_info_t (line 32) | struct netman_info_t
  function BaseType_t (line 37) | BaseType_t netman_service(void)
  function prvNetworkManagerTask (line 62) | static void prvNetworkManagerTask(void *pvParameters) {

FILE: services/service_queues.c
  function init_queues (line 33) | bool init_queues(void) {
  function cli_print_raw (line 62) | bool cli_print_raw(char *string) {
  function cli_print_timestamped (line 69) | bool cli_print_timestamped(char *string) {
  function taskman_request (line 82) | bool taskman_request(struct taskman_item_t *tmi) {
  function storman_request (line 89) | bool storman_request(struct storman_item_t *smi) {
  function netman_request (line 97) | bool netman_request(netman_action_t nma) {
  function usb_data_get (line 105) | bool usb_data_get(uint8_t *usb_rx_data) {
  function usb_data_put (line 112) | bool usb_data_put(uint8_t *usb_tx_data) {

FILE: services/service_queues.h
  type tm_action_t (line 50) | typedef enum tm_action_t {DELETE, SUSPEND, RESUME} tm_action_t;
  type taskman_item_t (line 51) | typedef struct taskman_item_t {TaskHandle_t task; tm_action_t action;} t...
  type storman_action_t (line 62) | typedef enum {LSDIR,      // list directory contents
  type storman_item_t (line 78) | typedef struct storman_item_t {storman_action_t action;
  type storman_item_t (line 87) | struct storman_item_t
  type netman_action_t (line 102) | typedef enum {NETJOIN, // join a network
  type netman_info_t (line 106) | typedef struct netman_info_t {hw_wifi_status_t status; // network connec...
  type netman_info_t (line 111) | struct netman_info_t
  type taskman_item_t (line 185) | struct taskman_item_t
  type storman_item_t (line 198) | struct storman_item_t

FILE: services/services.h
  type BaseType_t (line 239) | typedef BaseType_t (*service_func_t)(void);
  type service_desc_t (line 242) | typedef struct service_desc_t {

FILE: services/storman_service.c
  type storman_item_t (line 35) | struct storman_item_t
  function BaseType_t (line 40) | BaseType_t storman_service(void)
  function prvStorageManagerTask (line 68) | static void prvStorageManagerTask(void *pvParameters)

FILE: services/taskman_service.c
  function BaseType_t (line 31) | BaseType_t taskman_service(void)
  function prvTaskManagerTask (line 66) | static void prvTaskManagerTask(void *pvParameters)

FILE: services/usb_service.c
  function BaseType_t (line 31) | BaseType_t usb_service(void)
  function prvUsbTask (line 59) | static void prvUsbTask(void *pvParameters)

FILE: services/watchdog_service.c
  function BaseType_t (line 27) | BaseType_t watchdog_service(void)
  function prvWatchdogTask (line 55) | static void prvWatchdogTask(void *pvParameters)
Condensed preview — 73 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (344K chars).
[
  {
    "path": ".gitignore",
    "chars": 28,
    "preview": "build\n.vscode\n.trunk\n.idea/\n"
  },
  {
    "path": ".gitmodules",
    "chars": 338,
    "preview": "[submodule \"microshell\"]\n\tpath = microshell\n\turl = https://github.com/mcknly/microshell.git\n[submodule \"littlefs/littlef"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 2849,
    "preview": "cmake_minimum_required(VERSION 3.18)\n\nset(CMAKE_C_STANDARD 11)\nset(CMAKE_CXX_STANDARD 17)\n\n# Project/hardware-specific s"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "MIT License\n\nCopyright (c) 2024 Cavin McKinley (MCKNLY LLC)\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "README.md",
    "chars": 9821,
    "preview": "# [ B r e a d b o a r d O S ]\n```\n                             ___          ___\n    _____       _____       /  /\\       "
  },
  {
    "path": "cli/CMakeLists.txt",
    "chars": 1982,
    "preview": "# This file configures the build for the microshell submodule, which is included\n# as source for the CLI which will be b"
  },
  {
    "path": "cli/cli_utils.c",
    "chars": 2893,
    "preview": "/******************************************************************************\n * @file cli_utils.c\n *\n * @brief Utilit"
  },
  {
    "path": "cli/cli_utils.h",
    "chars": 1569,
    "preview": "/******************************************************************************\n * @file cli_utils.h\n *\n * @brief Utilit"
  },
  {
    "path": "cli/motd.h",
    "chars": 4743,
    "preview": "/******************************************************************************\n * @file motd.h\n *\n * @brief contains th"
  },
  {
    "path": "cli/node_bin.c",
    "chars": 16719,
    "preview": "/******************************************************************************\n * @file node_bin.c\n *\n * @brief /bin fo"
  },
  {
    "path": "cli/node_dev.c",
    "chars": 22135,
    "preview": "/******************************************************************************\n * @file node_dev.c\n *\n * @brief /dev fo"
  },
  {
    "path": "cli/node_etc.c",
    "chars": 2046,
    "preview": "/******************************************************************************\n * @file node_etc.c\n *\n * @brief /etc fo"
  },
  {
    "path": "cli/node_lib.c",
    "chars": 4459,
    "preview": "/******************************************************************************\n * @file node_lib.c\n *\n * @brief /lib fo"
  },
  {
    "path": "cli/node_mnt.c",
    "chars": 6794,
    "preview": "/******************************************************************************\n * @file node_mnt.c\n *\n * @brief /mnt fo"
  },
  {
    "path": "cli/node_net.c",
    "chars": 3157,
    "preview": "/******************************************************************************\n * @file node_net.c\n *\n * @brief /net fo"
  },
  {
    "path": "cli/node_proc.c",
    "chars": 8346,
    "preview": "/******************************************************************************\n * @file node_proc.c\n *\n * @brief /proc "
  },
  {
    "path": "cli/node_root.c",
    "chars": 1953,
    "preview": "/******************************************************************************\n * @file node_root.c\n *\n * @brief root f"
  },
  {
    "path": "cli/shell.c",
    "chars": 5674,
    "preview": "/******************************************************************************\n * @file shell.h\n *\n * @brief Microshell"
  },
  {
    "path": "cli/shell.h",
    "chars": 5008,
    "preview": "/******************************************************************************\n * @file shell.h\n *\n * @brief Microshell"
  },
  {
    "path": "cli/shell_cmd.c",
    "chars": 1459,
    "preview": "/******************************************************************************\n * @file shell_cmd.c\n *\n * @brief Define"
  },
  {
    "path": "driver_lib/CMakeLists.txt",
    "chars": 129,
    "preview": "# add source files to the top-level project\ntarget_sources(${PROJ_NAME} PRIVATE\n    device_drivers.c\n    bme280.c\n    mc"
  },
  {
    "path": "driver_lib/bme280.c",
    "chars": 8356,
    "preview": "/******************************************************************************\n * @file bme280.c\n *\n * @brief Hardware "
  },
  {
    "path": "driver_lib/device_drivers.c",
    "chars": 1653,
    "preview": "/******************************************************************************\n * @file device_drivers.c\n *\n * @brief M"
  },
  {
    "path": "driver_lib/device_drivers.h",
    "chars": 5026,
    "preview": "/******************************************************************************\n * @file device_drivers.h\n *\n * @brief D"
  },
  {
    "path": "driver_lib/mcp4725.c",
    "chars": 2450,
    "preview": "/******************************************************************************\n * @file mcp4725.c\n *\n * @brief Hardware"
  },
  {
    "path": "hardware/rp2xxx/CMakeLists.txt",
    "chars": 2572,
    "preview": "# add source files to the top-level project\ntarget_sources(${PROJ_NAME} PRIVATE\n    hardware_config.c\n    hw_gpio.c\n    "
  },
  {
    "path": "hardware/rp2xxx/hardware_config.c",
    "chars": 4534,
    "preview": "/******************************************************************************\n * @file hardware_config.c\n *\n * @brief "
  },
  {
    "path": "hardware/rp2xxx/hardware_config.h",
    "chars": 31935,
    "preview": "/******************************************************************************\n * @file hardware_config.h\n *\n * @brief "
  },
  {
    "path": "hardware/rp2xxx/hw_adc.c",
    "chars": 1429,
    "preview": "/******************************************************************************\n * @file hw_adc.c\n *\n * @brief Functions"
  },
  {
    "path": "hardware/rp2xxx/hw_clocks.c",
    "chars": 875,
    "preview": "/******************************************************************************\n * @file hw_clocks.c\n *\n * @brief Functi"
  },
  {
    "path": "hardware/rp2xxx/hw_cores.c",
    "chars": 726,
    "preview": "/******************************************************************************\n * @file hw_cores.c\n *\n * @brief Functio"
  },
  {
    "path": "hardware/rp2xxx/hw_gpio.c",
    "chars": 4486,
    "preview": "/******************************************************************************\n * @file hw_gpio.c\n *\n * @brief Function"
  },
  {
    "path": "hardware/rp2xxx/hw_i2c.c",
    "chars": 2108,
    "preview": "/******************************************************************************\n * @file hw_i2c.c\n *\n * @brief Functions"
  },
  {
    "path": "hardware/rp2xxx/hw_net.c",
    "chars": 6377,
    "preview": "/******************************************************************************\n * @file hw_net.c\n *\n * @brief Settings,"
  },
  {
    "path": "hardware/rp2xxx/hw_registers.c",
    "chars": 754,
    "preview": "/******************************************************************************\n * @file hw_registers.c\n *\n * @brief Fun"
  },
  {
    "path": "hardware/rp2xxx/hw_reset.c",
    "chars": 5108,
    "preview": "/******************************************************************************\n * @file hw_reset.c\n *\n * @brief Functio"
  },
  {
    "path": "hardware/rp2xxx/hw_spi.c",
    "chars": 3031,
    "preview": "/******************************************************************************\n * @file hw_spi.c\n *\n * @brief Functions"
  },
  {
    "path": "hardware/rp2xxx/hw_uart.c",
    "chars": 6207,
    "preview": "/******************************************************************************\n * @file hw_uart.c\n *\n * @brief Function"
  },
  {
    "path": "hardware/rp2xxx/hw_usb.c",
    "chars": 6396,
    "preview": "/******************************************************************************\n * @file hw_usb.c\n *\n * @brief Functions"
  },
  {
    "path": "hardware/rp2xxx/hw_versions.c",
    "chars": 896,
    "preview": "/******************************************************************************\n * @file hw_versions.c\n *\n * @brief Func"
  },
  {
    "path": "hardware/rp2xxx/hw_watchdog.c",
    "chars": 1163,
    "preview": "/******************************************************************************\n * @file hw_watchdog.c\n *\n * @brief Func"
  },
  {
    "path": "hardware/rp2xxx/hw_wifi.c",
    "chars": 5113,
    "preview": "/******************************************************************************\n * @file hw_wifi.c\n *\n * @brief Function"
  },
  {
    "path": "hardware/rp2xxx/net_inc/httpd_content/404.html",
    "chars": 8381,
    "preview": "<html>\n<head>\n    <title>BBOS</title>\n</head>\n<body>\n    <style>\n        * {\n            background-color: black;\n      "
  },
  {
    "path": "hardware/rp2xxx/net_inc/httpd_content/index.shtml",
    "chars": 1763,
    "preview": "<html>\n<head>\n    <title>BBOS</title>\n</head>\n<body>\n    <style>\n        * {\n            background-color: black;\n      "
  },
  {
    "path": "hardware/rp2xxx/net_inc/httpd_content/test.shtml",
    "chars": 838,
    "preview": "<html>\n<head>\n    <title>BBOS</title>\n</head>\n<body>\n    <style>\n        * {\n            background-color: black;\n      "
  },
  {
    "path": "hardware/rp2xxx/net_inc/hw_net.h",
    "chars": 2781,
    "preview": "/******************************************************************************\n * @file hw_net.h\n *\n * @brief Settings,"
  },
  {
    "path": "hardware/rp2xxx/net_inc/hw_wifi.h",
    "chars": 10891,
    "preview": "/******************************************************************************\n * @file hw_wifi.h\n *\n * @brief settings"
  },
  {
    "path": "hardware/rp2xxx/net_inc/lwipopts.h",
    "chars": 4404,
    "preview": "#ifndef LWIPOPTS_H\n#define LWIPOPTS_H\n\n\n// Common settings used in most of the pico_w examples\n// from: https://github.c"
  },
  {
    "path": "hardware/rp2xxx/onboard_flash.c",
    "chars": 4051,
    "preview": "/******************************************************************************\n * @file onboard_flash.c\n *\n * @brief Fu"
  },
  {
    "path": "hardware/rp2xxx/onboard_led.c",
    "chars": 1247,
    "preview": "/******************************************************************************\n * @file onboard_led.c\n *\n * @brief Func"
  },
  {
    "path": "hardware/rp2xxx/pico_sdk_import.cmake",
    "chars": 3775,
    "preview": "# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake\n\n# This can be dropped into an external project to he"
  },
  {
    "path": "hardware/rp2xxx/prebuild.cmake",
    "chars": 2782,
    "preview": "# PICO_BOARD should be one of the boards listed in Pico SDK header files\n# see: https://github.com/raspberrypi/pico-sdk/"
  },
  {
    "path": "hardware/rp2xxx/rtos_config.h",
    "chars": 2588,
    "preview": "/******************************************************************************\n * @file rtos_config.h\n *\n * @brief Hard"
  },
  {
    "path": "littlefs/CMakeLists.txt",
    "chars": 679,
    "preview": "# This file configures the build for the littlefs submodule, which is built\n# as a static library to be used by the main"
  },
  {
    "path": "main.c",
    "chars": 999,
    "preview": "/******************************************************************************\n * @file main.c\n *\n * @brief Main entryp"
  },
  {
    "path": "project.cmake",
    "chars": 831,
    "preview": "# PROJECT NAME - in quotes, no spaces\nset(PROJ_NAME \"my-bbos-proj\")\n\n# PROJECT VERSION - in quotes, no spaces, can conta"
  },
  {
    "path": "rtos/CMakeLists.txt",
    "chars": 98,
    "preview": "# add source files to the top-level project\ntarget_sources(${PROJ_NAME} PRIVATE\n    rtos_utils.c\n)"
  },
  {
    "path": "rtos/FreeRTOSConfig.h",
    "chars": 6656,
    "preview": "/******************************************************************************\n * @file FreeRTOSConfig.h\n *\n * @brief C"
  },
  {
    "path": "rtos/rtos_utils.c",
    "chars": 1185,
    "preview": "/******************************************************************************\n * @file rtos_utils.c\n *\n * @brief Utili"
  },
  {
    "path": "rtos/rtos_utils.h",
    "chars": 1412,
    "preview": "/******************************************************************************\n * @file rtos_utils.h\n *\n * @brief Utili"
  },
  {
    "path": "services/CMakeLists.txt",
    "chars": 342,
    "preview": "# add source files to the top-level project\ntarget_sources(${PROJ_NAME} PRIVATE\n    services.c\n    service_queues.c\n    "
  },
  {
    "path": "services/cli_service.c",
    "chars": 3861,
    "preview": "/******************************************************************************\n * @file cli_service.c\n *\n * @brief CLI "
  },
  {
    "path": "services/heartbeat_service.c",
    "chars": 3044,
    "preview": "/******************************************************************************\n * @file heartbeat_service.c\n *\n * @brie"
  },
  {
    "path": "services/netman_service.c",
    "chars": 7593,
    "preview": "/******************************************************************************\n * @file netman_service.c\n *\n * @brief n"
  },
  {
    "path": "services/service_queues.c",
    "chars": 3836,
    "preview": "/******************************************************************************\n * @file service_queues.c\n *\n * @brief I"
  },
  {
    "path": "services/service_queues.h",
    "chars": 8607,
    "preview": "/******************************************************************************\n * @file service_queues.h\n *\n * @brief I"
  },
  {
    "path": "services/services.c",
    "chars": 2060,
    "preview": "/******************************************************************************\n * @file services.c\n *\n * @brief Defines"
  },
  {
    "path": "services/services.h",
    "chars": 10800,
    "preview": "/******************************************************************************\n * @file services.h\n *\n * @brief Defines"
  },
  {
    "path": "services/storman_service.c",
    "chars": 15992,
    "preview": "/******************************************************************************\n * @file storman_service.c\n *\n * @brief "
  },
  {
    "path": "services/taskman_service.c",
    "chars": 3308,
    "preview": "/******************************************************************************\n * @file taskman_service.c\n *\n * @brief "
  },
  {
    "path": "services/usb_service.c",
    "chars": 3281,
    "preview": "/******************************************************************************\n * @file usb_service.c\n *\n * @brief USB "
  },
  {
    "path": "services/watchdog_service.c",
    "chars": 1630,
    "preview": "/******************************************************************************\n * @file watchdog_service.c\n *\n * @brief"
  },
  {
    "path": "version.h",
    "chars": 1238,
    "preview": "/******************************************************************************\n * @file version.h\n *\n * @brief contains"
  }
]

About this extraction

This page contains the full source code of the mcknly/breadboard-os GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 73 files (317.7 KB), approximately 83.0k tokens, and a symbol index with 223 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!