Get a push notification when your laptop is low on battery

A while ago I wrote about getting notified on battery low. It is quite useful when you leave your laptop unattended for a while, to get a notification on your phone and/or smartwatch when that laptop is about to run out of juice. It has saved me a few hard shutdowns (or interrupted tasks because of forced hibernation).

The udev rule in that article should still work (if your device/battery emits those kinds of events at least), but for me the PushBullet method used there stopped working.

As I recently started using PushOver next to PushBullet, I decided to redo the little setup with a cronjob and a PushOver shell script. The cronjob works around the battery subsystem not emitting an event when you need it, the shell script has less dependencies than a Python script.

An example with the udev rule could look like this:


# Suspend the system when battery level drops to 5% or lower
SUBSYSTEM=="power_supply", ATTR{status}=="Discharging", ATTR{capacity}=="[0-5]", RUN+="/home/youruser/bin/"

(Rule based on a snippet found on this handy wikipage)

Myself, I wrote a cronjob to be run every 5 minutes (you can use whatever interval you prefer of course), which looks like this:


BATTERYLEVEL=$(cat /sys/class/power_supply/BAT0/capacity)

if [ $BATTERYLEVEL -lt 15 ]; then
    /home/youruser/workspace/ -t "Power low on $HOSTNAME" "Battery power now is ${BATTERYLEVEL}%"

This takes the current battery level from the udev system path, checks whether the value is less than 15 (percent) and sends a message through PushOver if it is. It will do so every 5 minutes until it's over 15 again, but that's fine with me (and a small price to pay on a system without the battery events). It might be made a bit more clever with keeping track of 'did I just send a message already' state files, but that exercise is left for the reader :)

Then, the crontab entry looks thusly:

# Check battery level every five minutes, PushOver message when below a certain percentage
*/5 * * * * /home/youruser/bin/cron/check_battery_level

N.B.: the little script that I used, has a config file, located in ${HOME}/.config/pushover.conf, which takes your PushOver application token, your user token and optional CURL options:

TOKEN="your application's token here"
USER="your user/group key here"
CURL_OPTS="options to pass to curl"

Your user token is on the homepage of PushOver if you are logged in. The Application Token ('TOKEN') you can create by scrolling down that page and create a new application in Your Applications. There you can give it a nice icon and such too.

  • Michiel Scholten
  • Work
  • 🕔

I made a thing: a simple bookmarking service

A while back I started scratching an itch that I had and that wasn't relieved by some existing software or services I could find: a central place to keep track of interesting places on the web I wanted to follow up on, without having them saved as read-later in Pocket (which is an excellent tool, but I'd like to keep it for articles only).

One of the requirements was that I should be able to easily add some URL from my phone, so I can remember it for when I have more time or a bigger screen to research something or watch a video. Also, it needed to be cross-browser, preferably environment agnostic. That ruled out the various bookmark syncing systems of the respective browsers, also because I tend to use Firefox on the desktop and Chrome or Flyperlink on my mobile devices.

A web application seemed like a good fit, so I started on a Flask based website with peewee as uncomplicated ORM. To make it all look good, I used MaterializeCSS which is a great toolkit to create Google Material style websites (I also use it on for example). As backend, instead of choosing a 'big' database management system like MariaDB or PostgreSQL, I decided that peewee would store its models in a SQLite file.

This all resulted in a project that is quite portable: after creating a virtualenv and filling it with the requirements, one is already able to run the web app by just running python, which will run Flask's debug server, but that's fine for the casual use that a bookmarking system gets. The frontend is fully responsive, so looks just fine on a phone screen and big desktop monitor.

Of course, running it under nginx/uwsgi or Apache/mod_wsgi in a central location is a bit more useful, so for myself I did just that.

While toying around with the functionality after having implemented automatic retrieval of the title of the linked page, tags and stars/favourites were among the first to go in. I would want to find things easily by having relevant labels attached to the bookmarks, and if I especially liked something, a star can be added. Filtering on these, along with search for text makes it easy to find things again.

As I now have tagging, and occasionally want to share research with friends and other people, I decided to make it easy to create a read-only view of a certain tag, where the bookmark cards are available for review by whoever you share the (private-ish) url with, but do not disclose any other information about them, like the other tags attached, 'star' status, or edit possibility. An example is my list of useful Python resources.

All of this is open sourced on its own GitHub page, so check it out if you're interested. Installation instructions are included (you can either pip install it, or clone the project and go from there). An project page is online too, with currently slightly outdated screenshots.

  • Michiel Scholten
  • Home
  • 🕔

When your Pebble doesn't want to talk web anymore

A few weeks ago I started noticing an odd problem: my Pebble Time Steel didn't want to update its various apps anymore. The watchface showed stale weather data, the public transports apps didn't want to tap into the latest departure times and so on. This of course annoyed me a bit, especially because I couldn't really find someone else with the same issue.

When I reinstalled a watchface that was bought through KiezelPay (yes, you can buy watch faces nowadays, and I think you should if you find a neat one; devs put a lot of time into their products), the only thing I got was a big error message that KiezelPay and the internet could not be reached. Cue me mailing the watchface developer, who in turn hooked up the people from KiezelPay. I got a test app, which of course was also not able to reach the webs. Colour us baffled.

Pebble customer support didn't seem to be able to help me either, as they asked me to look into battery saving settings and such. A valid course of action of course, but my Nexus 6P doesn't have the really aggressive battery savers of some vendors, and everything had been working just fine for years before.

The only thing that I could think of was me updating to Android 7.0. I didn't remember whether the issue had been around before that, so I was kind of focussing on that now.

Meanwhile, the Modular dev (Modular is a great watch face, you should check it out if you have a Pebble, or buy a Pebble because of it), and the Kiezel devs were keeping contact with me, and reaching out to their contacts within Pebble. A suggestion came back about the Overlay permission. This "Draw over other apps" permission, as it's called in the Android settings, allows an app to draw a window or similar on top of the app you are currently viewing. This can pose security problems as it can draw a fake button, so if you install an app, Android refuses the "Install" button to be enabled if it detects a screen overlay. Same when trying to change app permission settings.

As I had been experimenting with some apps and encountered the "Screen overlay detected!" warning a few too often times, I dove into the app permissions and found quite a few apps that had the "Draw over other apps" permission enabled. I disabled it for a few apps where I couldn't think of a good reason to have it and didn't think about it anymore until the suggestion from the Kiezel dev came in.

Low and behold, if I enabled it for the Pebble app again, all Javascript apps on my watch were able to connect to the internet again just fine. It only took me two weeks or something to find this out...

I still think it's a weird permission and I wonder how it is connected to the ability of Pebble apps to connect to the internet straight from the watch (of course, routed through your phone). In the meanwhile, I'm happy the issue has been resolved, as it was quite the mystery. Not having any related problems popping up on internet searches isn't something I'm used to anymore. I hope this little article might help any other persons that might run into this (admittedly, obscure) problem.

  • Michiel Scholten
  • Train
  • 🕔

How to fix Secure Settings on Android devices with SuperSU systemless root

Secure Settings (Google Play) is a really helpful app to help Tasker (Google Play) do things automatically on your Android device.

However, it has not been updated for quite a while (January 2015 at the time of this writing) and since then SuperSU has changed its way of installing the su binary to your device, by preferring not to install this on the system partition.

As some older apps hardcode the complete path of this binary in their checks, and Secure Settings is one of these, it thinks it can not get root access.

On my quest of a fix for this problem, I found this post on XDA where a comment on Reddit was referenced, stating the following fix, running the commands from a command line (terminal) on your machine, having adb installed:

adb shell
mount -o remount,rw /system
touch /sbin/su /system/bin/su /system/xbin/su
mount -o remount,ro /system


This creates some empty files in locations that older (incorrect) apps check for the su binary, so Secure Settings (and likely other applications) are made to believe they can get root (which they actually can, as you can not make those files without being root through su anyway).

You can also do this on your device by just omitting the first line and starting with su in a Terminal app (or for example JuiceSSH in a local session).

I hope this helps other people looking for a solution too.

  • Michiel Scholten
  • Work
  • 🕔

Nienke says "Hello World"

In the night of Sunday to Monday, at 4:17, Nienke rushed into this world. Where Daan took his time being delivered, I was barely awake (Ineke woke me at 4:01) when Nienke saw her first lamplight.

Needless to say, she was born at home, in the tub where Ineke thought she was going to relax a bit while the contractions that started at around 1AM where building up; the delivery went well (thankfully so, as the midwife/verloskundige wasn't even there yet - I was telling her live what was happening while she rushed to our place where she arrived just minutes late), and both Nienke and Ineke are fine, be it tired.

Of course, this was already a few days ago, so we had some interesting times already. She's doing great; a lot more quiet than Daan used to be her age, which is really welcome. We are enjoying our ruined nights, but are relaxed. Daan is being the sweet big brother.

Official "Hello World" post

  • Michiel Scholten
  • Home
  • 🕔

dammIT: 12 years of rants

Today it's 12 years ago I officially started this weblog and I finally have taken some steps to get the next generation of the codebase working on my server. It's getting there, looking forward to continue dammIT in its Django incarnation :)

This year has been fascinating; junior went from barely walking to running around, thrashing the place while ranting about the wind blowing a bit too hard outside. In the meanwhile, junior number two is about to be here too, so I'm sure next year we are going to have a really interesting, sleepless time.

Looking forward to all the weird, frustrating, wonderful, funny things waiting for us!

  • Michiel Scholten
  • Home
  • 🕔

I made a thing to make your phone go 'pling' (when your train goes 'whatever')

I made a thing:

You can use it with my other thing at to get notified when your train is late, or to know when you can do a sprint upstairs on the next station because your otherwise impossible transfer is delayed a bit and you can catch it now.

See the and the ns-notifications README to configure.


PS: yeah, it makes use of the official Nationale Spoorwegen (NS/Dutch Railways) API, and you need to request a key with them to use it. That's easy though and worth it if you like timely notifications.

  • Michiel Scholten
  • Home
  • 🕔

ssh config.d to tidy up that archeology-worthy ~/.ssh/config

You might be in that same situation as I was until recently: you open up your ssh config file and start scrolling through the long list of nicely aliased private machines, that organically grown list of work - project A and the comment-section-divided work - project B, work - misc, work - are these still even on? and work - I have no clue why these are even in here.

At least you have them separated in sections.

However, to make my life a bit easier and to be able to generate parts of the list (for example), I decided that I would try my hand at a config.d directory with separate files that the real ~/.ssh/config file is generated from.

Here it is:

# SSH config merge
if [ -e ~/.ssh/config.d ]; then
    #if find ~/.ssh/config.d -mindepth 1 -print -quit | grep -q .; then
    # Do we have config files in that directory?
    if find ~/.ssh/config.d -print -quit | grep -q .; then
        newestconfig=$(find ~/.ssh/config.d/* -printf '%T+ %p\n' | sort -r | head -n1 | awk '{print $2}')
        if [ "$newestconfig" -nt ~/.ssh/config ]; then
            # We found a config that's newer than the generated config file, re-generate
            [ -e ~/.ssh/config ] && mv ~/.ssh/config ~/.ssh/config.bak.$(date -Ins)
            # Lets preserve order, so you can have  00_generic 10_homestuff 20_work1 21_work2  and such
            find ~/.ssh/config.d/* -type f -print0 | sort -z | xargs -0 -n1 cat > ~/.ssh/config

Well, this is the supporting code that is supposed to go in your ~/.bashrc. When a new shell is opened, it checks whether there is a file in ~/.ssh/config.d/ that is newer than the file ~/.ssh/config is, and only then the files are concatenated together to that certain file. They are sorted alphanumerically, so you can have a naming scheme like:


The 00_base one in my list contains some ssh configuration that might be of interest:

# == Generic config ======
TCPkeepAlive yes
ServerAliveInterval 30

# Re-use existing ssh connections
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600

If you are interested in my other dotfiles, I have them online in version control like the coding hipster I am ;)

  • Michiel Scholten
  • Home
  • 🕔

How to do location-based web requests with Tasker on Android

If you don't know Tasker go and check it out now. It's by far the greatest way to automate, well, about everything on your Android device and its surroundings.

However, one thing it is not, is really easy to start with. If you tap through the interface for the first (and second and third) time you will likely be wondering how to even start thinkering with the idea you have.

So, in the light of the ns-notifications service I recently completely rebuilt, I'm going to have a small step-by-step guide to set up a new Profile, which triggers a Task when arriving at a certain wifi network (say, your work office or your home) and then do an http request. This will show the basics of how Tasker works and how to set up your own scripts.

A Profile is really just a set of triggers (or just one generally) that are linked to a Task to perform when activated by the situation. There is also an exit state, so if the situation stops, another Task can be run (for example, enable wifi when your home network is detected, disable it again when the network goes out of reach).

To start, go to the Profiles tab and tap the + button in the bottom bar. You will be presented with a small list of trigger types. Because being in range of a certain network is state and not an event for example, now tap State and choose Net in the next overview. 'Wifi Near' is the option we are now looking for. This is a service of your device (provided for example by the background scanning Android can do for you) that tells your Android what wifi networks are in range.

Now you are in the properties window of the State. Tap the search button next to SSID (looking glass icon) and choose the relevant network. You can choose more options, but for our use case, this is enough. Now go Back.

A menu pops up with a list of Tasks already configured, with at the top 'New Task'. Choose this item and type a name. You've now created a new Task that will be triggered when coming in reach of the wifi network.

This empty window is the script window. Here you can add Actions. Do so by tapping the now familiar + button. You can add an Alert for testing. If you choose an Action by accident and don't want to save, choose Cancel (might be in the overflow menu on your phone). Otherwise, configure if there is anything to set and go Back.

To create our web request, choose a Net task called 'HTTP Get'. Fill in the server and port (if applicable), so for example: ''. The of ns-notifications listens on port 8086 by default, so you might want to use that one instead. Under Path you put the rest of the url you want to request, so for example 'disable/work' to trigger the disable alerts on the server, noting you arrived at work. You can leave the rest empty, but might want to put in a timeout of about 10 seconds or so to be save the Task will not hang when connectivity fails.

We are done :) You successfully made a web request on entering a certain wifi network. Or did we do so successfully? The 'HTTP Get' Action sets a variable when exiting successfully, which you can check. This variable is called %HTTPD (all variables in Tasker are prepended with a %).

So, add an Action: Task, If. Condition for the If is %HTTPD and choose 'Set' from the button next to the input field. This if statement checks whether the variable is set. You can now for example show an alert on your phone that everything went well, adding an Else with an alert that your request failed.

NS alerts disable because of arriving at %atLocation

Response: %HTTPR %HTTPD

The response variable '%HTTPR' shows the status code of the response (generally 200, but -1 in case of network failure) and '%HTTPD' shows the response body ('Disabling notifications') or just the string '%HTTPD' itself in case of network failure.

%atLocation is a global variable I set myself based on my travels. More on that in a later post.

Further reading

A more elaborate example for actually retrieving information through a web call can be found on this http-get example on the Tasker wiki. Also, I just found this page which is a continuation of a tutorial you might want to play with.

There's also the Tasker Google+ community with people writing tutorials, scripts and answering questions.

  • Michiel Scholten
  • Train
  • 🕔

How to smooth up your Firefox on Linux, including video

After having one of our Hangouts videocalls, I started looking into why the browser plugin takes so much CPU-time (and makes the laptop hot and breezy in the process). Is stumbled upon the article How to tell if you're using hardware acceleration and dove into my own about:support page in Firefox. Lo and behold, the information behind 'GPU Accelerated Windows' told me it did no hardware acceleration (it showed the 0/1). OK, so how to fix?

Mozilla decided a while ago to blacklist Intel GPU's from hardware acceleration, based on the state of the Intel drivers. However, as I'm running Ubuntu 15.04 on this machine with a recent enough graphical stack, I'd say that my Intel driver would be recent enough. Still, Firefox says no.

On to force enabling it then. More digging revealed that some settings in about:config could be turned to true to enable rendering through hardware. Open a new tab, type about:config and search for those two:


If they are false, just double click them to toggle the value to true. It seems that only the first is really necessary, but lots of people need the second to be enabled to, so I did that. Restarting Firefox didn't show any improvement though, as it needs an environment variable too.

On a terminal, type export MOZ_USE_OMTC=1 and then start Firefox from there (firefox). Now, if you open about:support again, you should now see something better than '0/1' behind the 'GPU Accelerated Windows'. Of course, we want this environment variable be automatically set so we can start Firefox from a menu item; I looked at this forum thread discussing some ways of making this persistent and choose to add it to ~/.profile.

Source that most forum threads ultimately pointed to: No more main-thread OpenGL in Firefox (important note for Linux users who use OpenGL)

So, recap:

- set environment variable MOZ_USE_OMTC=1
- set firefox setting layers.offmainthreadcomposition.enabled = true
(both are necessary)

Most likely layers.acceleration.force-enabled = true is also required

It might be me (or the placebo effect), but scrolling and video's (and even Google Maps) seem to be more responsive after all of this.

  • Michiel Scholten
  • Work
  • 🕔

« Old rants