more devices utility development with boost’s json library

Over the past few days I’ve been learning how to use Boost’s JSON library within my C++ device listing utility. My adventures up to this point are documented down in the links section, so I won’t be going over anything I’ve already written about. If you want to fully understand what’s happening in this post them please read the earlier posts.

I added the ability to define what all my devices were inside a JSON file, then modified my Python version of this utility to read in that file and use those descriptions to better remind me devices what I had plugged in. Along the way I also created the same type of utility in C++ and Rust, which were adventures in and of themselves.

This entry documents additional evolution of the C++ version of the utility. The changes from the first version of the C++ utility are;

  1. Read in and parse the devices.json file that contains the human understandable annotations.
  2. The ability to create a “raw” version of the devices.json file if you don’t have one.

The devices.json file must reside in the same folder as the devices utility itself. Furthermore, if you decide to generate a new devices.json file then you need to have write privileges to that folder; that’s why mine resides in my home folder’s bin directory. Finally, so that you don’t shoot yourself in the foot and try to generate a new annotations file on top of your existing annotations file, devices will abort and not write over an existing devices.json file.

Listing

#include <string>#include <array>#include <map>#include <algorithm>#include <iostream>#include <fstream>#include <filesystem>#include <regex>#include <exception>#include <boost/program_options.hpp>#include <boost/json.hpp>using std::cout;using std::cerr;using std::exception;using std::string;using std::array;using std::map;using std::pair;using std::regex;using std::regex_search;using std::replace;using std::smatch;using boost::program_options::options_description;using boost::program_options::value;using boost::program_options::variables_map;using boost::program_options::store;using boost::program_options::parse_command_line;using boost::program_options::notify;using std::filesystem::directory_iterator;using std::filesystem::path;using std::filesystem::read_symlink;using std::filesystem::exists;void split(const string &subj, const regex &rgx, array<string, 2> &vars) {smatch match;regex_search(subj, match, rgx);vars[0] = subj.substr(0, match.position(0));replace(vars[0].begin(), vars[0].end(), '_', ' ');vars[1] = match.str(0).erase(0,1);vars[1].erase(vars[1].end()-1);}map<string, array<string,2>> devices;map<string, string> device_annotations;void find_devices() {const regex hregex{"_[0-9A-Ia-f]+-"};for (const auto &entry : directory_iterator("/dev/serial/by-id/")) {string slink = read_symlink(entry).stem().generic_string();string sname = entry.path().stem().generic_string().erase(0,4);array<string, 2> results;split(sname, hregex, results);devices[slink] = results;}}void print_devices() {if (not device_annotations.empty()) {for (auto [device, values] : devices) {cout << device << ", " << (device_annotations.contains(values[1]) ?device_annotations[values[1]] : values[1]) << "\n";}}else {for (auto [device, values] : devices) {cout << device << ", "  << values[0] << ", " << values[1] << "\n";}}}namespace json = boost::json;using std::filesystem::file_size;void parse_devices_json_annotations(int argc, char **argv) {if (argc <= 0 or strlen(argv[0]) == 0) {cout << "JSON no application name in environment to work with\n";return;}auto json_name = path(argv[0]).stem().string().append(".json");auto json_path = read_symlink("/proc/self/exe").replace_filename(json_name);if (not exists(json_path)) {cout << "JSON annotation file missing - no annotations" << "\n";return;}auto json_file_size = file_size(json_path);if (json_file_size == 0) {cout << "JSON file size is 0 - aborting\n";return;}std::ifstream json_file(json_path);if (not json_file.is_open()) {cout << "JSON file did not open - aborting\n";return;}json::stream_parser parser;json::error_code err_code;auto memblock = new char[json_file_size];std::memset(memblock, 0, json_file_size);json_file.read(memblock, json_file_size);json_file.close();parser.write(memblock, json_file_size, err_code);delete [] memblock;if (err_code) {cout << "JSON failed to parse - aborting\n";return;}parser.finish(err_code);if (err_code) {cout << "JSON failed to finish parsing - aborting\n";return;}json::value json_data = parser.release();if (json_data.is_null()) {cout << "JSON data is empty - aborting\n";}if (auto obj = json_data.if_object()) {auto annotations = obj->at("devices");if (annotations.is_array()) {for( auto element : annotations.get_array() ) {auto my_key = element.at("hexid").as_string().c_str();auto my_val = element.at("description").as_string().c_str();device_annotations[my_key] = my_val;}}}else {cout << "JSON top level data is not an array - aborting\n";}}void generate_default_json_annotations(int argc, char **argv) {if (argc <= 0 or strlen(argv[0]) == 0) {cout << "JSON no application name in environment to work with\n";return;}find_devices();if(devices.empty()) {cout << "No devices found. No annotations file generated.\n";return;}auto json_name = path(argv[0]).stem().string().append(".json");auto json_path = read_symlink("/proc/self/exe").replace_filename(json_name);if (exists(json_path)) {cout << "JSON annotation file already exists, will not overwrite." << "\n";return;}std::ofstream json_file(json_path);cout << "Generating default JSON annotations file "<< json_path << "\n";if (json_file.is_open()) {auto device_count = devices.size();cout << "Device count: " << device_count << "\n";json_file << "{ \"devices\" : [\n";for (auto [device, values] : devices) {json_file << "{\n\"hexid\" : \"";json_file << values[1] << "\",\n";json_file << "\"description\" : \"" << values[0];json_file << (--device_count ? "\"\n},\n" : "\"\n}\n");}json_file << "] }\n";json_file.close();return;}else {cout << "JSON output file did not open - aborting.\n";return;}}int main(int argc, char **argv) {options_description options("devices options");options.add_options()("help", "Help using application.")("json", "Generate starting JSON annotations file.");try {variables_map varmap;store(parse_command_line(argc, argv, options), varmap);notify(varmap);if (varmap.count("help")) {cout << options << "\n";return 0;}if (varmap.count("json")) {generate_default_json_annotations(argc, argv);return 0;}}catch (exception &ex) {cerr << "Error: " << ex.what() << "\n";cerr << "\n" << options << "\n";return -1;}parse_devices_json_annotations(argc, argv);find_devices();print_devices();return 0;}

Example Runs

A run of the utility without a JSON annotations file. Note the comment on the first line, and a sorted list (by device) of all the connected development board devices. Without the annotations file all that’s listed are the name of each USB adapter on each board followed by each boards unique hexadecimal ID.

JSON annotation file missing - no annotationsttyACM0, Adafruit Adafruit Feather ESP32S3 4MB Flash 2MB PSRAM, 4F21AF95CC4DttyUSB0, Silicon Labs CP2102N USB to UART Bridge Controller, 7eab1642dbfaeb1198773ca4c6d924ecttyUSB1, Silicon Labs CP2102N USB to UART Bridge Controller, e08d862b0867ec118f12a17089640db2ttyUSB2, Silicon Labs CP2102N USB to UART Bridge Controller, 964756d6d823ed119c228ee8f9a97352ttyUSB3, Silicon Labs CP2102N USB to UART Bridge Controller, 80e3350df417ec11baa043103803ea95

Here’s what happens when I create a raw devices.json file.

$ ./build/devices --jsonGenerating default JSON annotations file "/home/mint/Develop/CPPwork/devices/build/devices.json"Device count: 5

Now when I re-run the utility, the annotations file is read in and parsed. Note that the hexadecimal IDs are gone. That’s because they’ve been used to read out the default annotations, which are the USB adapter names. This can be confusing at first. This is where you, as the developer, open up devices.json and edit those USB names and replace them with something that has meaning to you.

ttyACM0, Adafruit Adafruit Feather ESP32S3 4MB Flash 2MB PSRAMttyUSB0, Silicon Labs CP2102N USB to UART Bridge ControllerttyUSB1, Silicon Labs CP2102N USB to UART Bridge ControllerttyUSB2, Silicon Labs CP2102N USB to UART Bridge ControllerttyUSB3, Silicon Labs CP2102N USB to UART Bridge Controller

Here’s what happens when I run the utility with my personal devices.json file.

ttyACM0, CircuitPython 8.2 - WiFi/UDP/JSONttyUSB0, ESP32-C3-DevKitC-1-N4 - ESP-IDF 5.2-dev DisplaysttyUSB1, ESP32-S3-DevKitC-1.1-N32R8 - ESP-IDF 5.2-dev WiFittyUSB2, ESP32-C6-DevKitC-1-N8 - ESP-IDF 5.2-dev NeoPixelttyUSB3, ESP32-S3-DevKitC-1.1-N8R8 - MicroPython 1.20.0 WiFi

Building

I use cmake to build the application.

cmake_minimum_required (VERSION 3.27.0)cmake_policy(SET CMP0144 NEW)project (devices)set(Boost_NO_WARN_NEW_VERSIONS 1)find_package(Boost 1.83.0 REQUIREDCOMPONENTS json)# The switch -Weffc++ (using Effective C++) generates tremendous# warnings, including source you have no control over, such as in Boost.# Makes you wonder.#### add_compile_options(-Wall -Wextra -Wpedantic -Weffc++)add_compile_options(-Wall -Wextra -Wpedantic -std=c++20)include_directories(/home/mint/Develop/boost/)link_directories(/home/mint/Develop/boost/stage/lib)add_executable (devices main.cpp)### set_property(TARGET devices PROPERTY CXX_STANDARD 20)target_link_libraries(devices ${Boost_LIBRARIES} boost_program_options)

I use cmake to keep everything straight. For just 18 lines for cmake I get a 181 GNU makefile, which is correct. I could probably do better with my cmake file, and I probably will in the future. Today I’m happy everything builds and works as planned.

Links

additions to listing usb-connected development boards

python vs c++ vs rust — a personal adventure

successful build and flash of esp32-c6-devkitc-1

I managed to order three of the ESP32-C6-DevKitC-1 developer boards from the Espressif store on Amazon. They’ve been advertised since mid-2022, and where-ever I went to try to buy them, they were advertised as out of stock. Finally I stumbled onto them on Amazon, and grabbed three examples. They arrived at my home earlier this week. They had to wait until today before I could sit down and begin to work with them.

These are not supported in any existing release of Espressif’s ESP-IDF. I had to clone from the master, or trunk, of their GitHub repo. This is what I used to perform that clone:

git clone --recursive https://github.com/espressif/esp-idf.git esp-idf-main

Once cloned I installed the ESP-IDF software and enabled the development environment I then moved into my wifi-scan folder that I’d migrated to ESP-IDF V5, and ran the following:

idf.py --preview set-target esp32c6

You have to run --preview for everything to be properly set up for the build. After that you can run a regular idf.py build, followed by the flash and monitor commands. You will need to do this before the boards will work. Out-of-the-box the boards, when powered up via USB-C, will immediately go into an endless boot loop because they’re flashed with the wrong code.

Here’s a small portion of what I saw on successful boot after a successful flash.

...ESP-ROM:esp32c6-20220919Build:Sep 19 2022rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)SPIWP:0xeemode:DIO, clock div:2load:0x4086c510,len:0xd3cload:0x4086e610,len:0x2a64load:0x40875720,len:0x1790entry 0x4086c510I (23) boot: ESP-IDF v5.1-dev-3462-g045163a2ec 2nd stage bootloaderI (24) boot: compile time Feb 17 2023 13:48:04I (24) boot: chip revision: v0.0I (28) boot.esp32c6: SPI Speed  : 40MHzI (33) boot.esp32c6: SPI Mode   : DIOI (38) boot.esp32c6: SPI Flash Size : 2MBI (42) boot: Enabling RNG early entropy source...W (48) bootloader_random: bootloader_random_enable() has not been implemented yet...

Here’s some interpretation of those output lines. Line 2 shows that it’s an ESP32-C6 due to the boot ROM on the chip. Line 11 says it was built with ESP-IDF v5.1-dev. I cloned that specific tag, but it won’t support ESP32-C6. You have to use master. I found that a little concerning as the README says that ESP-IDF v5.1, when it’s formally released, is supposed to support the ESP32-C6 device. Oh well.

Everything else seems to be behaving normally, especially support for C++20 in the tool chain. This chip is also supposed to support Zigbee, but I haven’t researched that bit yet. Another goody to check out later.