circuitpython 8.0 rc 1

A new version of CircuitPython is near final release. Version 8 RC 1 was released today (yesterday, actually, but it wasn’t officially documented until today). I’m interested because now there’s an official CircuitPython released specifically for the Raspberry Pi Pico W ( https://circuitpython.org/board/raspberry_pi_pico_w/ ). I’m starting with one of my Pico W boards, and then moving around to all the devices that currently run 7.3.3. So far nothing has leaped out as a problem, but these are very early days. I know that WiFi works, but I don’t know if Bluetooth is working or not.

One application I’ve been tweaking along the way is an automatic quote displayer that queries the Adafruit quote server and prints it out. The response comes back in JSON. The original code, written for CP 7, only printed the response out in a raw format. This version parses the JSON and line wraps long quotes to less than 80 columns. Here’s a typical output.

...
   Injustice anywhere is a threat to justice everywhere.
 - Martin Luther King, Jr.

   I never am really satisfied that I understand anything; because, understand 
   it well as I may, my comprehension can only be an infinitesimal fraction 
   of all I want to understand.
 - Ada Lovelace

   Adversity is revealing of character.
 - Unknown

   The most exciting phrase to hear in science, the one that heralds new discoveries, 
   is not 'Eureka!' but 'That's funny...'.
 - Isaac Asimov

   The whole point of getting things done is knowing what to leave undone.
 - Oswald Chambers
...

And here’s the code.

# Copyright (c) 2023 William H. Beebe, Jr.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Derived/modified from"
# 2022 Liz Clark for Adafruit Industries

import os
import time
import ssl
import wifi
import socketpool
import microcontroller
import adafruit_requests
import json
import gc

# Adafruit quotes URL
#
quotes_url = "https://www.adafruit.com/api/quotes.php"

# Connect to a local access point.
# WIFI_SSID and WIFI_PASSWORD should be defined in your settings.toml file.
#
wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))

pool = socketpool.SocketPool(wifi.radio)
session = adafruit_requests.Session(pool, ssl.create_default_context())
print("\n\nFetching quotes from {}\n".format(quotes_url))
wrap_limit = 72

while True:
    try:
        time.sleep(5)
        gc.collect()

        # Get a quote. Parse the JSON response.
        #
        response = session.get(quotes_url)
        dic = dict(json.loads(response.text)[0])
        qstr = dic['text'] + "."

        # Primitive line wrapping for long text lines.
        #
        while len(qstr) > wrap_limit:
            cut = qstr.find(" ", wrap_limit, len(qstr)) + 1
            if cut > 0:
                print("   {}".format(qstr[:cut]))
                qstr = qstr[cut:]
            else:
                break

        if len(qstr) > 0:
            print("   {}".format(qstr))
        print(" - {}\n".format(dic['author']))

        response.close()
        gc.collect()
    # pylint: disable=broad-except
    except ValueError as e:
        print("Error: bad JSON")
        response.close()
    except Exception as e:
        print("Error:\n", str(e))
        print("Resetting microcontroller in 10 seconds")
        time.sleep(10)
        microcontroller.reset()

As they say, more to come.

building a qt 6.2 application — beginning usb comms

My Qt 6 application isn’t simple any more. It’s grown more complicated as I continue to add features and evolve it towards what I finally want. In this post I’m adding very basic serial communications via USB to my attached development boards. For this post I’m only communicating with the Raspberry Pi Pico W. I’ll be using QSerialPort to develop that functionality. I’ll show what additional source and libraries I had to install, the core modified code, and the application I had running on the Pico W to produce test output.

First things first. I had to install the headers and library files to support developing with Qt’s QSerialPort. Those extra packages are libqt6serialport6 and libqt6serialport6-dev. This will put the necessary header files down /usr/include/x86_64-linux-gnu/qt6/QtSerialPort/. You’ll also need to link against Qt6SerialPort.

Here’s the modified code module. I’ll highlight the pertinent sections after the code block.

#include <QStringList>
#include <QHeaderView>
#include <QTableWidgetItem>
#include <QSplitter>
#include <QPalette>
#include <QSerialPort>
#include <QByteArray>
#include <QScrollBar>
#include <QFont>

#include "MainWindow.hpp"
#include "devices.hpp"

void MainWindow::addCentralWidget() {
    console = new QPlainTextEdit(this);
    QPalette colors = palette();
    colors.setColor(QPalette::Base, Qt::black);
    colors.setColor(QPalette::Text, Qt::white);
    QFont font("Monospace", 12);
    font.setStyleStrategy(QFont::PreferAntialias);
    console->setPalette(colors);
    console->setFont(font);

    serialPort = new QSerialPort(this);

    connect(
        serialPort, &QSerialPort::readyRead, this, &MainWindow::readSerialData);

    serialPort->setPortName("/dev/ttyACM0");
    serialPort->setBaudRate(QSerialPort::Baud115200);
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setParity(QSerialPort::NoParity);
    serialPort->open(QIODevice::ReadOnly);

    table = new QTableWidget(this);
    table->setColumnCount(3);
    table->setRowCount(6);
    QStringList headers = {"Device", "Adapter Name", "Hex Identification"};
    table->setHorizontalHeaderLabels(headers);
    table->horizontalHeader()->setStretchLastSection(true);
    int row{0};

    for (auto [device, values] : getDevices()) {
        QString dev(device.c_str());
        QString v1(values[0].c_str());
        QString v2(values[1].c_str());
        table->setItem(row, 0, new QTableWidgetItem(dev));
        table->setItem(row, 1, new QTableWidgetItem(v1));
        table->setItem(row, 2, new QTableWidgetItem(v2));
        ++row;
    }

    table->resizeColumnsToContents();

    QSplitter *splitter = new QSplitter(Qt::Vertical, this);
    splitter->addWidget(table);
    splitter->addWidget(console);
    setCentralWidget(splitter);
}

void MainWindow::readSerialData() {
    QByteArray data = serialPort->readAll();
    console->insertPlainText(data);
    QScrollBar *scrollBar = console->verticalScrollBar();
    scrollBar->setValue(scrollBar->maximum());
}

The text box is being used again as a console. Starting at line 15, we set up the foreground and background colors and the font we want to see any text in. This gives us a console with white text on a black background, with a 12pt Monospaced font using anti-aliasing. The default font is horrible.

Lines 24 through 27 are critical to this working. While Qt calls the ability to handle callbacks as signals (the call) and slots (the callback), this is their nomenclature for a feature as old as C, which is the ability to call pointers to functions. This was a feature in the Unix widget toolkit, Motif, in which controls had the ability to be programatically configured with pointers to functions that were called when a given control was activated. Line 26 sets up the callback (MainWindow::readSerialData) to be called by the serialPort when any data is present. The callback reads that data and places it to be displayed on the console. This is all done asynchronously such that the GUI event queue isn’t blocked. This is very similar to what Java tries to do with its GUI toolkits. The callback is in lines 61-66. When the application is running, it will open the port to the Pico W and then begin to read the data that the Pico W sends out.

These are early days for this. I only wrote enough to get the callback going and to read from the port. You’ll note that the port is hard coded for the Pico W. In the future I’ll be able to pick any port just by clicking on the entry in the upper table and the console will be fully bi-directional. I’m also going to add the ability to log everything in the console. As a stretch goal I’d like to be able to edit Micropython source in the attached microcontroller as well. But that’s a stretch goal which is going to require almost as much work as all the work leading up to finishing the first three goals.

Now for the interesting part. The Pico W is running Circuit Python 8 beta 6.

# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import os
import time
import ssl
import wifi
import socketpool
import microcontroller
import adafruit_requests
import json
import gc

# Adafruit quotes URL
#
quotes_url = "https://www.adafruit.com/api/quotes.php"

# Connect to your local hostspot.
# WIFI_SSID and WIFI_PASSWORD should be defined in your settings.toml
# file.
#
wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))

pool = socketpool.SocketPool(wifi.radio)
session = adafruit_requests.Session(pool, ssl.create_default_context())
print("Fetching quotes from ", quotes_url)
print()

while True:
    try:
        time.sleep(5)
        gc.collect()

        # Get a quote.
        #
        response = session.get(quotes_url)
        dic = dict(json.loads(response.text)[0])
        qstr = dic['text'] + "."

        print("   " + qstr)
        print(".. " + dic['author'])
        print("")
        response.close()
        gc.collect()
    # pylint: disable=broad-except
    except ValueError as e:
        print("Error: bad JSON")
        response.close()
    except Exception as e:
        print("Error:\n", str(e))
        print("Resetting microcontroller in 10 seconds")
        time.sleep(10)
        microcontroller.reset()

The original code came from Adafruit. I cleaned up the while loop to make the output more readable. The original code just spat out the JSON strings that came back from the URL. I’ve done some formatting and cleanup instead.

That’s it for the time being. I’ll continue to clean up the C++ code. Hopefully I’ll have more progress to post by the weekend.