Sunday, 26 April 2020

Pimoroni Pirate Audio MP3 server.

Pimoroni of Sheffield have recently introduced a range of PHAT form boards for the Raspberry Pi family of single board computers. Their Pirate Audio range offer high quality MP3 playback via a variety of outputs together with the a 240 x 240 full colour display..

I decided on the Pirate Audio Headphone Amp.

The Components


The board is supplied in an resealable anti-static bag.

 I was so excited when I received the package from Pimoroni, I opened it before taking the photograph.
The PHAT form boards are smaller than the standard HAT boards, they are designed to stack on top of any 40 pin Raspberry PI, but they are specifically designed to have the same board dimensions as the Raspberry Pi Zero. So with the Pirate Audio board, I also bought a Raspberry Pi Zero W, as soldering is not one of my skills,  Pimoroni offer them with  pre-soldered  headers.

 So this is the board itself. The screen is not cracked, the protective plastic sheet has partially lifted. Eventually I will remove it once I have finished working on it.
There are four buttons around the screen and a headphone jack on the laft side.
 You can see how the PHAT form boards fit directly on top of the Pi Zero board.
The back of the board showing the Hi/Lo Gain amplifier option switch. Though it is inside the board sandwich when assembled, the switch can be operated using a suitably sized rod.

Assembly

I had a 64GB Sandisk SD in stock (I buy them when they are on sale at my local supermarkets), but Linux in general has issues with the default ExFAT format and Raspberry Pis cannot boot from them. However it is easy to reformat them to FAT32. See this post for details.

I used the recently released Raspberry Pi Imager to install the latest version of Raspbian Buster.

The new SD card was inserted into the Pi Zero and it was connected to a keyboard, mouse, monitor and a USB Ethernet connector via a powered hub. The USB Ethernet connector speeds up the updates
Once the initial set up was complete, it was time to assemble the boards.
The two boards were offered up and then gently pushed together.
I also had a couple of stand offs that I used to support the front of the board sandwich.

Software Installation


The installation script has some issues. Over the Christmas 2019 holidays, the core music server underwent a major upgrade. Unfortunately this seriously broke the software used on the board.
You can follow the story here.

The only real issue to currently be aware of is that the Mopidy Local service is not included in the install script. It was included in the base Mopidy repository, but was split off during the upgrade.

It is easy to install:
sudo apt-get install mopidy-local
After that it is all set up.

Loading MP3s.

Of course a music player is not much use without some music to play.

Martin O'Hanlon has a useful post on Stuff About Code on copying CD music to MP3.
The Python code opens the CD drive, you pop a CD in, close it and off it goes. When it has finished, it pops the drive open again. If you put another CD in and close it, it will process it. This continues until you stop feeding it CDs. Ingenious.

I used his code, but did convert it to Python 3.

I have a Samsung external USB DVD drive. Now it is a bit power hungry. I initially used a powered hub to supply power to the Raspberry Pi, the mouse and keyboard, and the DVD player.

When I tried it, it kept throwing errors. I did wonder if the DVD drive was not compatible. So I tried it on my Raspberry Pi 3 and that happily copied and converted a CD.

So I powered the Raspberry Pi with a separate USB supply and used the USB hub to power the DVD drive, and the keyboard and mouse.

It does make a bit of a rat's nest of cables.

Once I had loaded a few CDs (18), it was time to  give it a try.

I plugged in my headphones and pointed the browser of my tablet at the IP and port number displayed on the Pirate Audio screen. Select the album of choice and...
The sound level is a bit high, so I will have a look at that.

Conclusion

With the exception of the installation of the mopidy-local, the installation was easy.
Remember, if you use a CD/DVD player to ensure you have a suitable power supply.

The Raspberry Pi Zero is a lot less computationally powerful than the Pi 3 or 4, so it does take a while to process the CDs, and the user interface is a bit slow when you run it locally. It is fine on the browser of another machine.

Saturday, 18 April 2020

Ada on the Raspberry Pi

Introduction

The computer language  known as Ada was the result of a US Department of Defense competition to develop a computer language to replace the numerous computer languages used in existing defence projects. The competition was won by a team lead by a French team lead by Jean Ichbiah.

Installation

As with installing any new piece of software , you should update and upgrade the current installation
sudo apt update
sudo apt upgrade
sudo apt-get install gnat

As of 18/04/2020 it installed version 6.1.

Hello World

For simplicity, I used the Geany Program Editor to write my first Ada program.

Create a new file called helloworld.adb, I created a folder called Ada in my Documents folder to take the code. For later work, it is best to create a folder for the source code - compiling and building create a number of files.

In the new file enter the following.
-- My first ADA program
with Ada.Text_Io;
use Ada.Text_Io;
procedure HelloWorld is
begin
   Put_Line("Hello World");
end HelloWorld;
The two hyphens start a comment.
The with tells the compiler what package to use. The contents of the package are made visible to this program by the use.
The procedure has a name (HelloWorld) and the code is bounded by the begin - end.
The put_line is a procedure from the package Ada.Text_Io that takes a string parameter and outputs the string followed by a new line character to the standard output.

Compile and Build

Geany handles all the complicated parameters for compilation and building. The options can be found under the Build menu option. Alternatively the function key F8 compiles and F9 Builds.

Execution

Though there is an execute option in the Geany Build menu, the code executes and then disappears.
To see the result of the program start a terminal window and navigate to the folder with the source code.
The executable file has no suffix but has the name of the source file. Prefix the filename with ./ and press return:
pi@somepi:~/Documents/ADA $ ./helloworld
Hello World
pi@somepi:~/Documents/ADA $ 

References

https://www.adaic.org/learn/materials/#introtoada
https://askubuntu.com/questions/421084/how-do-i-install-the-ada-compiler
http://www.getadanow.com/#get_raspberry_pi
http://www.getadanow.com/#step2
https://en.wikipedia.org/wiki/Ada_(programming_language)

Sunday, 5 April 2020

Pimoroni BME680 Environment sensor

So, having a display is all well and good, but now you need something to display.

The Pimoroni BME680 breakout board has a Bosch BME680 temperature, pressure, humidity and air quality sensor.

The following code assumes you have the BME680 and the SH1106 OLED display on the default I2C addresses.

import os
import time
import datetime
import sys

from PIL import Image

from PIL import ImageFont
from PIL import ImageDraw

import bme680

from luma.core.interface.serial import i2c
from luma.oled.device import sh1106
from luma.core.render import canvas

TEMPERATURE_UPDATE_INTERVAL = 0.1  # in seconds


# Temperature offset 

TEMP_OFFSET = 0.0

print("Temperature/pressure monitor - OLED")

print("Initialising")

# Instantiate object for SH1106 OLED display

oled = sh1106(i2c(port=1, address=0x3C), rotate=2, height=128, width=128)

# Instantiate object to read  BME680 sensor

sensor = bme680.BME680()

# Set up the sensors

sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_temp_offset(TEMP_OFFSET)

# Build the fonts

rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts',
                                       'Roboto-Regular.ttf'))
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts',
                                       'Roboto-Black.ttf'))
rr_24 = ImageFont.truetype(rr_path, 24)
rb_20 = ImageFont.truetype(rb_path, 20)
rr_15 = ImageFont.truetype(rr_path, 15)
rr_40 = ImageFont.truetype(rr_path, 40)

# Get sensor data first so that device settings take effect

sensor.get_sensor_data()
# Get the initial date/time and max/min temperature
low_temp = sensor.data.temperature
high_temp = sensor.data.temperature
curr_date = datetime.date.today().day

last_checked = time.time()


# Main loop

while True:
    if sensor.get_sensor_data():
        temp = sensor.data.temperature
        press = sensor.data.pressure
        humidity =sensor.data.humidity
        if datetime.datetime.today().day == curr_date:
            if temp < low_temp:
                low_temp = temp
            elif temp > high_temp:
                high_temp = temp
        else:
            curr_date = datetime.datetime.today().day
            low_temp = temp
            high_temp = temp

        # Write data to canvas

        with canvas(oled) as draw:
            draw.text((65, 55), u"{0:4.0f}".format(press), fill="white", font=rb_20)
            draw.text((1, 55), u"{0:2.1f}%".format(humidity), fill="white", font=rb_20)
            draw.text((1, 1), u"{0:2.0f}°".format(temp), fill="white", font=rr_40)

            draw.text((65, 4), u"max: {0:2.0f}°".format(high_temp), fill="white", font=rr_15)

            draw.text((65, 30), u"min: {0:2.0f}°".format(low_temp), fill="white", font=rr_15)

            if int(time.time()) % 2 == 0:

                draw.text((14, 78), datetime.datetime.now().strftime("%H:%M"),
                          fill="white", font=rr_40)
            else:
                draw.text((14, 78), datetime.datetime.now().strftime("%H %M"),
                          fill="white", font=rr_40)

    time.sleep(TEMPERATURE_UPDATE_INTERVAL)


And this is the result.

There is a potential issue with the temperature. The temperature sensor is directly above the Raspberry Pi Zero, and more importantly close to being above the processor. This does mean that the heat from the processor may well be affecting the temperature being sensed and hence displayed.

Dealing with that is for the next instalment.


Sunday, 29 March 2020

Pimoroni 1.12" OLED breakout

A long time ago I bought a Pimoroni Breakout Garden and a selection of breakouts.

I had intended to write some very simple test code  and put it on the blog, unfortunately I then managed to break the SD card with the code on. Must remember to back up regularly...

The Breakout Garden equipped Pi Zero then languished at the back of the cupboard while other, shinier things caught my attention.

And then I wanted to know what the temperature was. I do have a number of pieces of technology that would do that (Arduino, Micro:Bit, Raspberry Pi), but decided that the Breakout Garden would be a good starting point.

But first I wanted to check out the operation of the OLED display.

1.12" OLED breakout

Pimoroni provide a good set of example code, including a complete weather station, but I wanted to build my own using the supplied modules.

First off a simple piece of Python to display a message on the screen.

# Simple test of a Pimoroni SH1106 Breakout board attached to a Breakout Garden HAT

# Required modules
from luma.core.interface.serial import i2c, spi
from luma.core.render import canvas
from luma.oled.device import ssd1306, ssd1309, ssd1325, ssd1331, sh1106

# Instantiate a luma SH1106 object
oled = sh1106(i2c(port=1, address=0x3C), rotate=2, height=128, width=128)

# Create a suitable canvas to draw on
with canvas(oled) as draw:
    draw.rectangle(oled.bounding_box, outline="white", fill="black")
    draw.text((30, 40), "Hello SH1106!", fill="white")

print("There should be something on the display now")

References



Securing your Raspberry Pi (Raspbian)

Linux,by design, is generally more secure than say, Windows.


There are many reasons, some historical, some design (including historical).

Of course this is no excuse for not taking precautions. Inside your own network, you should take care, but the Raspberry Pi is very portable, so it might be outside, facing the rampaging hordes on the Internet.

I have a Raspberry Pi 3 that I take on holiday with me, it is used as an entertainment centre (most hotels have HDMI compatible television) so I can listen to podcasts or watch streamed television (see also get_iplayer).

However, this does require a connection to the outside, and you are never sure what is on the other end of the WIFI connection.

For this you need to have and control a Firewall.

Linux has a number of built in firewall capabilities, but they are complicated to control out of the box.

This is where Uncomplicated Firewall comes in handy.

Uncomplicated Firewall

Install Uncomplicated Firewall (UFW).

Uncomplicated Firewall (UFW) is included in most Linux  repositories, so indtallation is pretty easy.

$ sudo apt-get install ufw

Allow SSH through (if required - be careful if you are working on a 'headless' raspberry Pi)
$ sudo ufw allow ssh

Check the rules

A command has been added that allows the rules to be checked even when the UFW is not enabled.

$ sudo ufw show added

Added user rules (see 'ufw status' for running firewall):

ufw allow 22/tcp

Enabling UFW

Before enabling UFW, if you are accessing the machine remotely, ensure that you a) have set a rule allowing an SSH connection, and b) just in case, that you can directly access the machine (have access to the machine and have a keyboard and display to hand).

$ sudo ufw enable

Show status

$ sudo ufw status
Status: active
To                         Action      From
22/tcp                     ALLOW       Anywhere                  
22/tcp (v6)                ALLOW       Anywhere (v6)

Disable UFW

$ sudo ufw disable

Allowing other ports

There are a large number of pre-defined port combinations available on UFW. These can be listed using:
sudo ufw app list

Conclusion

This does not make your Raspberry Pi secure, there are a number of additional actions it is worth taking, and other products are available, but you can at least start to protect your machine and the data on it.

References

https://www.raspberrypi.org/documentation/configuration/security.md
https://wiki.debian.org/Uncomplicated%20Firewall%20%28ufw%29
https://wiki.ubuntu.com/UncomplicatedFirewall
http://manpages.ubuntu.com/manpages/cosmic/en/man8/ufw.8.html


Saturday, 21 March 2020

Windows Script Host and JScript Revisited

Background

This is a small example Windows Script File (WSF) that can be used in certain work flows.

This work flow involves dealing with files that are created in the In folder.

Due to some issue with a process upstream in this work flow, some of the files that appear in the In folder are missing their file suffix. For reasons outside the scope of this article, this cannot be rectified upstream, and, because it is known that these files all should have a .txt suffix, this script appends the required suffix where there is no suffix.

The script is supplied with an In and an Out folder path.

The Code

<job>
    <script language="JScript">
        // Technology Is Not Dull 2020
        // Get in and out folders as named arguments
        var InFolder = "";
        var OutFolder = "";
        if(WScript.Arguments.Named.Exists("in")){
            InFolder = WScript.Arguments.Named("in");
        }
        if(WScript.Arguments.Named.Exists("Out")){
            OutFolder = WScript.Arguments.Named("Out");
        }
        // If there is an in and an out parameter
        if(OutFolder!="" && InFolder!=""){
            // Create objects to get the files and copy them
            var objFSO=WScript.CreateObject("Scripting.FileSystemObject");
            var objFolder = objFSO.GetFolder(InFolder);
            var colFiles =objFolder.Files;
            // Remove any trailing '\' in supplied Out path
            OutFolder = objFSO.GetFolder(OutFolder);
            // For each file in the In folder
            for(var objEnum = new Enumerator(colFiles); !objEnum.atEnd(); objEnum.moveNext()) {
                sFileName = objEnum.item();
                sOutName = OutFolder +"\\";
                // This assumes that any file with no file suffix
                // is actually a .txt file and adds the suffix for the copied filename
                if(objFSO.GetExtensionName(sFileName) == "")
                {
                    sOutName = sOutName + objFSO.GetFileName(sFileName) + ".txt";
                }
                else
                {
                    sOutName = sOutName + objFSO.GetFileName(sFileName);
                }
                // Copy the file (overwriting any existing file)
                objFSO.CopyFile (sFileName,sOutName,true);
            }
            // Quit with value 0
            WSH.Echo("Done");
            WSH.Quit(0);
        }
        // Else there are one or more missing parameters
        //so quit with error
        else
        {
            WSH.Echo("Missing Named Parameter(s)");
            WSH.Quit(1);
        }
    </script>
</job>

Operation

The script is tested with a folder structure based on "C:\Some Folder\Documents\Javascript\test folders" with two sub folders 1 and 2.

Folder 1
Folder 2

The parameters are named using /<name>:<value>.

C:\Some Folder\Documents>cscript JScriptExample.wsf /In:"C:\Some Folder\Documents\Javascript\test folders\1" /Out:"C:\Some Folder\Documents\Javascript\test folders\2\"
Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.

Done

C:\Some Folder\Documents>

As you can see the files have been copied with the suffix added to the file without the suffix.

References

A brief look at JavaScript on various systems including Windows Script Host can be found here.

Wednesday, 19 February 2020

Simple Bar Code Reader using a Raspberry Pi

Reading Bar Codes with the Raspberry Pi

Installation 

Install OpenCV
sudo apt get install python-opencv libopencv-dev 
You might find it is already installed.

Install Python bindings 

pip3 install opencv-python 

Install additional libraries 

sudo apt-get install libcblas-dev 
sudo apt-get install libhdf5-dev -y
 && sudo apt-get install libhdf5-serial-dev -y 
 && sudo apt-get install libatlas-base-dev -y
 && sudo apt-get install libjasper-dev -y
 && sudo apt-get install libqtgui4 -y
 && sudo apt-get install libqt4-test -y 

Install ZBAR and PYZBAR 

According to one of the references, zbar does not have a python3 version, however the pyzbar works with Python3.
sudo apt-get install libzbar0 
pip3 install pyzbar 
Remember to use pip3 not pip to install pyzbar for Python3 (your installation may vary - pip defaults to Python2 in most installations).

Test program 

from pyzbar import pyzbar
import cv2

imagefile="/home/pi/Documents/Python/barcodetest.jpg"

print(imagefile)
# load the input image
image = cv2.imread(imagefile)
# find the barcodes in the image and decode each of the barcodes
barcodes = pyzbar.decode(image)

# loop over the detected barcodes
for barcode in barcodes:
    # extract the bounding box location of the barcode and draw the
    # bounding box surrounding the barcode on the image
    (x, y, w, h) = barcode.rect
    cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
    # the barcode data is a bytes object so if we want to draw it on
    # our output image we need to convert it to a string first
    barcodeData = barcode.data.decode("utf-8")
    barcodeType = barcode.type
    # draw the barcode data and barcode type on the image
    text = "{} ({})".format(barcodeData, barcodeType)
    cv2.putText(image, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX,
        0.5, (0, 0, 255), 2)
    # print the barcode type and data to the terminal
    print("[INFO] Found {} barcode: {}".format(barcodeType, barcodeData))

# show the output image
cv2.imshow("Image", image)
cv2.waitKey(0)

Troubleshooting

There may be issues with the version of numpy installed by the above.
If you getr an import error, remove the installed version of numpy and replace it with one from the Raspbian repository.

pip3 uninstall numpy  # remove previously installed version
apt install python3-numpy
https://numpy.org/devdocs/user/troubleshooting-importerror.html

References 

https://www.pyimagesearch.com/2018/05/21/an-opencv-barcode-and-qr-code-scanner-with-zbar/
https://www.learnopencv.com/barcode-and-qr-code-scanner-using-zbar-and-opencv/
https://www.raspberrypi.org/forums/viewtopic.php?p=848265 
https://stackoverflow.com/questions/19876079/cannot-find-module-cv2-when-using-opencv