Sunday, 2 January 2022

PIMORONI Snowflake Solo

 Pimoroni released their Snowflake product based on a design by Lucky Resistor a number of years ago.


I thought it looked great but was not sure if five of them would be a bit overwhelming.

This year they introduced the Snowflake Solo, a single Snowflake board and a controller board all on a single beautifully crafted PCB.

The Snowflake Solo comes on a single etched PCB and includes a 200mm flat cable to run between the control board and the Snowflake.
Half etched supports hold the control board and the snowflake onto the PCB. There are instructions here on how to separate the components from the carrier board. The important thing is to bend the carrier and not the snowflake/controller board.

Once the two boards  are separated they need to be connected. The connectors on both boards are identical (note that there are two connectors on the snowflake - an In and an Out).
On the control board and the In connector on the snowflake, flip up the connector to open it, insert the cable with the blue side upwards, and close the connector.

The control board has a micro-usb socket for power, I used one of my Raspberry Pi Zero mains adaptors, any micro-USB charger should be fine.

The snowflake is programmed with a number of different patterns, these can be selected by clicking the centre button on the control board. Click it once to select change mode, then click it a number of times to select the rquired mode.

The acrylic stand is quite a tight fit.


Saturday, 26 June 2021

Temperature measuring Web Server

So, having had the Pimoroni Breakout Garden for a while and having used it as a temperature measurement device with a BME680 and display, I needed to measure the temperature and compare it with the temperature measured  by the analogue sensor of an Adafruit PyPortal and the CPU temperature.

The BME680 results are affected by the heat generated by the CPU, and a correction formula based on the CPU temperature was used to compensate for the CPU temperature.

An alternative is to separate the temperature sensor from the CPU.

This was achieved using a Breakout Garden to Stemma QT/QWIIC adapter and STEMMA QT / Qwiic to Breakout Garden Adapter  joined by a JST to JST cable. A BMP280 breakout was plugged in at one end, and the other plugged into the Breakout Garden. This should provide sufficient thermal isolation from the hot CPU.


Software installation

The Python library for the BMP280 temperature, pressure and altitude sensor is available via PIP. As with all installations, check if pip is for the now obsolete Python 2.x or Python 3.x. If the former, then use pip3 (that is the case on my Raspberry Pi Zero development systerm).

sudo pip3 install bmp280

The easiest way to make the data available to an Internet connectable device is by making the temperature measuring Raspberry Pi a web server.

The Flask library is similarly installed with pip. Note Flask starts with a capital letter
sudo pip3 install -U Flask

Web Server

This is a very simple web server, it has two  pages - /temperature and /json.

from flask import Flask, render_template
import time
import datetime
import json
from bmp280 import BMP280

try:
    from smbus2 import SMBus
except ImportError:
    from smbus import SMBus
app = Flask(__name__)

@app.route('/temperature')
def temperature():
    now = datetime.datetime.now()
    timeString = now.strftime("%Y-%m-%d %H:%M")
    temperature = bmp280.get_temperature()
    temp = '{:05.2f}*C'.format(temperature)
    templateData = {
      'temperature' : temp,
      'time': timeString
      }
    return render_template('temperature.html', **templateData)

@app.route('/json')
def tempjson():
    now = datetime.datetime.now()
    timeString = now.strftime("%Y-%m-%d %H:%M")
    temperature = bmp280.get_temperature()
    temp = '{:05.2f}'.format(temperature)
    templateData = {
      'temperature' : temp,
      'time': timeString
      }

    return json.dumps(templateData)

if __name__ == '__main__':
    bus = SMBus(1)
    bmp280 = BMP280(i2c_dev=bus)
    app.run(debug=True, host='0.0.0.0')

References


Saturday, 12 June 2021

Raspberry Pi Pico and MicroPython

Installing MicroPython



To install or upgrade MicroPython on a Pico there are three steps.

1. Download the UF2 file. There is a vanilla version available from micropython.org, or if you are using one of Pimoroni's boards then you can select one of the UF2 files with all their libraries preinstalled here.

2. Put the Pico into BootLoader mode by unplugging it, hold the BOOTSEL button down and then plugging it back in. The Pico will then appear as a USB device on the host machine.

3. Copy the UF2 file into the new USB device

Once the file has been copied over, the USB device will disappear.

Testing the MicroPython

As the Pico has no built-in display, it can only communicate via the USB. The MicroPython firmware has a built-in serial port available over the USB.

To access it, the popular Minicom package is used.

Install it on your Pi using:

sudo apt install minicom

Next, identify the serial port.

The Pico should appear as a port of the form /dev/ttyACM.

Use 

ls /dev/tty*

If it is unclear which port is the Pico, unplug it and try again, identifying which device disappears.

Connect to the MicroPython running on the Pico using:

minicom -o -D /dev/ttyACM0

This should display the Minicom opening text:

Welcome to minicom 2.7.1
OPTIONS: I18n 
Compiled on Aug 13 2017, 15:25:34.
Port /dev/ttyACM0, 07:39:31
Press CTRL-A Z for help on special keys

You now need to soft reboot, press Control and D. This will display information about the installed MicroPython and the prompt >>>.

MPY: soft reboot

MicroPython v1.15 on 2021-04-18; Raspberry Pi Pico with RP2040

Type "help()" for more information.

>>>

To test the MicoPython, try the classic:

>>> print("hello")

hello

>>>

This shows that MicroPython is running successfully on your Pico.

Control A then Z brings up help.

Control X closes Minicom.

References

https://micropython.org/download/rp2-pico/rp2-pico-latest.uf2

https://github.com/pimoroni/pimoroni-pico/releases

https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf

https://en.wikipedia.org/wiki/Minicom


Friday, 21 May 2021

Adafruit Trinkey

The Adafruit Trinkey is a tiny USB key format microcontroller with four Neopixels and an M0 processor.

 


The end opposite the USB contact can function as two touch sensitive buttons.

I bought mine from Pimoroni.

When plugged into the USB port of my Debian laptop, it failed to register. I thought it might be an issue with connecting to an elderly ex-Windows laptop. So I tried it on a Windows laptop. Still it did not register. The Neopixels worked, and the touch sensors started and stopped the display.

However, when set to Boot Mode (double press on the reboot button) it registered as a USB device.

I then downloaded the latest version of CircuitPython from https://circuitpython.org/board/neopixel_trinkey_m0/

This was then copied to the device which then causes the device to restart. The device now appeared as a USB memory device.

Programming

CircuitPython devices are simple to program, appearing as a USB memory device. Saving a new file to the device causes it to restart. Generally the file code.py is executed on starting.

Adafruit's recommendation is to use the Mu editor. This writes directly to the CircuitPython device. It does however mean you need to be particularly careful that you have a copy of the code somewhere.

To make my life easier, I put together a simple Visual Studio program to copy specific code.py files from specific folders. I then edited the code using Visual Studio Code.

Example code

The example code is derived from the code from the Neo Trinkey Zoom Short Cut project (see references)

import time
import board
import neopixel

#  setup for onboard neopixels
pixel_pin = board.NEOPIXEL
num_pixels = 4

pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.05, auto_write=False)

def wheel(pos):
    # Input a value 0 to 255 to get a color value.
    # The colours are a transition r - g - b - back to r.
    if pos < 0 or pos > 255:
        return (0, 0, 0)
    if pos < 85:
        return (255 - pos * 3, pos * 3, 0)
    if pos < 170:
        pos -= 85
        return (0, 255 - pos * 3, pos * 3)
    pos -= 170
    return (pos * 3, 0, 255 - pos * 3)

def rainbow_cycle(wait):
    for j in range(255):
        for i in range(num_pixels):
            rc_index = (i * 256 // num_pixels) + j
            pixels[i] = wheel(rc_index & 255)
        pixels.show()
        time.sleep(wait)

while True:
    rainbow_cycle(0.001)

References

https://www.adafruit.com/product/4870
https://circuitpython.org/board/neopixel_trinkey_m0/

Saturday, 24 April 2021

Writing Windows Services - Creating a Windows Service

Introduction

Windows Services are the Microsoft Windows' implementation of the computer science daemon.

Creating the service

In Visual Studio go to File/New/Project.

On the dialogue select Windows Service (.NET Framework).

Name the project and click Create.

Rename the Service1.cs file. VS will offer to rename the references as well, agree to it.

In the Design Tab, select Properties from the short cut menu.

Name the service. This is at the bottom of the properties and is easy to overlook (the error starts "service name contains invalid characters").

Add an Event Log object.

View Code to see the code.

Add a custom event log.

        public MyService()
        {
            InitializeComponent();
            eventLog1 = new System.Diagnostics.EventLog();
            if (!System.Diagnostics.EventLog.SourceExists("MySource"))
            {
                System.Diagnostics.EventLog.CreateEventSource(
                    "MySource", "MyNewLog");
            }
            eventLog1.Source = "MySource";
            eventLog1.Log = "MyNewLog";
        }

Add polling to the OnStart.

Add method called by timer.

        protected override void OnStart(string[] args)
        {
            eventLog1.WriteEntry("In OnStart.");
            ServiceStatus serviceStatus = new ServiceStatus();
            serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
            serviceStatus.dwWaitHint = 100000;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);            // Set up a timer that triggers every minute.
            Timer timer = new Timer();
            timer.Interval = 60000; // 60 seconds
            timer.Elapsed += new ElapsedEventHandler(this.OnTimer);
            timer.Start();
            // Update the service state to Running.
            serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);
        }

If the start (and/or) stop process is expected to take some time, it is possible to add additional service status information.

Creating the installer

Select the Service code file in Solution Explorer and choose View Designer.

In the Design View select the background and Add Installer.

This will add ProjectInstaller.cs to the project.

Click on the serviceInstaller1 and select Properties

Ensure the service name is correct, add a description and a display name.

Set the StartType to Automatic.

Right click on serviceProcessInstaller1 and select Properties. Set the Account property to LocalSystem.



Build the service

In the Solution Explorer select the service project and right click and select properties.

On the Application tab select the service program as the Startup object.

Build the project via Build/Build on the menu.

Installing the Service

Open the Developer Command Prompt for Visual Studio as Administrator from the Windows Start menu.

Change Directory to the location of the service executable.

Enter the command: installutil "My First Service.exe"

Checking your work

In the search box on the task bar type "services" and select the "Services" app.

Check for your new service.

Again in the search box on the task bar type "event" and select the Event Viewer.

Expand the Applications and Services Logs and look for your new log. Refresh the log to see each new log entry as the timer triggers.

Updating your Service

Now your service is n place and running, but you need to update it. Now you can uninstall the service (see later), rebuild it and re-install it.

Alternatively, you can just stop the service (using the Services app), rebuild it, and restart the service.

Cleaning Up

Open the Developer Command Prompt for Visual Studio as Administrator.

Change the Directory to where the executable is located.

Enter the command

installutil /u "My First Service.exe"

To uninstall the service.

References

https://en.wikipedia.org/wiki/Windows_service

https://en.wikipedia.org/wiki/Daemon_(computing)

https://docs.microsoft.com/en-us/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer

https://docs.microsoft.com/en-us/visualstudio/deployment/deploying-applications-services-and-components#create-an-installer-package-windows-desktop

https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontroller

https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventloginstaller


Saturday, 3 April 2021

Delivering files to the Browser

Sometimes you want to embed files in a browser, such as images or PDF files.

With modern browsers it is easy to just add some HTML to display a file.

 <object data="documents/Hello World.pdf" type="application/pdf" width="100%" height="800px">

 <p>It appears you don't have a PDF plugin for this browser.</p>

</object>

The file to be displayed needs to be in a valid location on a web server, either the webserver delivering the page or an external one accessed with a full URL.

This is fine if you do not care that it potentially exposes your file structure.


If your webserver runs a scripting language it is possible to script the delivery of the file - the file does not have to be located in a similar location to the web server files, nor does it need to be accessible by HTTP (though it can be but hidden on a SharePoint or cloud location).


The following uses ASP.NET Core to build a RESTFul interface to deliver selected files on receipt of a request.

If you have not built a Web API project before, it is worth running through the tutorial here (https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&tabs=visual-studio).

Web API to deliver selected files

This Web API uses a RESTFul interface to request a file, the file request is included within the URL, and in this case is just an integer.

Requests to the Web API have the form:

http://<domanin name>/getpdf/<file id>

The test web page builds on the HTML above, the data attribute containing the RESTFul URL to select the files.

<object data="https://localhost:44310/getpdf/0" type="application/pdf" width="100%" height="800px">

 <p>It appears you don't have a PDF plugin for this browser.</p>

</object>


GetPDFController.cs

The following code is for a controller (additional  code is required, use the set up from the tutorial).

using Microsoft.AspNetCore.Mvc;

using Microsoft.Extensions.Logging;

using System;

/// <summary>

/// Web App controller

/// Controller for RESTful access to PDFs

/// The last part of the URL is an integer that selects a specific PDF

/// https://&lt;host&gt;/getpdf/&lt;pdfID&gt;

/// </summary>

namespace GetPDF.Controllers

{

    [ApiController]

    [Route("[controller]")]

    public class GetPDFController : ControllerBase

    {

        private readonly ILogger<GetPDFController> _logger;


        public GetPDFController(ILogger<GetPDFController> logger)

        {

            _logger = logger;

        }


        /// <summary>

        /// Returns a PDF selected by <paramref name="pdfID"/

        /// THis example uses a simple switch to select the PDF,

        /// in a real application this might be search parameters or 

        /// >multiple values that build into the file path

        /// 

        /// Additionally, in a real application there would be a 

        /// requirement for the authentication of the requester.

        /// Additionally the PDF could be watermarked before being

        /// sent.

        /// </summary>

        /// <param name="pdfID">PDF identifier</param>

        /// <returns>PDF or 404</returns>

        [HttpGet]

        [Route("{pdfID:int}")]

        public IActionResult Get(int pdfID)

        {

            string filename;

            // This is for demonstration purposes only.

            // This would require a more complicated solution

            switch (pdfID)

            {

                case 0:

                    filename = @"C:\inetpub\wwwroot\documents\file-0.pdf";

                    return new PhysicalFileResult(filename, "application/pdf");

                case 1:

                    filename = @"C:\inetpub\wwwroot\documents\file-1.pdf";

                    return new PhysicalFileResult(filename, "application/pdf");

               default:

                    return NotFound();

            }


        }

    }

}

References

https://en.wikipedia.org/wiki/ASP.NET_Core

https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&tabs=visual-studio

https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-5.0

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.physicalfileresult?view=aspnetcore-5.0


Pimoroni Enviro board

 The Pimoroni Enviro pHAT format board combines  a BME280 temperature, pressure and humidity sensor, LTR559 light and proximity sensor, a microphone and small colour LCD.

The Enviro board comes packed in a neatly printed anti-static bag (I was too excited to photograph it before opening it up)




Pimoroni have a Python library and a bunch of examples.


Assembly was easy, and a a pair of stand-offs secured the sandwich.

Here is an example program that displays the temperature, humidity, atmospheric pressure, lux level and the current time.

import ST7735
from PIL import Image, ImageDraw, ImageFont
from fonts.ttf import RobotoMedium as UserFont
from bme280 import BME280
import time
import datetime

import os

try:
    # Transitional fix for breaking change in LTR559
    from ltr559 import LTR559
    ltr559 = LTR559()
except ImportError:
    import ltr559

print("Starting")
bme280 = BME280()

display_angle = 270 # 90 for upside dow relative to screen print

# Set up the display
disp = ST7735.ST7735(
    port=0,
    cs=1,
    dc=9,
    backlight=12,
    rotation=display_angle,
    spi_speed_hz=10000000
)

# Initialize display.
disp.begin()

# Width and height to calculate text position.
WIDTH = disp.width
HEIGHT = disp.height

# Calculate text position
x = 5
y = 0

# Set up canvas and font
img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
path = os.path.dirname(os.path.realpath(__file__))
font_size = 20
font = ImageFont.truetype(UserFont, font_size)
small_font = ImageFont.truetype(UserFont, 16)
large_font = ImageFont.truetype(UserFont, 38)

text_colour = (255, 255, 255)
back_colour = (0, 50, 170)

def display_data():
    temperature = bme280.get_temperature()
    pressure = bme280.get_pressure()
    humidity = bme280.get_humidity()
    lux = ltr559.get_lux()
    prox = ltr559.get_proximity()
    draw.rectangle((0, 0, WIDTH, HEIGHT), back_colour)
    draw.text((x, y), "{:04.1f}C".format(temperature), font=font, fill=text_colour)
    draw.text((x, y+25), "{:04.1f}%".format(humidity), font=font, fill=text_colour)
    draw.text((x, y+50), "{:4.0f}hPa".format(pressure), font=font, fill=text_colour)
    # alternating seconds flash the colon separator
    if int(time.time()) % 2 == 0:
        draw.text((x+60,y), datetime.datetime.now().strftime("%H:%M"),
                  font=large_font,fill=text_colour)
    else:
        draw.text((x+60,y), datetime.datetime.now().strftime("%H %M"),
                  font=large_font,fill=text_colour)
    draw.text((x+100,y+55),"{:05.02f}lx".format(lux), font=small_font,fill=text_colour)
    disp.display(img)

# Main loop
while True:
    #print(bme280.get_temperature())
    display_data()
    time.sleep(1)