From bash to zsh

Updating to the latest Kubuntu gave me a chance to revisit all previous decisions related to the OS. The login shell is one of them…

As a long term bash user migrating off is hard: everything is set up to work perfectly. And it works on every computer I use, be it at home, in the cloud or at work. Changing to another shell would need to give me significant benefits. Powerlevel10k on zsh was that benefit for me: Works out of the box and has a fancy prompt which shows contains important and useful information, especially when you use git (which I do):

Powerlevel10k typical prompt

There’s a lot of useful information: I’m on a GitHub repo, in the “experimental_compact_vars” branch with 2 changes not yet committed. The last command returned with an ok status and took 4s (time is not displayed if less than 3s which shows the attention to details).

In the words of Oh My Zsh:

Oh My Zsh will not make you a 10x developer…but you may feel like one.


Small minor problems with zsh which I have not seen with bash.

  • TZ is not set correctly. Set it in /etc/zsh/zprofile:
TZ=$(cat /etc/timezone)
export TZ

AKG Lyra – A (Big) Microphone

All this work-from-home puts more focus on sound quality of audio and video conferences. Some people are very hard to understand on a conference call. Speakerphone with echo, noisy background, windy area or they simply don’t have the microphone in the right place. In all cases having a discussion is hard when someone who has to say something is inaudible.

So good audio quality is important. And listening to YouTube, Twitch and Podcasts gives you an idea of what is good and what is bad.

So I wanted to get myself a decent microphone. Turns out that I’m not the only one who thought so: Amazon ( is pretty much sold out on USB connected “good” microphones. Literally all the recommended brand name microphones were sold out. What’s left is either way too expensive (30000 Yen or more) or of very questionable quality with matching bad ratings.

Moving off the mainstream helps: Where Amazon is sold out, Sound House is not. While they sell mainly music things, they also sell USB connected microphones. And while most were sold out here too, not all were. One in particular was supposed to arrive on April 30th: the AKG Lyra. It’s USB-C connected, and seems to be generally good according to reviews. It was marked as “in stock” on 30th, so I ordered one. And it arrived today.

And it’s…huge.

But it sounds really nice: very natural. Picks up sound from far away too. Not overly sensitive to plosives. Can do stereo. Has a headphone connector to listen to yourself and it acts as a sound output for your computer too.

I am mightily impressed with the whole design and functionality.

It works on Phones too!

But the best part, which was unconfirmed until I tested it: It works as a microphone and sound output using my Sony Xperia XZ2: plug it in via USB-C and programs like Audio Recorder use the Lyra as input (and output).

Will it work during a normal phone call? We’ll see.

MicroSD Cards Performance

App Performance Class Spec Table

I read about this “A1” specification on microSD cards: it’s supposed to be “App Performance” and is technically a minimum read and write IOPS.

So I got one to find out how much of a difference it makes in an OrangePi Zero running Armbian.

Simple test:

  1. Prepare an existing microSD card (I-O Data, Class 4, 8GB)
    • apt clear
    • apt update
    • shutdown
  2. Copy that card on another PC
    • dd bs=128k if=/dev/sdb of=8g.img
  3. Copy that image to the A1 card (Kingston Canvas Select Plus 16GB)
    • dd bs=128k if=8g.img of=/dev/sdb
  4. Now do the following commands after booting each card

This was the result:

CommandOld cardNew card
ls -Rl /usr >/dev/null5.6″4.9″
apt update2’22”1’57”
apt upgrade -y24’10”10’2″
Linux commands using different microSD cards

I thought the “ls” will make a bigger difference, but I guess the filesystem and its caching does a good job. The “apt update” is mainly pulling data from the Internet. But the “apt upgrade” was impressive: 97 packets were updated in both cases and while it includes a lot of data pulling from the Internet, the main time is spend on unpacking and installing all those Debian packets. That’s reading and writing plenty files. And it shows.

I did not expect the difference to be that much. I’m sold and will replace any future microSD cards with A1 models (note A2 needs drivers for queuing and without driver support, they can be slower than A1 models).

BME280 and Node.js on OrangePi

I got some more sensors, like an Bosch BME280 which uses I²C bus. Neat as it can measure temperature, air pressure and humidity.

Using it on an OrangePi Zero is simple:

  • Enable the I2C-0 module: in /boot/armbianEnv.txt add “i2c0” (or use i2c1 or i2c2) to the overlay.
  • Create /etc/udev/rules.d/60-i2c-tools.rules with the following content:
KERNEL=="i2c-0" , GROUP="i2c", MODE="0660"
KERNEL=="i2c-[1-9]*", GROUP="i2c", MODE="0666"
  • Add yourself to the i2c group:
useradd -a -G i2c harald
  • Reboot

You now have an accessible /dev/i2c-0 (or i2c-1 or i2c-2). You can check you have a BME280 visible on the I2C bus:

harald@opz2:~$ i2cdetect 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- 76 --                         

0x76 is the default I2C address of a BME280 (0x77 being the alternative address).

Now read it via Node.js (package from here):

const BME280 = require('bme280-sensor');

// The BME280 constructor options are optional.
const options = {
  i2cBusNo   : 0, // defaults to 1
  i2cAddress : 0x76 //BME280.BME280_DEFAULT_I2C_ADDRESS() // defaults to 0x77

const bme280 = new BME280(options);

// Read BME280 sensor data, repeat
const readSensorData = () => {
    .then((data) => {
      // temperature_C, pressure_hPa, and humidity are returned by default.
      // I'll also calculate some unit conversions for display purposes.
      data.temperature_F = BME280.convertCelciusToFahrenheit(data.temperature_C);
      data.pressure_inHg = BME280.convertHectopascalToInchesOfMercury(data.pressure_hPa);
      console.log(`data = ${JSON.stringify(data, null, 2)}`);
      setTimeout(readSensorData, 2000);
    .catch((err) => {
      console.log(`BME280 read error: ${err}`);
      setTimeout(readSensorData, 2000);

// Initialize the BME280 sensor
  .then(() => {
    console.log('BME280 initialization succeeded');
  .catch((err) => console.error(`BME280 initialization failed: ${err} `));

And it works:

harald@opz2:~/js/bme280$ node bme280.js 
Found BMx280 chip ID 0x60 on bus i2c-0, address 0x76
BME280 initialization succeeded
data = {
  "temperature_C": 23.19,
  "humidity": 35.62696153399403,
  "pressure_hPa": 1003.0408627864141,
  "temperature_F": 73.742,
  "pressure_inHg": 29.619784150102433

Reading Temperatures from many DS18B20 via Node.js

The easiest way:

harald@opz2:~$ cat /sys/bus/w1/devices/28-*/hwmon/hwmon/temp1_input

The problem is that each sensor takes about 800ms (750ms according to data sheet) conversion time. Making this better and reading them concurrently via Node.js:

const fs=require('fs');

const HWSENSORPREFIX='/sys/bus/w1/devices/';

async function getSensors() {
  let sensors=[];
  const dir = await fs.promises.opendir(`${HWSENSORPREFIX}`);
  for await (const dirent of dir) {
    if (,3)=='28-') {
  return sensors;

// Print temperature reading from a given 1-Wire sensor

async function printTemp(sensor) {
  const dir = await fs.promises.opendir(`${HWSENSORPREFIX}/${sensor}/hwmon`);
  for await (const dirent of dir) {
    if (, 5)=='hwmon') {
      let temp = parseFloat(await fs.promises.readFile(`${HWSENSORPREFIX}/${sensor}/hwmon/${}/temp1_input`))/1000.0;
      console.log(`${} (${sensor}): ${temp}`);

(async function () {
  try {
    let a=await getSensors();
    a.forEach(x => printTemp(x));
  } catch(e) {
    console.error(`Error: ${e}`);

That’s reading all in parallel (plus about 600ms startup time):

harald@opz2:~$ node read-temp.js
hwmon0 (28-80000004215e): 23.312
hwmon2 (28-0316884578ff): 21.812
hwmon1 (28-0316884fabff): 22.312

Adding sensors changes the time until all are read:

DS18B20 Countcatread-1w-temp.js
Time to read in [s] for 1 to 3 DS18B20 temperature sensors

OrangePi Zero + DS18B20

Making my OrangePi Zero useful (again) with updating it to the latest Armbian and connecting a 1-Wire DS18B20 temperature sensor to it.

OrangePi Zero on the left, DS18B20 on the right


Get a microSD card and install the latest Armbian image for your Zero. Boot and do the usual setup: set root password, create a new user, copy ssh keys etc.

Hardware Related Setup

Here the fun part starts. Define the features you want to use on the 26 pin header (see at the Sunxi wiki).

Edit /boot/armbianEnv.txt

Add the w1-gpio overlay and configure it to use PA07 (pin 12 on the 26 pin header) and enable pull-up (not really needed though as we need an external pull-up resistor anyway). Reboot when done. Other pins are ok too. PA07 works though.

overlays=usbhost2 usbhost3 w1-gpio

The name of the overlay are from /boot/dtb/overlay/.

Wire up everything

DS18B20 has 3 pins: GND, 3.3V and DATA. DATA goes to PA07. Don’t forget a 4.7k pull-up resistor between 3.3V and DATA.

Measure Temperature

If everything worked, you will have a temp1_input which has the temperature in °C * 1000:

harald@opz2:/boot$ modprobe w1_therm
harald@opz2:/boot$ lsmod | grep w1
w1_therm               16384  0
w1_gpio                16384  0
wire                   24576  2 w1_gpio,w1_therm
harald@opz2:/boot$ dmesg
[  142.370130] w1_master_driver w1_bus_master1: Attaching one wire slave 28.80000004215e crc 94
harald@opz2:/boot$ cat /sys/bus/w1/devices/28-80000004215e/hwmon/hwmon0/temp1_input

2020-04-09 Quick update: Adding a 2nd DS18B20 was picked up automatically and now I have 2 sensors:

harald@opz2:~$ cat /sys/bus/w1/devices/28-80000004215e/hwmon/hwmon0/temp1_input
harald@opz2:~$ cat /sys/bus/w1/devices/28-0316884fabff/hwmon/hwmon1/temp1_input 

Making JetBrain’s IDEs less sluggish

WebStorm works on my Chromebook in Crostini. I’m happy it works. It’s a bit sluggish, but I attributed this to the CPU (m3), the many pixels on the screen (2400×1600) and/or Crostini itself. It’s fine as I won’t run thousands of lines of code here.

On my home Linux desktop (i7-4510U) it’s much better. Not speedy, but ok. Sublime Text is way snappier though.

Turns out you can improve both significantly by changing the Java runtime environment to the one from JetBrain one. Here the steps:

  1. File->Settings->Plugins.
  2. Click marketplace, search for “Choose Runtime”
  3. Install official Choose Runtime addon from JetBrains
  4. Wait for install and click to restart IDE.
  5. Once back in project, press shift twice to open the search window
  6. Search for Runtime. Select “Choose Runtime”
  7. Change to “jbrsdk-8u-232-linux-x64-b1638.6.tar.gz”, which should be the very last one at the bottom of the list.
  8. Click install, restart IDE, enjoy!

Obviously pick the latest version of the jbrsdk-8u-232-linux-x64. Why this is not default, I cannot say. It should be.


Citrix Workspace and Linux

Installing the Citrix Workspace (formerly Receiver) on a Linux machine should be simple. After all, installing it on a Chromebook worked just fine (after enabling High DPI since my Chromebook has such a display).

But on Linux all I got was: You have not chosen to trust “Entrust Root Certification Authority – G2”, the issuer of the server’s security certificate.

Well, I was not aware of it and Chrome itself as well as Firefox had no issues with

So what’s throwing that error message?

Seems it’s the Citrix Workspace application itself which uses its own certificate store and it needs some more certificates (see

  1. Get the missing Entrust G2 root certificate from here
  2. Copy it to /opt/Citrix/ICAClient/keystore/cacerts/ (this is where the Debian package installed into)
  3. Rehash the certificates
sudo /opt/Citrix/ICAClient/util/ctx_rehash

Et voilà: it works. See also this entry in the Citrix forum. Different root certificate, but same problem.

Seems the Citrix Workspace app has its own certificate store. Why is beyond me as the system usually has a certificate store already. Why not use that one?

Note: The need for the Entrust certificate seems to be because the point to what I connect to uses a certificate signed by Entrust. Thus not everyone will need this particular root certificate. But whichever you need, it needs to be in /opt/Citrix/ICAClient/keystore/cacerts/

Installing the Garmin ConnectIQ SDK

Yesterday I ordered a Garmin vívoactive 3. At ¥15200 it was hard to not get one. As Garmin has an app store for their watches and there’s also a developer forum and of course an SDK, the next obvious step was to install that SDK.

Installing the ConnectIQ SDK

Get it from here. It seems to be slightly buggy. Details only though. On a recent Ubuntu build you run into several issues, but they are easy to fix (this was a great start):

$ cd ~
$ mkdir connectiq
$ cd connectiq/
$ unzip ../Downloads/

$ cd /var/tmp/
$ wget
$ sudo dpkg -i libpng12-0_1.2.54-1ubuntu1.1_amd64.deb
$ sudo apt-get install libwebkitgtk-1.0

$ tr -d '\r' < bin/monkeygraph >/tmp/monkeygraph && cp /tmp/monkeygraph bin/monkeygraph

Also have OpenJDK 8 installed. 11 does not work and is not supported by Garmin.

Now waiting for the watch to arrive…

Creating a Developer Key

The first problem trying out the samples in the SDK was that a developer key was required. While the instructions are here, it took me way longer than needed to find them:

$ openssl genrsa -out developer_key.pem 4096
$ openssl pkcs8 -topk8 -inform PEM -outform DER -in developer_key.pem -out developer_key.der -nocrypt

Testing the Examples

Make sure you use Java 8 (not Java 11). Start the simulator, compile a program and run it in the simulator:

$ simulator &
$ cd samples/Timer
$ monkeyc -f monkey.jungle -y ~/connectiq/developer_key.der -o Timer.prg -d vivoactive3
$ monkeydo Timer.prg vivoactive3

Not sure how realistic the simulation is, but it’s certainly ok to ensure it might work in real life.

Links to Working Examples

Create your website at
Get started