Category Archives: Code

Set up Docker on a Raspberry Pi

TLDR

curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh

Important note

  • Use a power supply that has enough power. The Rasperry Pi might not work correctly when it has not enough power.

Installation

First download from get.docker.com the installation script using curl. Save the script in the file ‘get-docker.sh‘. Check the script and compare it with the original version on github. Then run the script with ‘sh‘.

curl -fsSL get.docker.com -o get-docker.sh
sh get-docker.sh

The above codes can be joined into a single line. The script is then run immediately after downloading, it cannot be checked. Unfortunately, the ‘&&‘ are not rendered correctly with the SyntaxHighlighter:

curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh

Even shorter, without writing the script file ‘get-docker.sh‘ to the current directory. I would not recommend this version though.

curl -sSL https://get.docker.com/ | sh 

Problems

When installing Docker I somehow encountered problems and the connection to the Raspberry Pi froze. Looking at the Docker info I saw that the server was not running:

pi@raspberrypi:~ $ sudo docker info
Client:
 Debug Mode: false

Server:
ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
errors pretty printing info

After some research I tried to reinstall the docker-engine:

sudo apt-get install -y -q docker-engine

… but received again an error:

E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. 

… so I typed in the suggested command:

sudo dpkg --configure -a

As far as I remember, this command used to freeze previously. After some research I decided to use a more powerfull power supply for the Raspberry Pi. This time, it was installing correctly:

pi@raspberrypi:~ $ sudo dpkg --configure -a
Setting up containerd.io (1.2.10-3) ...
Created symlink /etc/systemd/system/multi-user.target.wants/containerd.service → /lib/systemd/system/containerd.service.
Setting up docker-ce-cli (5:19.03.5~3-0~raspbian-buster) ...
Setting up docker-ce (5:19.03.5~3-0~raspbian-buster) ...
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /lib/systemd/system/docker.service.
Created symlink /etc/systemd/system/sockets.target.wants/docker.socket → /lib/systemd/system/docker.socket.
Processing triggers for man-db (2.8.5-2) ...
Processing triggers for systemd (241-7~deb10u1+rpi1) ...

Check the installation

I checked the installation with docker info. It seems to be alright now:

pi@raspberrypi:~ $ sudo docker info
Client:
 Debug Mode: false

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 19.03.5
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: b34a5c8af56e510852c35414db4c1f4fa6172339
 runc version: 3e425f80a8c931f88e6d94a8c831b9d5aa481657
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 4.19.75-v7+
 Operating System: Raspbian GNU/Linux 10 (buster)
 OSType: linux
 Architecture: armv7l
 CPUs: 4
 Total Memory: 926.1MiB
 Name: raspberrypi
 ID: VMGN:PNRJ:3O7Q:PXSZ:HKHG:EF7R:6LAD:FAIJ:SJMS:GRMU:EF5P:6HNL
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No swap limit support
WARNING: No cpu cfs quota support
WARNING: No cpu cfs period support

Hello world

Now it’s time to run the hello world:

pi@raspberrypi:~ $ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4ee5c797bcd7: Pull complete 
Digest: sha256:d1668a9a1f5b42ed3f46b70b9cb7c88fd8bdc8a2d73509bb0041cf436018fbf5
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm32v7)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

References

Appendix: Curl options

See: https://manpages.debian.org/buster/curl/curl.1.en.html

-f, –fail

(HTTP) Fail silently (no output at all) on server errors. This is mostly done to better enable scripts etc to better deal with failed attempts. In normal cases when an HTTP server fails to deliver a document, it returns an HTML document stating so (which often also describes why and more). This flag will prevent curl from outputting that and return error 22.

This method is not fail-safe and there are occasions where non-successful response codes will slip through, especially when authentication is involved (response codes 401 and 407).

-s, –silent

Silent or quiet mode. Don’t show progress meter or error messages. Makes Curl mute. It will still output the data you ask for, potentially even to the terminal/stdout unless you redirect it.

See also -v, –verbose and –stderr.

-S, –show-error

When used with -s, –silent, it makes curl show an error message if it fails.

-L, –location

(HTTP) If the server reports that the requested page has moved to a different location (indicated with a Location: header and a 3XX response code), this option will make curl redo the request on the new place. If used together with -i, –include or -I, –head, headers from all requested pages will be shown. When authentication is used, curl only sends its credentials to the initial host. If a redirect takes curl to a different host, it won’t be able to intercept the user+password. See also –location-trusted on how to change this. You can limit the amount of redirects to follow by using the –max-redirs option.

When curl follows a redirect and the request is not a plain GET (for example POST or PUT), it will do the following request with a GET if the HTTP response was 301, 302, or 303. If the response code was any other 3xx code, curl will re-send the following request using the same unmodified method.

You can tell curl to not change the non-GET request method to GET after a 30x response by using the dedicated options for that: –post301, –post302 and –post303.

-o, –output <file>

Write output to <file> instead of stdout. If you are using {} or [] to fetch multiple documents, you can use ‘#’ followed by a number in the <file> specifier. That variable will be replaced with the current string for the URL being fetched. Like in:

curl http://{one,two}.example.com -o “file_#1.txt”

or use several variables like:

curl http://{site,host}.host[1-5].com -o “#1_#2”

You may use this option as many times as the number of URLs you have. For example, if you specify two URLs on the same command line, you can use it like this:

curl -o aa example.com -o bb example.net

and the order of the -o options and the URLs doesn’t matter, just that the first -o is for the first URL and so on, so the above command line can also be written as

curl example.com example.net -o aa -o bb

See also the –create-dirs option to create the local directories dynamically. Specifying the output as ‘-‘ (a single dash) will force the output to be done to stdout.

See also -O, –remote-name and –remote-name-all and -J, –remote-header-name.

Connect to a Raspberry Pi from macOS

TLDR

In the macOS Terminal preferences, uncheck ‘Set locale environment variables on startup’ in the ‘Advanced’ tab of the ‘Profiles’ section.

Issue

Weird things happen when connecting with the macOS Terminal to a Raspberry Pi. For example running the command ‘curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh‘ for installing Docker throws errors. The script struggles because Perl has a problem with the localization. So I started digging in the dirt. When running the command ‘locale‘ to check the localization, I encountered an error message containing the phrase ‘Cannot set LC_CTYPE to default locale: No such file or directory‘.

pi@raspberrypi:~ $ locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_GB.UTF-8
LANGUAGE=
LC_CTYPE=UTF-8
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=
pi@raspberrypi:~ $ 

or

pi@raspberrypi:~ $ locale -a
locale: Cannot set LC_CTYPE to default locale: No such file or directory
C
C.UTF-8
de_CH.utf8
en_GB.utf8
POSIX
pi@raspberrypi:~ $ 

Solution

Trying to set localization with ‘sudo raspi-config‘, ‘sudo dpkg-reconfigure locales‘ or by editing ‘/etc/locale.gen‘ and subsequently running ‘sudo locale-gen‘ did not lead to success. Finally, I found that a difference in the locale settings on the client (macOS) and the host Raspberry Pi causes conflicts and the observed issues.

Host (macOS) locale

Naiad:~ rolf$ locale
LANG=
LC_COLLATE="C"
LC_CTYPE="UTF-8"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=

Client (Raspberry Pi) locale

pi@raspberrypi:~ $ locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_GB.UTF-8
LANGUAGE=
LC_CTYPE=UTF-8
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=

Trick

The trick is now to not send the locale over SSH. This may be achieved by removing the ‘SendEnv LANG LC_*‘ in the ‘/etc/ssh/ssh_config‘ file.

An even easier way is to change the preferences of the macOS Terminal. To that end, uncheck ‘Set locale environment variables on startup’ in the ‘Advanced’ tab of the ‘Profiles’ section.

macOS Terminal settings: Uncheck ‘Set locale environment variables on startup’

Client (Raspberry Pi) locale after change

pi@raspberrypi:~ $ locale
LANG=en_GB.UTF-8
LANGUAGE=
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=

References

Set up a Rasperry Pi from scratch without a monitor and keyboard

Note

  • Setting up the Raspberry Pi without a monitor and without keyboard is called a ‘headless’ setup.
  • Using the NOOBS installer seems not to work. Instead flash the card directly with an image.
  • Activate the SSH service by placing a file called ‘ssh’ into the boot partition.
  • The initial start up of the Raspberry Pi may take 20 min.

Procedure

Dowload Raspian Image

Flash the image to the microSDHC card

Activate the SSH service

  • Mount the microSDHC card on the computer used to flash the image to the card
  • Add an additional, empty file called ‘SSH‘ to the boot partition
    • ‘cd’ into the boot partition
      cd /Volumes/boot/
    • Create an empty file named ‘ssh’:
      touch ssh

Set up the Raspberry Pi

  • Insert the microSDHC card into the Raspberry Pi
  • Connect the Rasperry Pi to the network
  • Boot up the Raspberry Pi by connecting it to the power supply
  • Be patient – it may take 20 min for the Raspberry Pi for the initial start up. Wait until the red power led glows steadily and the green activity led stopped flashing.

Find out the IP address of the Raspberry Pi

  • Login to the router. Check the assigned IPs.
  • Scan the IP range
    • On Mac in the Terminal (The && in the code below are not rendered correctly)
for ip in $(seq 1 254);
do ping -c 1 -t2 192.168.10.$ip;
[ $? -eq 0 ] &amp;&amp; echo "192.168.10.$ip UP" || : ; done

Connect with SSH to the Raspberry Pi

  • Find out the IP address of the Raspberry Pi
  • In the Terminal enter
    ssh pi@[IP address]
  • Confirm the message by typing ‘yes’
  • Enter the password ‘raspberry’

Configure with raspi-config

  • After connecting with SSH to the Raspberry Pi, enter:
    sudo raspi-config
  • Advanced Options → Expand Filesystem
    • Check the file system with
      df -h
  • Change User Password

References

Recover deleted files from emptied trash on macOS

Recover deleted files from emptied trash

Sometimes it happens that you need to recover files that were in the trash before you emptied it. Unfortunately, simply opening the trash and then Time Machine does not work. Time Machine just shows you your home directory. Fortunately, there is a workaround and it is still possible to retrieve the files.

Display hidden files in the Finder

Open the terminal and enter:

defaults write com.apple.finder AppleShowAllFiles TRUE;killall Finder

Retrieve the files

  • Navigate to ~/.Trash in the Finder
  • Open TimeMachine
  • Locate the files and retrieve them

Reset the Finder settings for showing all files

Enter this code in the terminal:

defaults write com.apple.finder AppleShowAllFiles FALSE;killall Finder

Reference

https://forums.macrumors.com/threads/recovering-files-in-trash-with-time-machine.1808864/
https://www.cnet.com/news/how-to-recover-items-in-the-os-x-trash-using-time-machine/

F# DynamicOracleDataReader

Dynamic reading from an Oracle database

Implementation

let inline tryUnbox<'a> (x:obj) =
    match x with
    | :? 'a as result -> Some(result)
    | _ -> None

type DynamicOracleDataReader(reader:OracleDataReader) =
    member private x.Reader = reader
    member x.Read() = reader.Read()
    static member (?) (dr:DynamicOracleDataReader, name:string) : 'a option = 
        tryUnbox (dr.Reader.[name])
    interface IDisposable with
        member x.Dispose() = reader.Dispose()

type DynamicOracleCommand(cmd:OracleCommand) =
    member private x.Command = cmd
    static member (?<-) (cmd:DynamicOracleCommand, name:string, value) = 
        let p = new OracleParameter(name, box value) 
        cmd.Command.Parameters.Add(p) |> ignore
    member x.ExecuteNonQuery() = cmd.ExecuteNonQuery()
    member x.ExecuteReader() = new DynamicOracleDataReader(cmd.ExecuteReader())
    member x.ExecuteScalar() = cmd.ExecuteScalar()
    member x.Parameters = cmd.Parameters
    interface IDisposable with
        member x.Dispose() = cmd.Dispose()

type DynamicOracleConnection(connStr:string) =
    let conn = new OracleConnection(connStr)
    member private x.Connection = conn
//    static member (?) (conn:DynamicOracleConnection, name) =
//        let command = new OracleCommand(name, conn.Connection)
//        command.CommandType <- CommandType.Text 
//        new DynamicOracleCommand(command)
    member x.GenerateCommand(query:string) = 
        let command = new OracleCommand(query, conn)
        command.BindByName <- true
        new DynamicOracleCommand(command)
    member x.Open() = conn.Open()

    interface IDisposable with
        member x.Dispose() = conn.Dispose()

Usage

type Order = {
    OrderId: int
    Comments: string option}

let connectionString = 
    "User Id = XXXXXX; " + 
    "Password = XXXXXX; " + 
    "Data Source = " + 
        "(Description = " +
        "(Address = " + 
        "(Protocol = TCP) " + 
        "(Host = 127.0.0.1) " + 
        "(Port = 1521)) " + 
        "(Connect_Data = (Service_Name = XXXXXX)))"

let getOrderByIdQuery =
    "select * " +
    "from ORDERS " +
    "where IDORDER = : orderId " +
    "order by IDORDER"

let getOrderById (orderId: int) = seq {
    use conn = new DynamicOracleConnection(connectionString)
    use cmd = conn.GenerateCommand(getOrderByIdQuery)
    
    cmd?orderId <- orderId

    conn.Open()
    use reader = cmd.ExecuteReader ()

    while (reader.Read()) do
        yield {
            OrderId = defaultArg reader?IDORDER 0
            Comments = reader?COMMENTS}}

References

F# applications with icons

Icons of an F# WPF application

There are at least two different usages for icons. First, there is the application icon displayd on the top left corner of the application window and also in the task bar. Second, Windows Explorer shows application icons in the file lists and also uses them for shortcuts.

Application icon

  1. Create the icon. For example, save an image with Paint as a 256 color bitmap and change the file extension to *.ico
  2. Save the icon in an “Resources” subfolder of the project
  3. Set the Build Action of the icon to Resource
  4. Include the icon with the XAML file
    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            Icon="/Resources/MyIcon.ico" 
            Title="MyApplication" Height="600" Width="1000" >

Windows Explorer icon

  1. Save the icon in 3.00 format using the free tool @icon sushi.
  2. Create a text file with a *.rc extension. Save the *.rc file in the “Resources” subfolder.
  3. The *.rc file should contain a single line. The ‘1’ is part of the text and not a line number.
    1 ICON "MyIcon.ico"
  4. Ensure that the Resource Compiler rc.exe is installed. Adding the C++ Tools to Visual Studio should also install rc.exe.
  5. Compile the *.rc file with rc.exe into an *.res file.
  6. C:\Program Files (x86)\Windows Kits\10\bin\x86>rc.exe /v C:\pathToMyFile\Resources\MyResources.rc
  7. In Visual Studio, select the Application tab of project property page
  8. Select the compiled *.res file in the Resources section. The complete absolute path to the file is shown. Leave the tick box “Use standard resource names” unchecked.
  9. With a text editor edit the project *.fsproj file. Change the absolut path to a relative path.
    Visual Studio should be closed.
    <Win32Resource>Resources\MyResources.res</Win32Resource>

References

F# and Excel

Add a reference to the solution:
Project menu > Add Reference. On the COM tab, locate Microsoft Excel 16.0 Object Library. That’s the reference which contains Microsoft.Office.Interop.Excel.dll.
Also, add a reference to the office library Microsoft Office 16.0 Object Library.

#if INTERACTIVE
#r "Microsoft.Office.Interop.Excel"
#endif

open System
open Microsoft.Office.Interop.Excel

let excel = new ApplicationClass(Visible = false)
let workbook = excel.Workbooks.Open (templateFile)
//let workbook = excel.Workbooks.Add (xlWBATemplate.xlWBATWorksheet)
let worksheet = (workbook.Worksheets.[1] :?> Worksheet)

excel.Visible <- true
excel.Application.ScreenUpdating <- false

worksheet.Range("A9","E9").Value2 <- [| "This"; "is"; "my"; "test"; ":-)" |]

workbook.SaveAs(outputFile)

workbook.Close()
excel.Workbooks.Close()
excel.Application.Quit()

releaseObject excel

VBA 96 well plates positions

Function IsValidPosition(position As Integer) As Boolean
    If 1 <= position And position <= 96 Then
        IsValidPosition = True
    Else
        IsValidPosition = False
    End If
End Function


Function GetCoordinateFromVerticalPosition _
            (verticalPosition As Integer) As String
    Dim row As Integer
    Dim rowString As String
    Dim column As Integer
    Dim returnValue As String
        
    rowString = "A"
    
    If IsValidPosition(verticalPosition) Then
        row = (verticalPosition - 1) Mod 8 + 1
        rowString = Chr(Asc(rowString) + row - 1)
        column = (verticalPosition - row) / 8 + 1
        returnValue = rowString & column
    Else
        MsgBox "Invalid position number." & _
                vbNewLine & vbNewLine & _
                "Allowed values are 1-96.", _
                vbCritical, "GetCoordinateFromVerticalPosition: Invalid input"
        returnValue = vbNullString
    End If
    GetCoordinateFromVerticalPosition = returnValue
End Function

F# split text into sections

type Section (lines:string list) =
    member x.Lines = lines

/// Splits a sequence after the last line of a section defined by the predicate.
let splitAtEach (predicate: 'T -> bool) (xs : seq<'T>) : seq<'T list> =
    let section = new ResizeArray<'T>()
    seq {
        for x in xs do
            section.Add x
            if predicate x then 
                yield Seq.toList section
                section.Clear()
        if section.Count > 0 then
            yield Seq.toList section}

/// Splits a sequence before the first line of a section defined by the predicate.
let splitBeforeEach (predicate: 'T -> bool) (xs : seq<'T>) : seq<'T list> =
    let section = new ResizeArray<'T>()
    seq {
        for x in xs do
            if predicate x then 
                yield Seq.toList section
                section.Clear()
            section.Add x
        if section.Count > 0 then
            yield Seq.toList section}

usage:

open System.IO

let lines = File.ReadAllLines(myTextFile)

let getSections lines =
    splitAtEach isSectionLine lines
    |> Seq.map (fun x -> new Section(x))
    |> Seq.toList

This is a slightly modified version from: https://codereview.stackexchange.com/questions/55554/parsing-sections-from-a-log-file-using-f