Add google traffic overlay to OSMAnd

OSMAnd is great with detailed maps, however when traveling I often miss traffic information. Luckily you can add an online overlay, that will show the google traffic data.

I used guides like these here or here, however all I could find would add the traffic information PLUS a whole google map, street names or anything else. I wanted to just have the traffic information.

So what is the solution

  1. First of all to use online overlays, you need to activate the plugin “Online maps” in OSMAnd

  2. After activating the plugin you can add in “Configure map” -> “Map source…” a new map:

New map settings

The URL string: https://mt1.google.com/vt/lyrs=h,traffic&x={1}&y={2}&z={0}&apistyle=s.t%3A0|s.e%3Al|p.v%3Aoff

The important part to remove labels and maps in here is the URL with the following config:

  • lyrs=h,traffic - selects the street layer with traffic information. If you use s or m in the beginning you will get the satellite or maps view with the traffic information
  • x, y and z - are the coordinates and zoom level filled by OSMAnd
  • apistyle=s.t%3A0|s.e%3Al|p.v%3Aoff - selects all feature styles (s.t), labels (s.e) and turns them off (p.v). Otherwise you will have street and location names in the overlay.

Especially the apistyle part was hard to find. Finally I found an explanation on stackoverflow for the different possible settings.

Saving this map, then you can configure your map and activate an overlay map. If you need to change the traffic map settings you find them in the local maps for editing. Be advised that this is an online function and requires a working internet connection and is also not included in route finding.

Full article view to comment or share...

What I learnt using docker as a beginner

Running my own server setup with docker, I ran into some issues over time. To not forget what I did and maybe to help others here is what I found.

It is a gathering of simple things I now try to include in all my docker setups, as they make life with docker easier. For some this might be obvious things. If you have additional thoughts, I am happy to hear of them.

Do not mix your hosts file system with your docker mounts

It might be a tempting idea to store config files of your container within your regular /etc or logs to /var/log. However you easily get lost to know which file is used by whom. Log files may get merged with others and so on. Hence have your own directory in which you store your docker stuff. It also makes backups, git use etc. easier.

Use git - a lot

You should use git to version everything you do on docker. Starting with your dockerfiles, to configuration files for services. Especially building a new container needs a lot of testing. Things might go forth and back, hence a history and versioning is utterly helpful.

Keep ENTRYPOINT and CMD separate

Use the entrypoint for something that needs to be done before each start of the service, like correcting permissions, update small things, etc. Keep the command separated from this (hence do not put the service start command in the entrypoint) so you can override it easily.

Use dumb-init in your own Dockerfiles

It is a small package you can easily install and easily add to your image. It simply puts a tiny init system into the container to properly start and stop your service. You can read more on the dumb-init github page. A lot of images out there ignore this. It is not per se harmful, but a lot of services get killed instead of properly shut down.

Prosody container example:

ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["bash", "-c", "/usr/local/sbin/entrypoint.sh && exec prosodyctl start"]

Learn about IPv6, iptables and internal docker networks

Docker takes over iptables when running a container, be sure to understand what is happening. Especially take a look on internal networks, as they can easily secure database container and other backend services.

For IPv6 better still use NAT. I didn’t like that in the beginning as well, however you run into so many troubles (like dynamic prefixes, limited IPv6 configuration options, etc.), that it is just way easier to use NAT again. Sadly docker does not support NAT on IPv6, for that use https://github.com/robbertkl/docker-ipv6nat. It is a simple binary to run in the background, that fills the gap.

Make sure your container runs with the correct user:group

Especially when using bind-mounts, make sure your use proper UID:GID combinations. I have created the users on my host machine that will be used inside the container and pass the UID:GID into the container. This ensures, that files stored on the host will not by accident be accessible by other processes due to UID overlap.

Again a prosody example on debian for your Dockerfile

ARG uid=1001
ARG gid=1002

# Add user and group before install, for correct file permission
RUN addgroup --gid $gid prosody \
    && adduser --system --disabled-login --disabled-password --home /var/lib/prosody --uid $uid --gid $gid prosody

Use docker logging

It is tempting to just mount a folder and store logfiles. However when using images, e.g. from hub.docker it can be quite difficult to correctly map this and often leads into some sort of chaos.

I actually like the webserver approach to just link the logfiles to /dev/stdout and /dev/stderr

RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log

With that you can use the regular docker logs command and use configuration options to divert it to whatever log facility you are using.

Make sure your container are in the right timezone

A lot of images nowadays use the TZ=<timezone> as an environment variable in the container to control the timezone. You should set this always and use it in your own Dockerfiles

RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime

Nothing is more confusing than log files with non matching timestamps.

If you need to build from source use multi-stage builds

Instead of bloating your production image with build-tools and sources, use multi-stage builds.

With that you can create a build image, run it and use the build results in a regular image:

Shortened example for coturn

### 1. stage: create build image
FROM debian:stable-slim AS coturn-build

# Install build dependencies
[...]

# Clone Coturn
WORKDIR /usr/local/src
RUN git clone https://github.com/coturn/coturn.git

# Build Coturn
WORKDIR /usr/local/src/coturn
RUN ./configure
RUN make


### 2. stage: create production image
FROM debian:stable-slim AS coturn
[...]

# Copy out of build environment
COPY --from=coturn-build /usr/local/src/coturn/bin/ /usr/local/bin/
COPY --from=coturn-build /usr/local/src/coturn/man/ /usr/local/man/
COPY --from=coturn-build /usr/local/src/coturn/sqlite/turndb /usr/local/var/lib/coturn/turndb
COPY --from=coturn-build /usr/local/src/coturn/turndb /usr/local/turndb

# Install missing software
[...]

Make sure your services start in order

A lot of errors come up when services that depend on each other just start in random order. Sometimes you are lucky and it works, sometimes you get an error. Within docker-compose you can use the depends_on directive. If you have for some reason separate setups, you can use the following bit within your entrypoint:

/bin/bash -c " \
  while ! nc -z mariadb 3306; \
  do \
    echo 'Waiting for MariaDB'; \
    sleep 5; \
  done; \
  echo 'Connected!';"
Full article view to comment or share...

Parsing config files in bash using awk

Ever so often I get to some sort of complex bash script and find, that a configuration or ini file would make sense, to control the script and make it more flexible. However bash does not bring nice libraries to parse config files, e.g. like python.

In essence two things are needed to write a config file parser:

  1. good trim of the config option, as you can have
    • <option>=<value>
    • <option> = <value>
    • <option> =<value>
    • and so on…
  2. structured way to move the config to usable variables within bash.

So what is the solution

First lets take a look on how to analyze a config file line. It is normally setup like: <option><delimiter><value>

To parse this, we need to separate the option and value by the delimiter. For this awk is perfect to use as it can seperate a line with a field separator in parts and store each in a variable.

Additionally we can eliminate spaces around the delimiter to make the config parser more robust. However we need to do that left and right of the delimiter, to only trim the leading and trailing spaces of each side. Just eliminating spaces in the whole string would delete spaces in configurations as well. Using the delimiter = this would look like the following for the left side (for right side change $1 to $2).

awk -F'[=]' '{gsub("^\\s+|\\s+$", "", $1); print $1}' <<< $line

So what does this do?

  • -F[=] sets the delimiter to =
  • gsub("^\\s+|\\s+$", "", $1); starting starting from the first letter (^) replace 1 or more spaces at beginning and end (\\s+|\\s+$) with an empty string (""). $1 use the left part of the delimiter for the gsub command
  • print $1 as awk automatically splits with the delimiter, print the first part (left side of the config line)
  • <<< $line is the feeded line of our config. Within bash you can easily loop through a file line by line with the read command.

Now that we have the basics, we can write a parser function with it.

An example within a script:

#!/bin/bash

declare -A settings_map

function read_config () {
    while IFS= read -r line; do
        case "$line" in
            ""|\#*)
                # do nothing on comments and empty lines
                ;;
            *)
                # parse config name and value in array
                settings_map[$(awk -F'[=]' '{gsub("^\\s+|\\s+$", "", $1); print $1}' <<< $line)]=$(awk -F'[=]' '{gsub("^\\s+|\\s+$", "", $2);print $2}' <<< $line)
                ;;
        esac
    done < $1
    echo ${settings_map[*]}
}

# call fuction with the config file to parse
read_config parser-test.conf

# show that it is working and picking "option" from the array
echo ${settings_map[option]}

This would read a config file like this (or with some spaces around the =):

# parser-test.conf
# Use option=value
#
type=value_type
option = value_option

test =value_test
spaces    = value space

It reads line by line, remove leading and trailing spaces on the left and right site of the config line, then store it into the settings_map array. The array can be used within the rest of the bash script.

Important: This function uses a globally declared array. Handing back an array from a function is not trivial. You could create and return a string, then initialize an array afterwards. However I feel that to be a bit cumberstone and not adding to the functionality.

Full article view to comment or share...

Deactivate bluetooth on startup

For some reason my laptop thinks bluetooth is the technology to go and activates it on startup of Linux Mint/Ubuntu.

Searching the internet you will find a lot of answers for this problem, that quite brutally kill the bluetooth service or block devices. All not very elegant.

So what is the solution

Most laptops use tlp for power management. If you don’t you should check it out. With that it is quite easy. Uncomment and edit in /etc/tlp.conf:

DEVICES_TO_DISABLE_ON_STARTUP="bluetooth"

You can also edit tlp to remember the last state when powering down. But for me deactivating bluetooth was all enough.

Full article view to comment or share...

Open Source Wifi Speaker based on IKEA Symfonisk - Part II: Software / OS

After finishing the hardware in part I of this little tutorial, we need to set up the operating system to run our speakers. Of course it should be open source software.

There are multiple solutions out there to do multiroom streaming, however the slimserver or squeezserver eco system looks the easiest for me right now. It seems to support everything I am looking for:

  • Central streaming server with lightweight clients for playback
  • Multiroom and sync features
  • Extensible with plugins
  • Internet radio
  • Streaming services, like Spotify or Tidal
  • Open source

Most of the features are actually done in the server and are pretty easy to set up. I am running the server in a docker on my NAS, where it also has access to my local music library.

This part however is focusing on the player and have that as slim and robust as possible. I checked a setup on HifiberryOS, Rasbian, etc.. However was convinced to start with piCorePlayer as a clever solution based on tinycore Linux.

The advantage of piCorePlayer / tinycore Linux is, that it runs completely in RAM. Hence you can unplug the speaker without too much worries about the SD card in the raspi. Also the system is very small. I use now only a 1GB SD card, which is not even used 10% (Sadly you can not buy such small cards anymore ;-)). I could have used piCore (the barebone tinycore Linux for raspis), however then I would need to do a lot of work that has already been done by the piCorePlayer team.

So now we only need to extend the piCorePlayer with specialties of our setup:

Step 1: Prepare Richard Taylor’s LADSPA plugins

As tinycore is loading everything into RAM on boot, the system is build out of different extensions, which are pretty much zips with the files for that extension. All loaded extensions get merged into one filesystem in RAM on which the system is running. Most things are already prepackaged and can be downloaded as an extension, however within the repositories, I could not find the LADSPA plugins of Richard as a prepared extension. However creating such extension is not too complicated.

Download Plugins (Author published source code under GPL-v3.0)

If you want to compile the plugins yourself, it makes it easier to use a piCore setup on the Raspberry.

Step 2: Get piCorePlayer and install it to the SD card of your speaker

Get the latest piCorePlayer here and write it to your SD card.

Next you need to add your wifi information to have it log in automatically. For that, mount the just created SD card. It should have two partitions, a PCP_BOOT (probably mmcblk0p1) and PCP_ROOT (probably mmcblk0p2) partition. Create a wpa_supplicant.conf in the root of the PCP_BOOT partition and fill in the relevant fields (country, SSID, password). There is also a sample file which you can just copy, rename and edit. This is also explained here.

Next you need to resize the root partition to use the free space on the SD card. On my systems (Linux Mint), I can easily to this with gparted (right click on partition to resize). Otherwise you can do it in the piCorePlayer GUI after booting. However to do it right away is easier, as we want to copy Richard Taylors plugins as well. The plugins from Step 1, will now be added to the piCorePlayer by copying them to the PCP_ROOT storage

cp rt-plugins-0.0.6.tcz /mnt/mmcblk0p2/tce/optional/
cp rt-plugins-0.0.6.tcz.md5.txt /mnt/mmcblk0p2/tce/optional/
echo "rt-plugins-0.0.6.tcz" >> /mnt/mmcblk0p2/tce/onboot.lst

The last line adds the extension to the boot list, so they will be loaded and available in the file system after boot. Now the piCorePlayer is ready to boot and to run the speaker.

Step 3: Configure piCorePlayer

You need to do some basic configuration of piCorePlayer to run it with the speaker. For that use a browser to access the piCorePlayer web interface under the IP of our speaker. First activate the advanced mode on the tab of the bottom of the Main Page. During the configuration you will have a few reboots. I would do each one of them, to be thorough.

If you haven’t extended the filesystem yet, you need to do it now (Main Page -> Resize FS) and probably still need to copy the plugin

After that update the player under Main Page -> Full Update. Update the extension under Main Page -> Update. Check for possible hotfixes under Main Page -> HotFix.

Then head to the Squeezelite Settings page and set Amp2 as audio output device. After saving and possible reboot, use the Card control button and deactivate the raspis internal audio device down at the bottom. Only after that squeezelite will start. Back on the Squeezelite Settings page you have to enter the name of this player and the IP of the LMS server. Finally on the Tweaks page enable the ALSA 10 band Equalizer. Probably another reboot is helpful and our basic configuration is complete.

The speaker now will be working, however it will sound weird. You still need to activate the crossover and probably use the equalizer.

Step 4: Activate crossover, mono downmix and equalizer settings

For the crossover you need to connect to the player console via SSH. After login you need to edit the /etc/asound.conf file. Vi is installed in the system. You will find a pre-generated configuration from piCorePlayer. Edit this to match the following:

# default - Generated by piCorePlayer
pcm.!default {
  type plug
  slave.pcm "plugequal"
}

ctl.!default {
  type hw
  card 0
}

pcm.pcpinput {
  type plug
  slave.pcm "hw:1,0"
}

#---ALSA EQ Below--------
ctl.equal {
  type equal;
  controls "/home/tc/.alsaequal.bin"
  library "/usr/local/lib/ladspa/caps.so"
}

pcm.plugequal {
  type equal;
  slave.pcm "plug:crossover";
  controls "/home/tc/.alsaequal.bin"
  library "/usr/local/lib/ladspa/caps.so"
}

pcm.equal {
  type plug;
  slave.pcm plugequal;
}

#--- Speaker crossover below-----
pcm.crossover {
  type ladspa
  slave.pcm "amp2"
  path "/usr/local/lib/ladspa"
  channels 6
  plugins
  {
    0 {
       label RTlr4lowpass # lowpass left output to channel 2
       policy none
       input.bindings.0 "Input"
       output.bindings.2 "Output"
       input { controls [ 3000 ] }
      }
    1 {                                                    
       label RTlr4lowpass # lowpass right output to channel 3
       policy none                       
       input.bindings.1 "Input"                            
       output.bindings.3 "Output"                           
       input { controls [ 3000 ] }       
      }                                       
    2 {                                                     
       label RTlr4hipass # highpass left output to channel 4
       policy none                       
       input.bindings.0 "Input"                             
       output.bindings.4 "Output"                           
       input { controls [ 3000 ] }                         
      }                                       
    3 {                                                     
       label RTlr4hipass # highpass right output to channel 5
       policy none                                         
       input.bindings.1 "Input"                            
       output.bindings.5 "Output"                           
       input { controls [ 3000 ] }                          
      }                                  
  }                                                         
}                                

# --- Output device and mono downmix below -----                                                            
pcm.amp2 {                                                  
  type plug                                                 
  slave {                                                   
    pcm "t-table"                
    channels 6                                              
    rate "unchanged"                                       
  }                                                         
}                               
                                 
pcm.t-table {                                               
  type route                                               
  slave {                                                  
    pcm "hw:0,0"                                            
    channels 2                                              
  }                              
  ttable {                       
    2.0 0.5 # Mix both stereo low (main) to mono            
    3.0 0.5                                                 
    4.1 0.5 # Mix both stereo high (tweeter) to mono        
    5.1 0.5                                                
  }                                                         
}                                                  
                                               
pcm.plughw.slave.rate = "unchanged";

So what is this doing? Basically it tells ALSA to use a chain of filters for the default device and then send it to the hardware:

  • pcm.!default: redirect the default device to plugequal
  • pcm.plugequal/pcm.equal: this is the ALSA equalizer and first step in our chain. We will configure it later. We can use this to influence the sound of our speaker
  • pcm.crossover: here we use Richard Taylor’s plugins to split our stereo signal in 4 channels. The highpass ones above 3000Hz and the lowpass below that
  • pcm.amp2/pcm.t-table: this is the hardware device for playback (hw:0,0), the table does the mono downmix for high- and lowpass channels

After this, we get 2 mono signals, one above 3000Hz, one below, each left and right of the stereo signal downmixed. These are then put to the tweeter and mid-range speaker. Having the equalizer in the chain, we additionally have the opportunity to tune the sound of our speaker. This will impact all sound being send to the speaker.

Save the file and get back to the command line. Next you need to set the equalizer

Step 5: Setting the equalizer

The ALSA equalizer is set via the command line.

export TERM=xterm
sudo alsamixer -D equal

The first line will prevent the error Error opening terminal: xterm-256color.

You will get a nice looking console equalizer, in which you can tune the sound of the speaker (left/right, up/down arrow keys). Now this is complicated, as sound is something very personal. Hence my settings might not be the right ones for you. I advise to come back later when you play some music and tune this to your taste (I am happy to hear of any settings). Changes to the equalizer are audible right away.

Here are my settings (observe the numbers on the bottom)

Equalizer

As you can see, I did not change too much. As far as I know, within an equalizer the less is better.

IMPORTANT step: We now edited some files on the player, to make that survive the next reboot, we need to backup our config. Go back to the piCorePlayer web interface to the Main Page and click the Backup button. Now the settings will survive possible reboots.

Step 5: Add controls to the front buttons

To make our buttons on the front work during playback, we need another extension.

Head to Main Page -> Extensions and click on the Available tab. Under the Available extensions select pcp-sbpd.tcz and click load. This installs all necessary plugins to monitor the raspi pins.

Next - via SSH - create the file /home/tc/sbpd-script.sh with the following content (please adjust the -A and -P to your LMS server instance and port. You can try auto discovery by removing both flags as well):

#!/bin/sh

# start pigpiod daemon
sudo pigpiod -t 0

# give the daemon a moment to start up before issuing the sbpd command
sleep 1

# load uinput module, then set the permission to group writable, so you don't need to run sbpd with root
sudo modprobe uinput
sudo chmod g+w /dev/uinput

# issue the sbpd command
sbpd -s -d -A 192.168.2.9 -P 9000 b,11,PLAY,2,0 b,10,VOL-,2,0 b,9,VOL+,2,0

After saving the file, make it executable chmod +x /home/tc/sbpd-script.sh This script basically starts the GPIO monitoring. The last line defines the GPIOs to watch. here it is 9, 10 and 11 (each right after the b,). If you remember the table out of the hardware wiring for the buttons this is SW_Mute, SW_Down, SW_Up. If you changed the pins used on the raspi, then you will need to adapt it here.

If you are interested in the whole syntax given there or to add a long press event, use sbpd --help on the command line to get the details.

Now the script needs to be launched at startup. In the web interface go to Tweaksand add /home/tc/sbpd-script.sh as User command #1 and click Save.

Now finally go back to the Main Page and hit Backup one last time. One last reboot and your speaker should be ready to rock…

Addendum: Changing crossover frequency and equalizer

The selected crossover frequency and equalizer settings are based on my personal taste. It might be that there are better options. Overall, this is not a high audiophile level of speaker, hence do not expect too much. If you want to fiddle with that, the following commands might help a bit.

The crossover frequency is set in /etc/asound.conf. I guess you can spot the 3000. ;-). The frequencies for each filter should match, otherwise it sounds quite weird, but feel free to play around with it. Just beware, that forcing a speaker into frequencies it was not build for is stressful for it and might even break it. Hence not change your tweeter into a sub-woofer and vice versa.

Calling the equalizer: export TERM=xterm sudo alsamixer -D equal

Relaod ALSA after changes (reboot is always better): alsactl kill rescan

Reduce the volume directly on the speaker to 70% (otherwise the next command is very loud): amixer sset Digital 70%

Produce a pink noise speaker test sound (for frequency measurements): speaker-test -c2 -tpink

Full article view to comment or share...