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)
    







Saturday 27 March 2021

Developing Linux programs in Visual Studio C++

 Developing Linux programs in Visual Studio C++

Though Raspberry PIs (and other Linux based machines) are well supported with development environments, the familiar Windows based Visual Studio does make it easier to develop applications, especially where the target machine is not particularly powerful.

The following uses the free Visual Studio Microsoft Visual Studio Community 2019 Version 16.9.2.

Set Up

Start the Visual Studio Installer from the Start button or by searching.

 


Update Visual Studio.

Once it has been updated, click on the Modify button (not quite as obvious as it could be).

Select the Linux C++ option (you need to scroll down a bit).


Click on the Modify button (bottom right).

Set up Linux machine

sudo apt-get install openssh-server g++ gdb make ninja-build rsync zip

Start SSH on the Linux machine (if not already started).

sudo service ssh start

First project

Start Visual Studio. Click File/New Project.

Search for Linux templates.

Start with a Console Application, suitable for any Linux machine.

This provides a bare bones “Hello World” C++ project.


 

Click on Build/Rebuild Project.

Visual Studio will ask for the Linux machines hostname,  username and password. The username and password should be a login on the Linux machine. Ensure that the Linux machine is configured to allow SSH through the firewall (see Simple Firewall oSimple Firewall r the Raspberry Pi Config program).

On the Linux machine you will find a folder called projects in the user folder. See below. 

Open a terminal on the Linux machine and change directory to this location.

To run the program enter.

./"Linux Console Application.out"

References

https://docs.microsoft.com/en-us/cpp/linux/download-install-and-setup-the-linux-development-workload?view=msvc-160

https://docs.microsoft.com/en-us/cpp/linux/create-a-new-linux-project?view=msvc-160


Saturday 6 March 2021

Raspberry Pi COBOL

COBOL

Introduction

COBOL is one of the oldest computer languages, having been created in 1959. COBOL is an acronym – “COmmon Business Oriented Language”.

The language grew out of a US Department of Defence requirement for a portable programming language for data processing.

Installation

sudo apt-get install open-cobol

First program

Use the Geany editor (Visual Studio Code might work as well)

       Identification Division.

       Program-ID. HelloWorld.

       Data Division.

       Procedure Division.

       Main-Paragraph.

       Display "Hello World"

       Stop Run.



Open a console window and change the directory to the location of the file.

Compile with

cobc -x -o hello HelloWorld.cbl


References

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

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

http://opencobol.add1tocobol.com/gnucobol/

http://rosettacode.org/wiki/Category:COBOL

https://gnucobol.sourceforge.io/doc/gnucobol.html

 

Friday 5 February 2021

BBC Microbit Version two

 So while I was ordering the Raspberry Pi Pico, I also ordered  the recently released BBC Microbit V2 from Pimoroni.

Here is a comparison shot of the V1 (top) and V2 (bottom).

On this side, the most obvious change are the crenellations on the connectors.They make it easier to use crocodile clips, preventing them slipping onto the adjacent connectors. The touch sensor is under the Microbit logo.

Comparison of three different versions of the Microbit. The version 1.5 has an integrated accelerometer/magnetometer. The speaker on the V2 version is the most obvious change.



Raspberry Pi Pico part two

 So I ordered a Raspberry Pi Pico from Pimoroni (before picking one up on the cover of Hackspace).

It came in its own little box from a reel.

Here are the two Picos.

And a comparison with the Rapspberry Pi Zero.

And if you want to program it in Micropython, Raspberry Pi Foundation has a book. It is available as a physical book or as a free PDF.


Sunday 24 January 2021

Raspberry Pi Pico Microcontroller

 Raspberry Pi have just released a new product into a new market for them a low cost microcontroller. And not just a microcontroller using an existing piece of silicon, no, this is a in-house custom designed processor.

Why a microcontroller?

General purpose computers like the Raspberry Pi are great at doing lots of things, but that comes at a price. They can do lots of things that appear to humans  to be happening at the same time, but it is like juggling, if you are late getting to one of the items in the air, one or more of the items is going to come crashing down. By dedicating a microcontroller to the task, you can ensure the timely response.

Also, because microcontrollers do not have to contain all the clever hardware to handle lots of memory, task swapping and other things required for general purpose computers they are both economical in cost and energy requirements.

There are plenty of microcontrollers about such as the BBC Microbit and Arduino (and compatible), but most are built using an existing System On A Chip.

Raspberry Pi decided to build a dedicated microcontroller chip based on their experiences with the Sense Hat and the all in one Raspberry Pi 400.

What does Raspberry Pi Pico look like?


As you can see, it is much smaller than even the Raspberry Pi Zero (a WH example above). 

Out of the box it has only limited built in semsors and outputs, this is not a Circuit Playground Express.

Basically it has a green LED on GPIO pin 25 and a chip temperature sensor.

What is it for?

The Raspberry Pi Pico can be used where other microcontrollers would be used. It lacks the existing Arduino Shield eco-system, so generally where it is the basic I/O that is required.

It is small, and frugal with the power so it can run independently on two or three AA batteries.

How do you get one?

They are available from the usual suspects, I have one on order from Pimoroni.
In addition, they are on the cover of issue 39 of the print version of the Hackspace magazine.


What can you program it in?

At the moment there is a full C/C++ SDK and ports of Micropython and CircuitPython.
There is currently no Arduino implementation, but there will be an official RP2040 based Arduino device so I suspect that will not be a long wait.

Raspberry Pi RP2040

There is a full description of the RP2040 here, but these are the highlights:

  • In-house design using dual core ARM Cortex M0+
  • 264KB RAM
  • Upto 16MB of external Flash memory via a QSPI bus.
  • 30 GPIO pins (four owhich can be configured as analogue input)
  • Two each of UART, SPI and I2C controllers
  • 16 PWM channels
  • USB host and device support plus mass-storage boot mode for drag and drop programming
  • Eight Programmable I/O state machines

The eight PIO state machines are a particular innovation - they are programmable in a simple assembly language to perform tasks at set rates. Each instruction takes one cycle and is independent of the two main cores. This allows you to set up time sensitive operations at known speeds, irrespective of what the main processor is doing. Most microcontrollers would require bit-banging, using the processor to transmit or receive data by changing/reading the state of an input. The PIO allows this to be offloaded to a PIO with full control of the process. All the processor has to do is ensure that it is kept fed or emptied in time.

The chip is called an RP204 based on a naming system:

Cores: 2

CPU type: 0 ~ M0 - this is a loose description of the CPU type.

RAM: 4  = floor(log2(ram / 16k))

Flash: 0 = floor(log2(nonvolatile / 16k)) or zero in this case (the host board provides the Flash storage).

The Future

There are a range of products from Pimoroni, Adafruit, SparkFun and Arduino planned to use the RP2040, so we shall see.

References



Saturday 2 January 2021

Reading RSS feeds using Python

RSS Feeds

RSS stands for Really Simple Syndication and is a method of supplying information generally to be read by computers. An RSS feed is a web page and contains a number of entries, each entry commonly containing: title, link, description, publication date, and entry ID.

If you Google reading RSS in Python, you will often be directed to the feedparser library. This handles a lot of the background work involved in using an RSS feed and has a lot of protection against some of the potential pitfalls of reading what is effectively XML from potentially unknown sources. This includes only reading elements from a whitelist of trusted elements.

Unfortunately not all RSS feeds use elements on that whitelist.

One of those is the BBC RSS feeds. This is a problem in that I wanted to download the free BBC podcasts automatically.

So it was back to basics, the Requests library to access the RSS feed and download the files, and the defusedxml library to manipulate the RSS file as XML.

RSS files contain publication dates, so if you record the date of the last file you have downloaded, you can then use that date to only download new files when you retry the feed.

Installation

Remember to check that the version of pip is for Python 3.x (if not then replace pip with pip3)

Requests

pip install requests

Defusedxml

pip install defusedxml

Dateutil

This is required for date reading and comparison
pip install python-dateutil

Design

There are four main elements of the program:
  • Read the RSS file
  • Iterate through the new files
  • Download the podcasts
  • Update the last file date

Code

import defusedxml.ElementTree as ET
from pathlib import Path
import requests
from dateutil.parser import parse
import os

# Save the file refereenced by the URL to the supplied filepath
def saveMP3(url, filepath):
    print(filepath)
    r = requests.get(url)
    with open(filepath, 'wb') as f:
        f.write(r.content)

# Get all the files from the supplied RSS url that are newer than the 
# last file date stored in the matching file
def getFiles(url, target_folder, prefix):
    # Build the Namespace for the XML searching
    ns = {'media': "http://search.yahoo.com/mrss/"}

    # Create target folder if required
    if not Path(target_folder).is_dir():
        os.makedirs(target_folder)

    # Get response from URL
    r = requests.get(rss_url)
    print(r.status_code)

    # Make XML document from response text
    root = ET.fromstring(r.text)

    # Get the channel title
    channel_title = root.find("channel/title").text.replace(": ", "-")

    # Build the filename for the last downloaded file date
    channel_file = channel_title + "_lastdate.txt"
    print(channel_file)
    
    last_file_date = ""
    # Set up a default date if there is no file
    last_date = parse("2000-01-01 00:00:00 +00:00")

    # If there is a file, obtain the date from the file
    if Path(channel_file).is_file():
        f = open(channel_file, "r")
        last_file_date = f.read()
        last_date = parse(last_file_date)
        f.close()
    print(last_date)

    # Find the item elements from the XML
    items = root.findall("channel/item")

    print(channel_title + " files:" + str(len(items)))
    download_count = 0
    first_file_downloaded_date = ""

    # For each item found
    for y in items:
        # Obtain the information required
        title = y.find("title")
        link = y.find("media:content", ns)
        pub_date = y.find("pubDate")
        # Create the published date as datetime
        datetime_object = parse(pub_date.text)
        print(title.text)
        print(datetime_object)
        if last_file_date == "":              # Download if no previous date
            saveMP3(link.attrib['url'], target_folder+prefix+datetime_object.strftime("%Y%m%d%H%M%S") + ".mp3")
            download_count = download_count+1
        elif last_date < datetime_object:     # Download if newer than the last run
            saveMP3(link.attrib['url'], target_folder+prefix+datetime_object.strftime("%Y%m%d%H%M%S") + ".mp3")
            download_count = download_count+1
        else:                               # Otherwise exit loop
            print("Not downloading")
            break
        # Record the filedate of the first (most recent) file
        if first_file_downloaded_date == "":
            first_file_downloaded_date = pub_date.text
    
    # Print how many files and the most recent file date
    print("Downloaded " + str(download_count))
    print(first_file_downloaded_date)
    
    # Write the most recent file date to the file
    if first_file_downloaded_date != "":
        f = open(channel_file, "w")
        f.write(first_file_downloaded_date)
        f.close()

# If this is the main call
if __name__ == "__main__":
    rss_url = 'https://podcasts.files.bbci.co.uk/p02nrss1.rss'
    getFiles(rss_url, "/home/pi/Music/moreorless/","mol")

References