You have reached the personal website of derf / derfnull. Hi!
I enjoy fiddling with hardware and software components, building anything between low-level AVR assembler hacks and full-stack web applications, and just relaxen und watchen das blinkenlichten. Some projects I am involved with are listed here. Every now and then, I also take pictures and write blogposts about whatever is on my mind at the time.
For professional content such as publications and past work experience, please refer to my professional website.
You can reach me by E-Mail (firstname.lastname@example.org) and on IRC (derf0 @ oftc, hackint). My PGP key for E-Mail encryption is 781BB707 1C6BF648 EAEB08A1 100D5BFB 5166E005. I occasionally post stuff on Mastodon (@email@example.com).
I'm a frequent user of public transport and enjoy building API clients and web services to provide as much transit data as I can find in a structured manner. You'll also find some energy measurement utilities and assorted minor hardware hacks here.
Public Transport Websites
Public Transport CLIs
Interface to EFA-based itinerary services
> efa Essen Martinstr Düsseldorf Hbf 14:34 ab Essen Martinstr.: Bstg. 1 Straßenbahn 108 Essen Altenessen Bf Schleife 14:38 an Essen Hauptbahnhof: Bstg. 1 14:47 ab Essen Hauptbahnhof: 2 R-Bahn RE11 (RRX) Düsseldorf Hbf 15:24 an Düsseldorf Hbf: 10
Interface to Deutsche Bahn Wagon Order API
> db-wagenreihung 'Essen Hbf' 723 ▏ G ▕▏ F ▕▏ E ▕▏ D ▕▏ C ▕▏ B ▕▏ A ▕ > 39 38 37 36 35 33 32 31 29 28 27 26 25 23 22 21 >
Interface to HAFAS-based arrival/departure monitors
> hafas-m 'Hamburg Dammtor' 14:43 +45 ICE 2922 Hamburg-Altona 15:10 U 1 Ohlstedt, Hamburg 15:10 +4 Bus 112 Osterbrookplatz, Hamburg 15:10 NBE RB61 Itzehoe
Interface to the Deutsche Bahn IRIS arrival/departure monitor
> db-iris 'Dortmund Hbf' 14:38 +16 IC 2027 Passau Hbf 11 14:39 ABR RE11 Kassel-Wilhelmshöhe 8 14:41 RE 57 Winterberg(Westf) 2 └──── RE 57 Brilon Wald 2 14:41 S 5 Hagen Hbf 5 14:42 S 2 Dortmund Hbf 6 14:45 +1 RE 1 Aachen Hbf 16
Interface to URA-based realtime departure monitors
> ura-m Talbot 14:49:41 52 Aachen Bushof 15:04:47 11 Lichtenbusch 15:05:00 52 Eschweiler Bushof 15:18:00 1 Aachen Bushof 15:19:56 11 Hoengen Markt 15:35:00 1 Schevenhütte
Interface to EFA-based departure monitors
> efa-m Dortmund 'Universität S' 08:32 +1 02 445 Dortmund Am Kai 08:35 3 HB1 Dortmund Technologiezentrum 08:36 3 HB1 Dortmund Eichlinghofen H-Bahn 08:38 02 447 Dortmund Bandelstraße 08:39 2 S1 Dortmund Hbf 08:40 01 447 Dortmund Hacheney
Other CLI Software
vcs-home inspired dotfile manager
> ct a mutt mutt: retrieving package Cloning into 'mutt'... [..] created .muttrc -> /home/derf/packages/mutt/etc/muttrc
Electrocardiograph-like graphical and audible ping
> ekgping ccc.de __________^________^__________^________^______
imlib2 based image viewer
Keysight DLog Viewer
Korad KAxxxxP data logger and controller
MSP430 EnergyTrace viewer
RGB moodlight mod
8x8 LED board with audio-based programmer
DIY silicone chocolate molds
Light and power remote control
Embedded Library Operating System
ESP8266 Lua drivers for embedded devices
Tentacle candles? Sure, why not?
USB ↔ I²C adapter
embedded decompression library
I am no longer working on these projects.
Icinga1 Commandline Interface
CLI password safe
automated mirrorer for webcomics and image galleries
Create thumbnail index for a set of images
save and restore environment variables
Publish multiple MQTT messages at once
Whitelist remote commands via ssh config
- Use mgate.exe HAFAS interface instead of stboard.exe/bhftafel.exe. This introduces several breaking changes in hafas-m, Travel::Status::DE::HAFAS, and Travel::StatuS::DE::HAFAS::Result.
- hafas-m: Options
--urlare no longer supported
- hafas-m now supports journey details by specifying a journey ID instead of a station name.
timekeys are no longer supported. Use
langkey is no longer supported.
urlkey is no longer supported.
modekey is no longer supported. Set
arrivalsto a true value to request arrivals instead of departures.
Travel::Status::DE::HAFAS->new: add optional
cachekey and support for
journeyrequests with optional
new_pconstructor for async requests via Promises.
- Rename Travel::Status::DE::HAFAS::Result to ...::Journey. The accessors
line_noare no longer supported. Introduces several new ones instead.
- The module no longer depends on XML::LibXML
- New dependency: Digest::MD5
After nearly five years of service, my PowerCore+ 26800 power bank broke down recently. After a few weeks with an only intermittently working USB-C output, it stopped providing power altogether – and also stopped accepting power to recharge the battery pack, providing a distinct smell of magic smoke and an internal short circuit instead.
As the power bank is out of warranty anyways, this is a good opportunity for a happy little autopsy.
Caution: This powerbank contains nearly 100 Wh worth of LiIon cells. In normal operation, the (dis)charge PCB is in charge of battery management tasks like short circuit prevention. Disassembling the device exposes raw LiIon cells, which typically do not contain separate protection circuitry. Puncturing, shorting, or otherwise mishandling one of those can lead to fire. Don't disassemble a powerbank unless you know what you are doing.
The top and bottom plastic covers are glued on and can be pried open with moderate effort, revealing four screws each.
After removing the screws and a second (also glued-on) top cover, you can push onto the connector board (top) to coax the cell and PCB assembly out of the case. A good spot for application of force is the plastic surface next to the USB-C port. It's a tight fit, so the assembly won't slide out by itself. Pushing from the bottom won't work.
The LiIon cell layout is 2S4P with balancing. The cells are labeled “LGGBF1L1865”, which appears to correspond to LG's INR18650F1L model.
Each cell is rated as follows:
- Nominal capacity: 3.3 Ah at 3.63 V (12 Wh)
- Charge current: 0.3C (975 mA) nominal, 0.5C (1625 mA) maximum, 4.2 V / 50 mA cut-off
- Discharge current: 0.2C (650 mA) nominal, up to 1.5C (4875 mA) maximum, 2.5V cut-off
For the 2S4P pack, this gives:
- Nominal capacity: 13.2 Ah at 7.26 V (96 Wh)
- Charge current: 0.3C (3.9 A) nominal, 0.5C (6.5 A) maximum, 200mA cut-off
- Discharge current: 0.2C (2.6 A) nominal, up to 1.5C (19.5 A) maximum, 5.0V cut-off
For comparison, the powerbank's product specifications state:
- Capacity: 26.8 Ah at 3.6V (96 Wh)
- Input: Up to 27 W (9 V, 3 A) → Charging probably uses less than 0.3C
- Output: Up to 25 W (20 V, 1.25 A) via USB-C + about 20 W (5 V, 2 A) via USB-A → Discharge current is up to 0.5C (6.6 A)
My mostly discharged cells read 3.18 and 3.20V, respectively, so the cell management chip seems to be operating in a rather conservative voltage range. This should be good for longevity.
The top PCB is only responsible for LED output and button input.
The bottom PCB includes an SC8802 synchronous, bi-directional, 4-switch buck-boost charger controller and a HT66F319 microcontroller.
Even with disconnected batteries, there's a two ohm short circuit between USB-C VCC and USB-C GND. The culprit turned out to be the USB-C plug itself.
USB-C plugs contain a tiny PCB with contacts on both sides that the cable slides onto. In this case, the lower (recessed, non-contact) part of the PCB is embedded into a metal piece for stability. Over time, the metal piece had moved towards the contacts, eventually causing an electrical connection and thus a short circuit. After moving it back, the power bank is working again. I don't trust the USB-C port anymore, though.
Over the past few years, I've been frequently working with I²C environmental sensors for measuring temperature, humidity and so on. Here are some thoughts and observations of sensors and breakout boards I made along the way. Note that this is by no means a proper professional review, you should take everything posted here with a grain of salt.
Minimal drivers for all sensors listed here can be found in the multipass project.
Sensors and Datasheet specs
|-40 .. 80
0 .. 99.9
|±1 @ 0 .. 65
±3 @ 20 .. 80
|-40 .. 85
0 .. 100
300 .. 1100
|±1 @ 0 .. 65
±3 @ 20 .. 80
|-40 .. 85
0 .. 100
300 .. 1100
0 .. ?
|CCS811||TVOC [ppb]||1||?||0 .. 1187|
|±0.2 @ 5 .. 60
|-40 .. 125
0 .. 100
|LM75B||Temperature [°c]||0.125||±2 / ±3||-55 .. +125|
- I²C readout is a multi-step process with special timing requirements
- Reported humidity appears to be far too low on some devices
- max 3.6V; some breakout boards provide LDO and level shifters for 5V operation
- Supports both I²C and SPI; operating mode selected by CSB value
- The breakout boards I am aware of connect VCC to both VDD and VDDIO, making power sequencing with respect to CSB a tad difficult. On some of them, I had to power CSB before providing power to VCC to ensure that the chip starts up in I²C mode.
- IAQ calculation is only possible with a closed-source BLOB provided by Bosch SensorTec, setting that up on a Raspberry Pi is quite easy though.
- In addition to Total Volatile Organic Compound (TVOC), the sensor reports “equivalent CO₂” (eCO₂) data calculated from TVOC. I found these to be unreliable.
- readout is trivial
- SMBus compatible: using it on a Raspberry Pi is a simple as
i2cget -y 1 0x48 0x00 w
- reported humidity appears to be a tad too low
Depending on the configuration of a few GPIO pins during reset, ESP8266 chips can boot into a variety of modes. The most common ones are flash startup (GPIO0 low → execute the program code on a flash chip connected to the ESP8266) and UART download (GPIO0 high → transfer program code from UART to the flash chip).
Most development boards use the serial DTR and RTS lines of their usb-serial converter to allow reset (and boot mode selection) of the ESP8266 by (de)assertion of the DTR/RTS signals. esptool also uses this method when uploading new firmware to the flash.
Usually, things just work™ and an ESP8266 can be used with esptool, nodemcu-uploader, miniterm/screen, and other software. If esptool/nodemcu-uploader work, but miniterm/screen do not (and show repeating gibberish or nothing at all instead), the reason may be unusual DTR/RTS behaviour. I found manual control of DTR/RTS to help in this case:
- Connect to the serial device
- de-assert DTR
- de-assert RTS
- receive a working UART connection
For example, in pyserial-miniterm these signals can be set on startup:
pyserial-miniterm --dtr 0 --rts 0 /dev/ttyUSB0 115200
They can also be toggled at runtime via Ctrl+T Ctrl+D (DTS) and Ctrl+T Ctrl+R (RTS).
$ pyserial-miniterm /dev/ttyUSB0 115200 --- Miniterm on /dev/ttyUSB0 115200,8,N,1 --- --- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- --- DTR inactive --- --- RTS inactive --- Hello, World!
Vindriktning is a cheap USB-C powered particle sensor that uses three colored LEDs to indicate the amount of PM2.5 (i.e., particulate matter with a diameter of less than 2.5µm) as a proxy for indoor air quality. By default, it simply measures PM2.5 and indicates whether air quality is good, not so good, or poor – there is no digital read-out of PM2.5 values.
Luckily, adding an ESP8266 to integrate it with MQTT, HomeAssistant, InfluxDB, or other software is quite easy. However, while most examples use Arduino's C++ dialect for programming, I personally prefer to stick to the NodeMCU Lua firmware on ESP8266 boards. Here is my basic readout code for reference.
function uart_callback(data) if string.byte(data, 1) ~= 0x16 or string.byte(data, 2) ~= 0x11 or string.byte(data, 3) ~= 0x0b then print("invalid header") return end checksum = 0 for i = 1, 20 do checksum = (checksum + string.byte(data, i)) % 256 end if checksum ~= 0 then print("invalid checksum") return end pm25 = string.byte(data, 6) * 256 + string.byte(data, 7) print("pm25 = " .. pm25) end function setup_uart() port = softuart.setup(9600, nil, 2) port:on("data", 20, uart_callback) end setup_uart()
This code assumes that the Vindriktning's TX pin is connected to ESP8266 GPIO4 (labeled "D2" on most esp8266 devboards). As the ESP8266 only has a single RX channel, which we reserve for programming and debugging, I'm using a Software UART implementation. At 9600 baud, that's not an issue.
If you're running a MediaWiki 1.35 with PluggableAuth and LdapAuthentication2, there's two ways of supporting login for LDAP accounts and local accounts.
In LocalSettings.php, set either
$wgPluggableAuth_EnableLocalLogin = true;
$LDAPAuthentication2AllowLocalLogin = true;
They have slightly different UI, but work pretty much the same from a login perspective. However, the LDAPAuthentication2 variant does not support local account creation.
So, if you're getting an error message along the lines of "The supplied
credentials could not be used for account creation" when trying to register
a local account on your MediaWiki instance, you may need to set
$wgPluggableAuth_EnableLocalLogin = true; in your LocalSettings.php.