NextCloudPi on Raspberry Pi 5

I finally took an evening to get NextCloudPi installed on a Raspberry Pi 5 with a large-ish NVMe drive. This was not a smooth ride. For your pleasure, this is how I got it working.

First, use Jeff Geerling’s guide to get the Pi booting from the NVMe drive.

Second, use this guide to move from Debian networking to systemd-networkd, but do not hold the avahi-daemon package.

Third, run the NextCloudPi curl install script.

Next up – the migration from my old instance. I have 1.5TB of files on a spin disk connected via USB that I need to move to the new NVMe storage – but that is for another night.

For the record – I do love NextCloud and NextCloudPi, so no finger pointing here, just sharing some frustration and how I got around the issue.

Sharing dimensions

Continuing my adventures in FreeCAD, I now had to design a top to my Raspberry Pi enclosure. The FreeCAD people were friendly and told me to checkout spreadsheets.

However, since I already started down another path, I decided to name my constraints. Something I picked up from a random YouTube tutorial I cannot seem to find again.

Having done so, I can then refer to the name in a formula and thus, reuse the old value.

Now, let’s see how the lid will fit. :-)

FreeCAD-ing

I just got myself a 3D printer. It is my second one – I built a reprap back in 2013 – but this one is slightly more convenient to use. I went for the Creality CR6-SE. It works good enough for me out of the box, even though 3D printing is a topic where you can spend infinite time tweaking things.

I used to use OpenSCAD to create my models, as it maps well to my way of thinking (i.e. coding). However, I’ve decided to take some time to learn some new skills and this time around I went for FreeCAD.

FreeCAD is a free, as in freedom, parametric CAD program with a vibrant community. I’m new to CAD – parametric or not – so this is a learning experience for me.

I decided to create a case for my Raspberry Pi and PiTFT based home automation control panel shown in the video below.

To make this happen I first drew the important parts of the Raspberry Pi and the PiTFT hat, to be able to check that the case actually fits the contents. Then I dove into the creation of an enclosure.

The process of sketching, padding, rinse, and repeat is easy and somewhat tedious. I bet there are lots of ways to optimize this. E.g. by parametrize some dimensions to avoid having to “hardcode” them into each sketch. I guess smarter people can share some reference points between sketches too, e.g. to align the holes in the Raspberry Pi to the pegs and peg feet. I’ll learn this the next time around.

Having designed a bottom half, I decided to test fit it, so time to slice it up and then print it. Yes, I use the slicer that came with the printer, as I don’t want to tinker with the printer, but rather with the things I print.

Slicing the bottom of the case

Below, you can see the end result from the first test fitting. Hopefully the picture below shows something that fits as I bravely wrote this while printing. If not, then I really learned something, because I made a mistake.

Update! Turns out I made a 1mm mistake towards the bottom. The two HDMI connectors and the audio port project out more than I expected. Apart from that it sort of works. Now I’m printing v2 with 0.5mm thicker walls and the necessary adjustments to fit the ports.

Upgrading NextCloudPi

So I finally got around to upgrading my NextCloudPi to version 20 with the hub and all. I really like it so far.

It also seems that I bumped into a know issue. All of a sudden I could not upload files over 8kB. It turns out that for some reasons the sys_temp_dir in /etc/php/7.3/php.ini had shifted from my USB media (a portable HDD) to the default location (/var/www/nextcloud/tmp) during the upgrade. After a quick move back to /media/USBDrive/data/tmp, everything is back to normal.

I found the solution over at the nextcloud forums.

Photoframe Hack

Sometimes you just want to get something done. Something for yourself.

You do not intend it to be reused, or even pretty.

You build a tool.

My tool was a photoframe with some basic overlays. I wanted the family calendar, some weather information (current temperature + forecast), time, and the next bus heading for the train station.

To make this acceptable in a home environment, I built it as a photoframe. You can find the sources in the hassframe-ui repository on my github.

A hidden feature is that if you tap the screen, a home automation control panel slides up. That way you can control all the lights, as well as heat in the garage and an AC in the bedroom. Very convenient.

All this is built using QML. Three somewhat useful models are available:

  • IcalModel, taking a URL and parsing whatever it gets back as ICAL data. It is a very naive parser and does not care about things such as time zones and other details.
  • YrWeatherModel, uses yr.no‘s public APIs to pull out a weather forecast for a given location.
  • ButStopModel, uses the APIs from resrobot to look for departures to the train station from two bus stops close to my home and then merge the results into a model.

I also have a bunch of REST calls to my local home assistant server. Most of these reside in the HassButton class, but I also get the current temperature from there. These are hardcoded for my local network, so needs refactoring to be used outside of my LAN.

All of these interfaces require API keys of one kind or another – be it a proper key, or a secret URL. These are pulled from environment variables in main.cpp and then exposed to QML. That way, you can reuse the components without having to share your secrets.

All in all the code is quite hacky. Especially main.qml. I refactor out parts from there now and then, but the photoframe works, so its not anything that I prioritize.

Currently it runs on a Raspberry Pi on top of Raspbian. I want to build an optimized Yocto image making it less hacky and more pre-packaged. Perhaps there will be a rainy day this summer and I’ll get around to it. Burkhard has prepared the instructions needed over at embedded use.

NextCloud on Pi Adventures

I spent yesterday *finally* setting up a NextCloud instance of my own. It’s been on my todo since I installed fiber at home and got a decent Internet connection.

I started out with Rasbian Lite and combined it with the NextCloudPi install script from ownyourbits. I then used certbot to install certificates from let’s encrypt before migrating the data directory using these instructions.

After that it was happy account creation time, before realizing that I could not upload files larger than ~10kB. Very annoying.

After having duckduck-ed and browser issues and articles for hours, I finally found that that the /etc/php/7.3/fpm/php.ini file contained a reference to the data directory.

sys_temp_dir = /new/path/to/data/tmp

This one-liner cost me about four hours to find, so hopefully this post saves someone else that time.

QML Weather

I recently took some time to develop a photo frame style home automation control panel. The idea is to control some common tasks of my home assistant setup from a panel instead of having to rely on the phone. To hide the panel, it currently act as a photo frame until touched.

The build is based on a Raspberry Pi 2 with the official touch screen attached and a USB wifi dongle. Nothing fancy, but still good enough.

One of the features that I wanted was a weather forecast, so I decided to use Yr’s xml weather as a base for this. The result is the YrWeatherModel QML item.

The weather forecast overlay.

The presentation side of things is the fairly straight forward piece of QML shown below, resulting in the overlay shown above.

Row {
    anchors.bottom: dateText.bottom
    anchors.right: parent.right
    anchors.rightMargin: 40

    spacing: 20
    Repeater {
        delegate: Column {
            spacing: 2
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                color: "white"
                font.pixelSize: 16
                font.bold: true
                text: {
                    switch (period) {
                    case 0:
                        "00 - 06"
                        break;
                    case 1:
                        "06 - 12"
                        break;
                    case 2:
                        "12 - 18"
                        break;
                    case 3:
                    default:
                        "18 - 00"
                        break;
                    }
                }
            }
            Image {
                anchors.horizontalCenter: parent.horizontalCenter
                source: symbolSource
            }
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                color: "white"
                font.pixelSize: 16
                font.bold: true
                text: precipitation + "mm"
            }
            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                color: "white"
                font.pixelSize: 16
                font.bold: true
                text: temperature + "°C"
            }
        }

        model: weatherModel.model
    }
}

This is followed by the model itself, and a small notice of the data source.

YrWeatherModel {
    id: weatherModel
    place: "Sweden/V%C3%A4stra_G%C3%B6taland/Alings%C3%A5s"
}

Text {
    anchors.bottom: parent.bottom
    anchors.right: parent.right
    anchors.bottomMargin: 5
    anchors.rightMargin: 40
    text: weatherModel.dataSourceNotice
    color: "white"
    font.pixelSize: 16
    font.italic: true
}

Diving into the model itself, we hit the interesting parts. The structure looks like this:

Item {
    id: root

    property alias model: weatherModel
    property int refreshHour: 1     // How often is the model refreshed (in hours)
    property int dataPoints: 6      // How many data points (max) are expected (in 6h periods)
    property string place           // Place, URL encoded and according to Yr web site, e.g. Sweden/V%C3%A4stra_G%C3%B6taland/Alings%C3%A5s
    readonly property string dataSourceNotice: "Data from MET Norway"

    ListModel {
        id: weatherModel
    }

    Timer {
        interval: 3600000 * root.refreshHour
        running: true
        repeat: true
        onTriggered: {
            _innerModel.reload();
        }
    }

    XmlListModel {
        id: _innerModel

        query: "/weatherdata/forecast/tabular/time"

        source: (place.length === 0)?"":("https://www.yr.no/place/" + root.place + "/forecast.xml")

        XmlRole { name: "period"; query: "string(@period)" }
        XmlRole { name: "symbol"; query: "symbol/string(@number)"; }
        XmlRole { name: "temperature"; query: "temperature/string(@value)"; }
        XmlRole { name: "precipitation"; query: "precipitation/string(@value)"; }

        onStatusChanged: {
            // ...
        }
    }
}

As you can see, the model consists of an inner model of the type XmlListModel. This model is refreshed by a timer (don’t refresh too often – you will most likely be auto-banned by Yr). At the top, there is also a ListModel that is the actual model used by the user interface.

The reason for the ListModel to exist is that I wanted to be able to limit how many data points I show. Each data point represents a six hour window, and I’d like 6 of them, i.e. one and a half day of forecasting.

The onStatusChanged handler in the XmlListModel takes care of this in the following for loop:

onStatusChanged: {
    if (status === XmlListModel.Ready)
    {
        for(var i = 0; i< root.dataPoints && i < count; ++i)
        {
            var symbol = get(i).symbol;
            var period = parseInt(get(i).period);
            var is_night = 0;

            if (period === 3 || period === 0)
                is_night = 1;

            weatherModel.set(i, {
               "period":period,
               "symbol":symbol,
               "symbolSource":"https://api.met.no/weatherapi/weathericon/1.1/?symbol=" + symbol + "&is_night=" + is_night + "&content_type=image/png",
                "temperature":get(i).temperature,
                "precipitation":get(i).precipitation
                });
        }
    }
    else if (status === XmlListModel.Error)
    {
        console.warn("Weather error")
        console.warn(errorString());
    }
}

As you can tell, this code has *very* limited error handling. It is almost as it has been designed to break, but it works. The code also shows how convenient it is to connect to online services via QML to build simple, reusable, models that can be turned into beautiful user interfaces.

Next time I have some free time, I’ll look at interfacing with the public transport APIs. Then I will have to deal with JSON data and make explicit XmlHttpRequest calls.

Five days left

I use to joke that the last week before foss-north is the worst – everything is done, all that is left is the stress.

This year, we have the broadest program yet. 25 speakers talking about everything from community policies, GPU isolation, blockchain, historical KDE software, retro computers, IoT, Android, SailfishOS, bug triaging, crowd funding, software updates, yocto, home automation, design to sub-atomic particles.

You can still get a ticket (and make sure to bring a friend) at foss-north . Welcome!

Vacation 2015

IMG_20150703_172538So, vacation has finally arrived in 2015. To the despair of some, and joy of others, the Swedish standard vacation is 3-5 weeks over the summer. I’ll be having five weeks of this year.

So, what do you do with five weeks? Generally, I start out ambitious and end up in reality after 3-4 weeks and then scramble to get something done. Here is my list for the summer 2015:

  • Hang out with the kids and wife and do fun stuff.
  • Do some work around the house (a shed for our bikes and some general painting are on the wish list).
  • Get the calendar for foss-gbg into place. It does look as if we will have an awesome autumn.
  • Work on a whole bunch of little Yocto projects that I’ve been planning for a while (meta-kf5 being one of the high priority ones, playing with various targets such as the Raspberry Pi 2, Beagle Bone Black and more another).
  • Getting my 3D printer back into shape and do something fun with it (if it rains a lot)

That summarizes my ambition pretty much – but first things first – lets spend a few days hanging out with the kids before they grow tired of me and think that I’m old and boring :-)

Qt 5 Cadaques is Released!

Juergen Bocklage-Ryannel and myself are writing a book! As we’ve reached the point where we want to share our work with the workd, we are happy to open the doors to the book Qt 5 Cadaques – A Book about Qt 5! It all started a couple of years back, when two developers saw a need: there where no books approaching Qt from the QML side. We decided to solve the problem by writing a book.

During these years, lots has happened in the land of Qt. The ecosystem around the Qt Project has stabilized after the whole Microsoft-Nokia thing. Jolla has released a phone with Qt 5, as has Blackberry. Ubuntu Phone will be based on Qt 5. The Raspberry Pi has taken on the whole world with a great hardware that runs great with Qt 5. It is now possible to target both Android and iOS using Qt 5.

Looking at these changes, one can see that Qt really has grown towards mobile. Towards device user interfaces. User interfaces where the look and feel has to be integrated into the context of a physical device. This is where Qt Quick and QML really shines. Before I go on, it is worth pointing out that the Qt ecosystem has grown towards mobile, it has not moved there. The cross platform desktop support is still there and works just as great as it has for almost 20 years now!

For aspiring Qt developers, there is a plethora of documentation for using Qt from C++. There are multiple books on the topic. However, when targetting devices, C++ is only a part of the story. Here, Qt Quick and QML are equally important. That is why we are happy to make Qt 5 Cadaques available!

A final word of warning. The book is being written as you read it. We believe in the open source mantra release early, release often. This means that the text currently is in its early stages. We have setup a feedback system so that you as a reader can help us improve the material by pointing out issues. We are also looking for ways to grow the contents. We also keep a project blog on the site to announce our releases and to ask you about your opinion on the book. We would love for you to tell us what you want to read about!