donderdag 11 augustus 2016

Reverse engineering the SLAB HT2000 CO2, temperature and relative humidity sensor

TL;DR: I reverse engineered the SLAB HT2000 CO2, temperature and relative humidity (RH) data logger made by Dongguan Xintai Instrument Corporation. Sourcecode and binaries available at

I found a great CO2, temperature and relative humidity meter with USB connection. It's made by Dongguan Xintai Instrument Co. in China and branded under the name HT-2000. Available online for less than $100, it is quite cheap for this kind of device.

There's only one downside to a lot of cheap Chinese product and this one is no exception.. it came bundled with:
  • no Windows software or drivers, although I found one online
  • no manual, although I found one online
  • no Linux software or drivers
  • no protocol specification
  • no datasheet
So how does one read out the values or communicate with this thing through the USB port?

With the help of the Linux kernel, of course!

Connecting the device to a modern Linux (kernel version 4.7) and running lsusb yields in:

# lsusb
Bus 003 Device 011: ID 10c4:82cd Cygnal Integrated Products, Inc. # this is the one!

Cygnal makes USB-to-serial devices that are supported by the cp120x driver but this device actually reports itself to be a Human Input Device in its USB device descriptor:

# dmesg | tail
[407925.138165] usb 3-6: new full-speed USB device number 10 using xhci_hcd
[407947.073443] usb 3-6: new full-speed USB device number 11 using xhci_hcd
[407947.203629] usb 3-6: New USB device found, idVendor=10c4, idProduct=82cd
[407947.203632] usb 3-6: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[407947.203634] usb 3-6: Product: HT2000
[407947.203635] usb 3-6: Manufacturer: SLAB
[407947.209156] hid-generic 0003:10C4:82CD.0007: hiddev0,hidraw0: USB HID v1.01 Device [SLAB HT2000] on usb-0000:00:14.0-6/input0

And our Linux system creates a /dev/hidraw0 device as a virtual respresentation of the physical hardware device, the CO2 meter.

Manually reading out the device descriptor results in more confirmation that this is a raw HID device:

# lsusb -v -v -v

Bus 003 Device 003: ID 10c4:82cd Cygnal Integrated Products, Inc. 
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x10c4 Cygnal Integrated Products, Inc.
  idProduct          0x82cd 
  bcdDevice            0.00
  iManufacturer           1 SLAB
  iProduct                2 HT2000
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower               64mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0 
      Warning: Descriptor too short
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.01
          bCountryCode            0 Not supported
          bNumDescriptors         2
          bDescriptorType        34 Report
          wDescriptorLength     128
          bDescriptorType        31 (null)
          wDescriptorLength     157
         Report Descriptors: 
           ** UNAVAILABLE **

      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval              10

      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval              10
Device Status:     0x0000
  (Bus Powered)

So we know it's a Human Input Device but we have no further info on how to get data from it.

No data is coming out when just reading from it while the device is making measurements:

sudo head -c 1   /dev/hidraw0 # Never finishes so not a single byte of data comes out here

Time to check out the Linux kernel samples of how to deal with hidraw devices. In the Linux kernel sources (version 4.7) there is an example samples/hidraw/hid-example.c which shows how to read a report from a hidraw device.

This example tool yields the following output:

# sudo ./hid-example /dev/hidraw0 

Report Descriptor Size: 128
Report Descriptor:
6 0 ff 9 1 a1 1 85 1 95 6 75 8 26 ff 0 15 0 9 1 91 2 85 2 95 3c 75 8 26 ff 0 15 0 9 1 91 2 85 3 95 1 75 8 26 ff 0 15 0 9 1 91 2 85 4 95 2 75 8 26 ff 0 15 0 9 1 91 2 85 5 95 1f 75 8 26 ff 0 15 0 9 1 81 2 85 6 95 3c 75 8 26 ff 0 15 0 9 1 81 2 85 7 95 3c 75 8 26 ff 0 15 0 9 1 81 2 85 8 95 3c 75 8 26 ff 0 15 0 9 1 81 2 c0 

Raw Name: SLAB HT2000
Raw Phys: usb-0000:00:14.0-6/input0
Raw Info:
bustype: 3 (USB)
vendor: 0x10c4
product: 0x82cd
ioctl HIDIOCGFEATURE returned: 4
ioctl HIDIOCGFEATURE returned: 61
Report data (not containing the report number):
8 9b a5 22 5 5 9b a3 22 5 5 9b a1 22 5 5 9b a1 22 5 5 9b a1 22 54 5 9b a2 22 54 5 9b a4 22 54 5 9b a5 22 54 5 9b a4 22 84 5 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 

write() wrote 2 bytes
read: Resource temporarily unavailable

The "Report data" looks interesting but we are looking for live measurements and these values don't seem to change when running the program multiple times.

So we need to dig deeper.

Now, looking at the source code of the hidraw example, I noticed that it is trying to read out report number 9. So I set out to read the other report numbers and seeing whether anything useful comes out.

Report numbers 1 to 4 all have the same content as the 9, except that the first byte is different and seems to contain the report number itself.

Report number 5 is interesting though. The report data seems to be live because it is changing slightly after every run:

5 77 0 c4 e1 0 36 2 8c 1 f7 1 90 3 20 0 64 3 b6 b0 3 1 a1 22 2 73 0 0 7 d0 5 9b
5 77 0 c4 c6 0 64 2 8c 1 f9 1 90 3 20 0 64 3 b6 b0 3 0 a1 22 2 75 0 0 7 d0 5 9b
5 77 0 c4 e6 0 64 2 8c 1 fa 1 90 3 20 0 64 3 b6 b0 3 0 a1 22 2 7c 0 0 7 d0 5 9b
5 77 0 c4 c7 0 64 2 8c 1 f9 1 90 3 20 0 64 3 b6 b0 3 0 a1 22 2 7f 0 0 7 d0 5 9b

AHA! That is live data we are seeing there!

Comparing these values to the actual measurements on the display allowed me to spit out the following partial reverse engineered specification for this report #5:


5 77 0 c5 f2 0 64 2 97 1 ee 1 90 3 20 0 64 3 b6 b0 3 0 ff ff 2 e8 0 0 7 d0 ff ff 
  DD D DD DD      T TT H HH                                  C CC


DD D DD DD = a timestamp, seconds since epoch + 2004450700 (magic number)
T TT = the temperature, multiplied by 10 and plus 400 (26.3 degrees Celcius in the example above)
H HH = the humidity, multiplied by 10 (49.4 % R.H. in the example above)
C CC = the CO2 concentration, 744 ppm in the example above

Writing a C program that reads out these values was trivial and published on GitHub.

Future work

Report #6

Report #6 toggles between 2 different outputs:

6 1 3 20 0 64 3 b6 0 57 22 b5 b6 1 57 22 b5 b6 0 0 0 0 7 d0 0 0 0 0 0 0 0 6 ff ff ff 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1


6 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 fe 20 0 64 1 0 1 b0 1 90

Report #7

There seems to be a pattern here, similar to what report #6 outputs:

7 1 3 20 0 64 3 b6 0 57 22 b5 b6 1 57 22 b5 b6 0 64 0 0 7 d0 0 0 0 0 0 0 0 7 ff ff ff 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

More features

According to the specifications, this device also contains memory for at least 10000 measurements and can log in different modes (Immediately, Schedule, Real-time & Roll-over) but I could not figure out how to change the default mode without having the Windows PC software. This means the "REC" button does not do anything because the mode is set to "Immediately" instead of "Manual".

Also, recalling minimal and maximal values should be possible on the display but I have no idea how.

If you have any idea on how to use those extra features, leave a comment please. Thanks!

woensdag 6 juli 2016

Nichrome and black powder electrical pyrotechnic igniter with GALCIT solid rocket propellant

We've made progress on the pyrotechnic igniter. It now uses nichrome wire because it has a high resistance, so it gets very hot when electricity is forced through it.

We wind the nichrome in a spiral so that it is longer, again for more electrical resistance, and place it on a paper rectangle of around 5x10cm.

Nichrome wire spiral

The nichrome wire has a diameter of 0.2mm and when stretched out is about 150mm long. With a resistance of 34 Ohm per meter, that is around 0.51 Ohm resistance in that piece of 150mm nichrome wire. 

Then we pour some black powder over the coil and roll it up into a cylinder, reinforced with some paper tape.

Black powder in paper with nichrome wire spiral inside

As a test we ignited a bit of leftover GALCIT in a heat resistant 2l lab beaker.

Heat resistant, yes, but as it turns out it was not GALCIT heat resistant. 

Our final setup looks like this, placed on a burn pile:

Pyrotechnic igniter installed in a lab beaker 

To get the nichrome to glow red hot, we used a powerful 18V battery from a Makita drill to supply the current through an old UTP cable. But a 4.5A 6V battery should also do the trick.

The ignition went smoothly and the burn was quite spectacular. The jet of fire that was spewed into the air indicated that we were already developing some thrust, which is remarkable with such a huge throat (in this case, beaker) area.

Here's the video:


dinsdag 7 juni 2016

Electrical model rocket black powder igniter with steelwool

Hypothesis: A cheap rocket motor igniter can be made out of steel wool and black powder.

We tried out the concept by sending an electrical current through some steel wool and got it to ignite at 3 Volt and 5 Ampère.

Power required = 3 Volt * 5 Ampère = 15 Watt

Readers note: steel wool requires quite some energy to get it to glow red hot and ignite. But 15 Watts is easily reached by using a powerful battery such as an 28 Volts battery of an electrical hand-held drill.

Here's the video of steel wool igniting at around 15 Watts:

Then we arranged the steel wool on some paper tape:

Poured black powder on top and simply wrapped it up.

We made 3 versions:

  • test #1: a black powder igniter with paper tape, pictured above
  • test #2: a black powder igniter with a plastic straw
  • test #3: a black powder igniter with a post-it, rolled up and taped together 

Test #3 contained a bit too much black powder than strictly necessary, check out the video. It will be discussed in the"Safety Improvements" section below!


  • Small amount (2 g) of steel wool: 0.2 euro
  • Small amount (10 g) of black powder: 0.6 euro
  • Copper wire (10 cm) to connect to the steel wool: 0.2 euro
Total price per igniter: around 1 euro


The hypothesis has been validated; a cheap rocket motor igniter can be made out of steel wool and black powder. The igniter has a power requirement of around 15 Watt.

Safety improvements

To stay focused on safety, we'll conclude every session by trying to find at least one safety improvement to be implemented.

Next time we'll be sure to fixate the camera and leave it unmanned. That will give us a better view of the reaction from close by and will keep us safe at the same time.

Simple do-it-yourself automatic (black)powder mixer

This simple, cheap home-made powder mixer can be used to mix relatively large quantities of powder, such as blackpowder, automatically.

Since a picture says more than a 1000 words, let's start with 160 pictures in rapid succession:

Above: The powder mixer in action. This video says more than 160 x 1000 words.

Note that the final model looks slightly different; in the end we moved one of the rolls to the opposite side of the box so that the mixing container is spinning between the powered and the non-powered roll. This reduces the friction on the container so that it spins around more easily.

Shopping list

  • 1 hand-held drill (battery powered or otherwise is fine) to provide rotation
  • 1 plastic box (cheap at IKEA or simular) to support the axles
  • 2 cardboard cylinders
  • 2 axles (we used metal bars but wood should also work)
  • glue to attach the cylinders to the axles

Mixing container

The mixing happens inside a container. We used a metal container with a plastic lid, which is not ideal for pyrotechnic powders - see the "Safety Improvements" section below.

To ensure proper mixing, we glued a wooden stick to the inside of the mixing container. That way, the powder gets scooped up and dropped down continuously, similar to a concrete mixer.

We also threw in a few plastic cubes, again to have better mixing by creating some "chaos" or randomness in the mixing process.

On the picture below, the wooden stick is barely visible because of the blackness of the powder:

The powder mix container filled with black powder.

The results

This blackpowder is of the best quality that we have made with any dry mixing process so far.

The only superior quality that we obtained in the past was with a liquid mixing processes, such as the process that involves boiling water, a blender and isopropyl alcohol, but that is more cumbersome and the quality we have here will suffice for our igniters.

We will be able to use this powder for our igniter tests, as documented in the article "Electrical model rocket black powder igniter with steelwool".

Safety improvements

In hindsight, mixing black powder in a metal container with a plastic lid is not ideal for safety. Should the black powder ignite for whatever reason, and should the plastic lid fail to release, then  the pressure would build up inside the container and it could potentially blow up. 

In general, it is recommended never to place pyrotechnical substances inside metal or glass containers because they could shatter when the substance ignites. Also, make sure that the container has a lid that easily comes off to ensure no pressure can build up inside. 

woensdag 1 juni 2016

Making GALCIT propellant + test burn

We resumed our GALCIT solid rocket propellant preparation that was started a few days ago.

High-level plan:

  1. Melting the mixture of bitumen and paraffin oil that we made last time
  2. Mixing in 2614.4 grams of potassium perchlorate (KClO4)
  3. Casting the GALCIT into some cardboard test engine tubes and into a small ceramic bowl
  4. Lighting a small amount of GALCIT at atmospheric pressure in the ceramic bowl

Step 1. Melting the mixture of bitumen and paraffin oil

Melting the mixture of bitumen and paraffin oil took around 1 hour at 300 degrees Celcius. This is quite slow because the contact surface area between the melting beaker and the temperature controlled hotplate is quite small. To optimize this, we'll use a crucible with a larger base next time.

For safety, a gas mask and protective gloves were worn during the mixing. The gloves are great, they protect from heat and fluids so they are ideal for bitumen. Without them, this would have been a lot more difficult.

Once the mixture had completely melted, it was quite runny, quite comparable to molten chocolate.

Step 2. Mixing in potassium perchlorate

Mixing in the oxidizer was done in open air for safety reasons, again with protective gear.

The large volume of propellant in comparison with the small surface contact area with the hot plate meant it was difficult to keep the GALCIT hot enough.

And since the oxidizer has quite a high heat capacity (111.35 J/mol·K), every time a quantity of the oxidizer was added, the mixture would cool down and stiffen up. Also the volume would increase, so the unfavorable volume/contact area would worsen at every increment.

In the end, we divided the 2l mixture over two containers and continued with 1l of volume.
That resolved the heating issue and allowed us to finish up our first liter of GALCIT.

The final GALCIT mixture is quite viscose (similar to wet clay) and very sticky.

Step 3. Casting the GALCIT

The viscosity of GALCIT was too high to allow pouring it directly into our cardboard test engine tubes but it was easy enough to scoop it out, smear it into the tubes and pack it together with a stick.

We filled up 3 cardboard test engine tubes that had a clay stopper on the bottom, those still need to be reinforced, fitted with an ignition and nozzled up.

To avoid pockets of air in the propellant, which would dramatically increase the surface area, we just tried to pack it tightly. In the future, we might need a better way to ensure no bubbles get trapped inside to ensure a continuous burn.

Step 4. GALCIT burn test

Since we had a bit of GALCIT left, we decided to do a small test to see whether we could get it to ignite at atmospheric pressure with 10 grams of (relatively poor quality) black powder that we made on the side.

We placed around 14cl (around 300g) into a small ceramic bowl and poured the black powder on top.

This batch of black powder did not easily ignite when placing a burning cigarette into it, although the previous batch did. But that wasn't unexpected, considering the poor quality of the black powder. Luckily, we had a solid plan B for igniting the black powder and the GALCIT, using a simple hand-held propane blow torch.

The blow torch did the trick. We were surprised of how well GALCIT burns at 1 bar, considering it burns optimally at 70-140 bar.

Just take a look at the video, at 1m 10s...

Timeline of the test burn:

  • 1:10 start of blackpowder ignition
  • 1:20 start of GALCIT ignition
  • 2:05 fade out of GALCIT burn (smaller diameter and surface area at bottom of ceramic cup)
  • 2:13 end of GALCIT burn

Total burn time: 45 seconds

A total burn time of 45 seconds is huge for such a small amount of GALCIT. It means the burn rate was very slow, about 0.27 mm/s. Consider the excessively high burn rates (~ 36.5 mm/sec) that have been reported and large burning surface area, estimated at around 40 cm3.

Yet this is to be expected, due to the low atmospheric pressure at which the burn occurred.
GALCIT has a very high pressure exponent so the burn rate increases dramatically with the pressure.

So the time has come to test the propellant at a higher chamber pressure!

maandag 30 mei 2016

First stab at making solid rocket propellant (GALCIT)

Last weekend we took a first stab at making the solid rocket propellant GALCIT.

Long story short: melting bitumen on the temperature controlled hotplate took a lot longer than expected so we weren't able to mix in the potassium perchlorate yet. That's planned for the next session, tomorrow!

Below is a small report of the session with some visuals.


The goal was to make 2l of GALCIT, with the following composition:
  • 76% Potassium perchlorate (KClO4) Superfino from Nitroparis
  • 20% Solid block bitumen from Icopal through Defranq
  • 4% Paraffin oil from the local drugstore, Estfarma
Lots of gratitude to Peter Madsen of the RML spacelab for providing us with this formula and lots of useful pointers. They are doing amazing work.

Also a big thanks to the mentioned suppliers. Each of them went out of their way to guide us to the proper channels for acquiring these products. They seem to be of great quality!

We are making a volume 2l of GALCIT so at the published density of 1.72 kg/l the total propellant mass computes to 3.44 kg.

Applying the above GALCIT composition, that translates to:
  • 76% = 2614.4 g of potassium perchlorate
  • 20% = 688g of solid block bitumen
  • 4% = 137.6 g of paraffin oil
The high-level procedure to make GALCIT is:
  1. Heat paraffine oil
  2. Melt bitumen in paraffin oil
  3. Mix in potassium perchlorate through a fine sieve to remove lumps
This is our block of 10kg bitumen:

Cutting 688g off of it was difficult, as expected. Bart discovered that cutting it with a long, strong, heated knife turned out to be the easiest way to cut slices off. 

I was surprised to see that although bitumen looks heavy, it actually has a low density. Look at all that volume to make up just 689.28g! 

To melt the bitumen, we are using the RSM-05-H by Phoenix Instrument. This hot plate with built-in magnetic stirrer can stir 20l of water and reach up to 550 degrees Celsius. Having accurate temperature control is important to avoid overheating the propellant. Overheating is in any case unlikely because of the big difference in preparation temperature (less than 200 degrees C) and combustion temperature (500 degrees C).

The RSM-05-H has an RS-232 interface to control and monitor it from a computer but we didn't use that this time. And although it came with a probe for accurate in-liquid temperature control, we wanted to spare the poor probe from being dipped into sticky, molten bitumen...

Melting the bitumen took a lot longer than expected. This is not unusual - bitumen just takes a long time to melt. Also, we did the mixing outside with a light breeze, which is great for ventilation but causes quite some heat loss on the hot plate.

To speed up the melting process, we turned the temperature up to 200 degrees C, which is without risk as long as the oxidizer (the potassium perchlorate) is not mixed in yet.

In total, melting 689.28g of bitumen with paraffin oil took around 1 hour and 15 minutes.


Before melting bitumen, we took the time to make various cardboard test-engines to cast the propellant into. Bart knew that the best way to cut cardboard tubes is with a fine metal saw:

While melting, we experimented with a small amount of ignition powder, composed of 80% potassium nitrate (KNO3) and 20% fine carbon powder.

We were unable to ignite this mixture, not by electrically heating a thin copper wire until melting point and neither with open flames. The video isn't quite as spectacular as we hoped, anti-climaxing at around 0:20s with some smoke:

We posit 2 reasons the powder is not igniting:
  • Poor mixing of the ignition powder components
  • Lack of sulfur in the mixture
So next time, we'll add some sulfur to the mix and try to get our hands on an automatic powder mixer.

woensdag 7 januari 2015

3D File System Browser (3dfsb) forked back to life and improved

7 January 2015, Gent - The decades-old (but very cool!) 3D File System Browser tdfsb has been forked back to life and received new features, improvements, and cleanups. Version 1.0 has just been released under its new name: 3dfsb.

Among the new features are tons of additional supported audio and video formats (with GStreamer), better filetype identification (with libmagic), higher resolution textures and a new "lasergun tool" that allows you to zap your files away in the 3D world to delete them!

From the author, Tom Van Braeckel:

"I've been cleaning up and improving a very old 3D File System Browser. The original version was called tdfsb, and had been dormant for 7-13 years.

After trying to get in touch with the original author (Leander Seige) via various channels without reply, I decided it's probably best to fork the original code. Leander was kind enough to release his code under the GPLv2 software license, so no problems there. Thank you, Leander!

The new software is called 3D File System Browser, or 3dfsb for brevity.

The code was, and still is, fairly rough. It's all in one big .c file and comments were few and far between. That's completely understandable, historically speaking. Although I didn't hear from the original author, I imagine that it started out as some kind of a research/educational project to get to know the various libraries it uses.

And that's exactly why I picked it up: to deepen and refresh my GStreamer, OpenGL, SDL and 3D geometry knowledge. Yes, indeed: 3D geometry, of which you need a lot of when rendering 3D worlds! All in all, it's been very educational, and I hope we can keep improving this cool program further.

I'm releasing version 1.0 today for your feedback, testing and enjoyment."

Here's an excerpt of the CHANGELOG:

Version 1.0 (07-01-2015)

Major changes
- Extended audio and video support: more than 100 additional container formats and decoders are now supported through the latest GStreamer 1.4.3
- Better file identification: filetype is now determined by the contents of the file (with libmagic) with the extension of the file as a fallback
- High-resolution video previews: cranked up from the old 256x256 pixels to however high your graphics card supports (eg: 8192x8192)
- More fun: you can now zap away at your files with the lasergun tool! For your own protection, nothing is physically deleted from disk, unless y
ou explicitly configure the program to do so.
- Video input device (eg: webcam) file previews: Video4Linux (V4L2) capture devices are now visible in the 3D world and can be viewed just like your movies!

Minor changes
- Faster navigation by default
- Visible on-screen info by default
- Uncapped framerate by default
- Uncapped texture size by default
- Higher rendering resolution by default
- Lots and lots of code cleanups and comments added
- No more dependency on libsmpeg

This version runs at 1920x1080 resolution while playing 720p H264 video (2048x2048 texture) on a single-core of the Intel Core i7 at 2.90Ghz.

Here are a few screenshots of the new features:

Above: all major audio and video formats are supported through GStreamer. Video texture size went up from 256x256 to however high your GPU can go. Think 8192x8192.

Above: hardware device files (such as webcams) are visible in the 3D world and can be accessed from it.

Above: filetype detection is much more robust and versatile, relying on libmagic to identify a filetype by its contents. The old method, which is based on filename extensions, is used as a fallback.

Above: you can use different tools to operate on your files, for example: blast them with the laser to delete them! And don't worry: for safety reasons, the program doesn't actually delete them from your disk unless you explicitly configure it to do so.

All sources and releases are GPLv2 and published in the 3dfsb repository at GitHub.

If you need any help, including (but not limited to) packaging this software, don't hesitate to contact me or drop a line in the comment section.

zaterdag 8 november 2014

Activating A2DP bluetooth stereo headphones in Ubuntu Trusty (14.04.1 LTS)

For future explorers who might struggle with this, and possibly for myself :-)

1) Install recommended software:

sudo apt-get install pulseaudio pulseaudio-module-bluetooth bluez-audio pavucontrol bluez-firmware bluez-tools blueman

2) Add the below line to the [General] section of /etc/bluetooth/audio.conf:


3) Ensure module-bluetooth-discover is loaded (can't hurt):

pactl load-module module-bluetooth-discover

Note: you can also disable unloading the module-bluetooth-discover this patch.

4) Restart all affected services to take the new configuration into account:

sudo service bluetooth restart; sudo killall pulseaudio

5) Pair your A2DP bluetooth headset and add the trust attribute. You can do this in the graphical manager, blueman-manager. 6) Activate the bluetooth audio sink in a graphical manager (blueman-manager) or on the command line:

bt-audio -c 0C:E0:E4:49:8D:FD

7) Now, in your favorite volume manager, pavucontrol, you can redirect any audio stream to your bluetooth stereo headphones. Enjoy!



vrijdag 31 mei 2013 script

# - login to my online banking website using the challenge-reponse mechanism
# Purpose of this script (work in progress) is to further automate online banking.
# More info:
# Author: Tom Van Braeckel 


if [ -z "$pin" -o `expr length "$pin"` -ne 4 -o -z "$challenge" -o `expr length "$challenge"` -ne 8 ]; then
 echo "Usage: $0  "
 echo "Example: $0 3117 12345678"
 exit 1

# Can you guess what this method does ?
bintodec() {
 echo "obase=10; ibase=2; $bin" | bc

# remove < and " "
compact() {
 while read line; do
  echo "$line" | tr -d '<' | tr -d ' '

# args: command to execute on the card
execOnCard() {
 #echo "Sending $tosend" >&2
 echo "$tosend" | scriptor 2>/dev/null | grep -A 10000 "<"
  # does not work:
# echo "$cla $ins $p1 $p2 $le $data" | scriptor 2>/dev/null | grep "<"

execOnCardRespond() {
 result=$(execOnCard $cla $ins $p1 $p2 $le $data) ; echo "result = $result" >&2
 # get length in hex
 length=$(echo "$result" | cut -d ' ' -f 3); #echo "length = $length"
 execOnCard 00 c0 00 00 $length | compact

# Can you guess what this method does ?
hextobin() {
 echo "ibase=16; obase=2; $hex" | bc

# do a read data with the correct length...
readData() {
 recordLength=$(execOnCard 80 ca 9f $p2 00 | cut -d ' ' -f 3)
 #echo "recordLength = $recordLength" >&2
 # cut off first bytes (status code, length)
 # cut off last bytes (9000Normalprocessing)
 # for some reason we NEED CLA 80 here, 00 doesn't work...
 execOnCard 80 ca 9f $p2 $recordLength | compact | tail -c +7 | head -c -23

readRecord() {
 errorWithLength=$(execOnCard 00 b2 $p1 $p2 00)
 #echo "errorWithLength = $errorWithLength"
 recordLength=$(echo "$errorWithLength" | cut -d ' ' -f 3)
 #echo "recordLength = $recordLength"
 execOnCard 00 b2 $p1 $p2 $recordLength | compact

readRecords() {
 SFI="$1" # five most significant bits
 recNr="$2" # second byte
 lastRecNr="$3" # third byte
 if [ "$SFI" = "1" ]; then
  p2=0c # (sfi << 3) + 4
  echo "I don't know p2 !"
  sleep 3
 for i in `seq $recNr $lastRecNr`; do
  readRecord 0$i $p2

zeropadfront() {
# echo "targetLength = $targetLength"
 while read line; do
#  echo "got line $line"
  length=$(expr length "$line")
  while [ $length -lt $targetLength ]; do
   length=$(expr length "$line")
  echo "$line"

###################### EXECUTION STARTS HERE ####################

# reset
execOnCard reset

#select file/application
aid=A0000000048002 # securecode aut
#aid=A0000000043060 # maestro (debit)
response=$(execOnCardRespond 00 a4 04 00 07 ${aid})
echo $response

# this is needed, otherwise the commands below fail...
PDOL=$(echo "$response" | grep -o -E "9F38........" | tail -c +7)
echo "PDOL = $PDOL"
# for securecode get a PDOL of "9F3501" (eg "9F38039F3501")
# this is the "Terminal Type" - don't know the value, but I use 00...

# get processing options:
# - Application Interchange Profile (AIP)
# - Application File Locator (AFL)
if [ -z "$PDOL" ]; then
 response=$(execOnCardRespond 80 a8 00 00 02 8300); echo $response
 # eg: ...94080801030110010201
 AIP=3800 # this means "initiate", "data auth", RFU
 AFLs="08010301 10010201"
 # PDOL specifies one tag with value of 1 byte, but I don't know which value...
 response=$(execOnCardRespond 80 a8 00 00 03 830100); echo $response
 # 770E82021000940808010100080404009000:Normalprocessing.
 AIP=1000 # this means "initiate"
 AFLs="08010100 08040400"
echo "Found AIP = $AIP and AFLs = $AFLs"

# Read SecureCode records and extract valueable info
cardinfo=$(readRecords 1 1 1)
PAN=$(expr substr "$cardinfo" 9 18)
echo "Application Primary Account Number (PAN) = $PAN"
authinfo=$(readRecords 1 4 4)
bitmap=$(expr substr "$authinfo" 11 22)
echo "Proprietary Authentication Challenge-Reponse Bitmap = $bitmap"

# This works for maestro:
if false; then
 readRecords 1 1 3

 # second group in AFL
 SFI=10 # five most significant bits
 recNr=1 # second byte
 lastRecNr=2 # third byte
 offlineDataRec=1 # fourth byte
 # p2=binary(firstbyte)100 left shift 3 OR 100 ???
 # p2=binary(firstbyte) shift right 3
 # I calculated 84, but it seems to be (13) 14 / (1B) 1C / (23) 24
 # and also 03 04 0B 0C but we found those above
 p2s="14 1C 24"
 for p2 in $p2s; do
  for i in `seq $recNr $lastRecNr`; do
   echo -n "p2 = $p2, recordNr = $i: "
   readRecord 0$i $p2

# get pin try counter length
pinTry=$(readData 17)
echo "pinTry = $pinTry"
if [ "$pinTry" != "02" -a "$pinTry" != "03" ]; then
 echo "WARNING: pinTry < 2 !"
 sleep 15

# get Application Transaction Counter
echo -n "Application Transaction Counter = "
readData 36

# get Last Online ATC Register
echo -n "Last Online ATC Register = "
readData 13

echo -n "Log Entry = "
readData 4D

echo -n "Log Format = "
readData 4F

# submit pin
execOnCard 00 20 00 80 08 24${pin}FFFFFFFFFF

# generate Application RQ Cryptogram
# p1=80=Authorisation Request Cryptogram
countrycode=0056 # OTF mentions 0000, chip&pin uses 0826
currencycode=0978 # OTF mentions 0000, chip&pin uses 0826
date=$(date +"%y%m%d")
# 8000000000 = Terminal Verification Codes: Data authentication was not performed (recommended by optimized to fail)
# 00 = transaction type
# 0000 = ICC Dynamic Number
ARQC_response=$(execOnCardRespond 80 AE 80 00 22 000000000000000000000000${countrycode}8000000000${currencycode}${date}00${challenge}0000${CardholderVerificationMethodResults})
echo "ARQC_response = $ARQC_response"
CID=$(expr substr "$ARQC_response" 11 2)
ATC=$(expr substr "$ARQC_response" 19 4)
AC=$(expr substr "$ARQC_response" 29 16)
#IAD=$(expr substr "$ARQC_response" 51 15)
# testing to see if we get the same response as in chip&pin - we do, so this is OK.
# CID=80; ATC=A52D; AC=AD452EF6BA769E4A; IAD=06770A03A48000; bitmap=00001F00000000000FFFFF00000000008000

# generate Application Cryptogram for challenge 12345678
# 5a33 = Authorisation Response Code, generated by terminal or bank (for online stuff)
IAuthData=0000000000000000000000000000000000000000 # Issuer Authentication Data (NOT Issuer Application Data !)
AC_response=$(execOnCardRespond 80 AE 00 00 2e 5a33000000000000000000000000${countrycode}8000000000${currencycode}${date}00${challenge}0000${CardholderVerificationMethodResults}${IAuthData})
echo "AC_response = $AC_response"
#AC_response="$ARQC_response" # Testing which response we need - normally the second one, but let's try this for a laugh
CID=$(expr substr "$AC_response" 11 2)
ATC=$(expr substr "$AC_response" 19 4)
AC=$(expr substr "$AC_response" 29 16)

# extract response

# length of bitmap is used as the target length for the response
bitmap_hex_length=$(expr length "$bitmap")
bitmap_bin_length=$(expr "$bitmap_hex_length" \* 4)
echo bitmap_hex_length=$bitmap_hex_length, bitmap_bin_length=$bitmap_bin_length

# Do logical AND with bitmap
CIDATCAC_bin=$(hextobin "${CID}${ATC}${AC}${IAD}" | zeropadfront "$bitmap_bin_length")
echo "CIDATCAC_bin = $CIDATCAC_bin"
bitmap_bin=$(hextobin "$bitmap" | zeropadfront "$bitmap_bin_length")
echo "  bitmap_bin = $bitmap_bin"

# if a bit of bitmap = 1, then we add the bit from CIDATCAC_bin to filtered_bin
for bitpos in `seq 1 $bitmap_bin_length`; do
 bitmap_bit=$(expr substr "$bitmap_bin" "$bitpos" 1)
 if [ $bitmap_bit -eq 1 ]; then
  CIDATCAC_bin_bit=$(expr substr "$CIDATCAC_bin" "$bitpos" 1)
echo "filtered_bin = $filtered_bin"
# Note: if ATC>0x26(38) then response = 8 characters
# if ATC=0xFFFF(65536) then response = 12 characters !!! is this correct ?

filtered_dec=$(bintodec $filtered_bin)
echo "RESPONSE = $filtered_dec"


# this is never executed

if false; then
# Make AND of ARQC and Cap bit Filter:
fortis: 77269F2701 00 9F3602 00B7 9F260886F857BEB767E8FB 9F100F 0601560384B0A80701030000000000
kbc:    77269F2701 00 9F3602 0004 9F260831F94BDAB5DF0F38 9F100F 0601560384B0400701030000000000
kbc: 77269F2701 00 9F3602 0005 9F2608B29D6D20BFE004AD 9F100F 0601560384B0400701030000000000
# extracted values
output 00 00B7 86F857BEB767E8FB 0601560384B0A80701030000000000
output 00 0004 31F94BDAB5DF0F38 0601560384B0A80701030000000000
bitmask 00 00FF 000000000003FFFF
filter .. ..04 ...........30F38
binary     0100 110000111100111000
decimal 1249080

Example protocol log:

Collected from a NatWest reader and card performing a respond computation
(ISO 7816, T=0 protocol). Personal details have been redacted.

Command:   00a4040007 (select application)
Proc:      a4
Data:      a0000000048002
Proc:      61
Status:    6112 (more data available)

Command:   00c0000012 (application selected)
Proc:      c0
Data:      6f108407a0000000048002a5055f2d02656e
Proc:      90
Status:    9000 (OK)

Command: 80a8000002 (initiate transaction)
Proc:    a8
Data:    8300
Proc:    61
Status:  6108 (more data available)

Command: 00c0000008 (transaction initiated)
Proc:    c0
Data:    8006100008010100
Proc:    90
Status:  9000 (OK)

Command: 00b2010c00 (get static data length)
Proc:    6c
Status: 6c57 (wrong length)

Command: 00b2010c57 (read static data)
Proc:    b2
Data:    7055
          8e0a 00000000000000000100 (CVM list)
          9f5501 a0 (unknown)
          9f5612 00001f00000000000fffff00000000008000 (bit filter)
          8c15 9f02069f03069f1a0295055f2a029a039c019f3704 (CDOL1)
          8d17 8a029f02069f03069f1a0295055f2a029a039c019f3704 (CDOL2)
Proc:    90
Status: 9000 (OK)

Command: 80ca9f1700 (get PIN try counter length)
Proc:    6c
Status: 6c04 (wrong length)

Command: 80ca9f1704 (get PIN try counter)
Proc:    ca
Data:    9f170103 (3 remaining tries)
Proc:    90
Status:  9000 (OK)

PIN entered: ?

Command: 0020008008 (verify PIN)
Proc:    20
Data:    24xxxxffffffffff
Proc:    90
Status:  9000 (OK)

Challenge entered: 12345678

Command: 80ae80001d (generate ARQC)
Proc:    ae
Data:    0000000000000000000000000000800000000000000000000012345678
Proc:    61
Status:  6114 (more data available)

Command: 00c0000014 (return ARQC)
Proc:    c0
Data:    80 1280 0042b7f9a572da74caff 06770a03a48000
Proc:    90
Status:  9000 (OK)

Command: 80ae00001f (generate AC)
Proc:    ae
Data:    5a330000000000000000000000000000800000000000000000000012345678
Proc:    61
Status:  6114 (more data available)

Command: 00c0000014 (return AAC)
Proc:    c0
Data:    80 1200 00424f1c597723c97d78 06770a03258000
Proc:    90
Status:  9000 (OK)

Response returned: 4822527

donderdag 31 januari 2013

Love is Math As Well

We don't remember what it was like
that first time
what we talked about 
whether we showed each other things 
which we didn't yet have
the stale wines
in the polished vases
warm bodies in winter
whether we read
from closed books
added ourselves together and 
knew we never
would have to divide
together exponential
a multiplication with
in the cross that we
no longer wanted to carry
a conclusive casting out of nines.

[I translated "Liefde is ook Wiskunde" by Bart Plouvier from Dutch to English for the national poetry day in Belgium.]

donderdag 6 september 2012

File type associations with xdg-open and xdg-mime (for openbox) example

Just so I don't forget, here is how to do it - it's very simple, really:

xdg-mime default xarchiver.desktop application/x-compressed-tar

Why ?

When I clicked on a .tar.gz file in chromium, my desktop environment would hang.

This was caused the fact that chromium calls xdg-open to know which program to use to open the .tar.gz file. xdg-open used "xdg-mime query default application/x-compressed-tar" to figure out what application to use (which was none, because I hadn't configured any default application).

After that, xdg-open doesn't stop. If /etc/debian-version exists, it additionally uses run-mailcap to figure out which program to use. And mailcap was configured to run "less" somewhere down the line, which stops and apparently, stops it's parent processes for some unknown reason when run in this configuration.

So I deleted /etc/debian-version because I don't see the point of having it. We'll see if something what breaks.

How ?

xdg-mime will create the file ~/.local/share/applications/mimeapps.list which then contains:

[Default Applications]

zondag 17 juni 2012

Rudimentary PDF to LaTeX conversion in Linux

I finally got around to trying a rudimentary PDF to LaTeX conversion in Linux.

"It's like turning a hamburger into a cow" :-)


./ "filename.pdf"

Example output:

# test.pdf 

PDF to LaTeX conversion script
Copyleft 2012 (c) Tom Van Braeckel <>

WARNING: this is a rudimentary first stab. Proceed with caution.

Checking if all dependencies are found...
Dependency pdftohtml found.
Dependency gnuhtml2latex found.
Dependency pdflatex found.

Converting test.pdf to test.pdfs.html

Fixing up test.pdfs.html to test.pdf_fixedup.html...

Readying test.pdf_fixedup.html for tex conversion in test.pdf_fixedup_ready_for_tex_conversion.html

Converting test.pdf_fixedup_ready_for_tex_conversion.html to test.pdf_frompdf.tex
The resulting file is in test.pdf_frompdf.tex

Fixing up test.pdf_frompdf.tex to test.pdf_frompdf_fixedup.tex

Converting test.pdf_frompdf_fixedup.tex to test.pdf_frompdf_fixedup.pdf for inspection...

Opening test.pdf_frompdf_fixedup.pdf with Evince - you can try another PDF viewer if you like...

Script source code:

dependencies="pdftohtml gnuhtml2latex pdflatex"
echo "PDF to LaTeX conversion script"
echo "Copyleft 2012 (c) Tom Van Braeckel <>"
echo "WARNING: this is a rudimentary first stab. Proceed with caution."
if [ -z "$file" ]; then
echo "Usage: $0 <pdf file>"
echo "The resulting .tex file will be stored somewhere here."
exit 1

echo "Checking if all dependencies are found..."
for dependency in $dependencies; do
which $dependency
if [ $? -ne 0 ]; then
echo "Dependency $dependency not found, install it using:"
echo "sudo apt-get install $dependency"
exit 1
echo "Dependency $dependency found."

echo "Converting $file to ${file}s.html"
pdftohtml -nomerge "$file" "$file".html

echo "Fixing up ${file}s.html to ${file}_fixedup.html..."
# This nasty br in a b causes problems later on
sed "s,<br/></b>,</b><br/>,g" "${file}s.html" > "${file}_fixedup.html"
# ending with bold text menas it is the end of a title and can be on a newline
sed -i "s,</b><br/>\$,</b><br/><br/>,g" "${file}_fixedup.html"
# starting with bold text means it is the start of a title so can be on a new line
sed -i "s,^<b>,<br/><b>,g" "${file}_fixedup.html"
# spaces ?
sed -i "s,\&#160;, ,g" "${file}_fixedup.html"
# there is no use in a space before a newline, and it causes a bogus indent when converting to .tex later on
sed -i "s, <br/>,<br/>,g" "${file}_fixedup.html"
# encoding, although gnuhtml2latex ignores this
sed -i "s,<HEAD>,<HEAD><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />," "${file}_fixedup.html"

echo "Readying ${file}_fixedup.html for tex conversion in ${file}_fixedup_ready_for_tex_conversion.html"
cp "${file}_fixedup.html" "${file}_fixedup_ready_for_tex_conversion.html"
# br is ignored and should be replaced by a newline
sed -i "s,<br/>,<p></p>,g" "${file}_fixedup_ready_for_tex_conversion.html"
# Remove bogus links - this fixes the empty } problem
sed -i "s/<A name=[0-9]\+><\/a>//g" "${file}_fixedup_ready_for_tex_conversion.html"

echo "Converting ${file}_fixedup_ready_for_tex_conversion.html to ${file}_frompdf.tex"
# -c = table of contents
# -s = write to standard out
# -p  Break page after title / table of contents
# -H  use hyperref package to process anchors
# -g images
# -n  Use numbered sections
gnuhtml2latex -c -s -p -H -n "${file}_fixedup_ready_for_tex_conversion.html" > "$file"_frompdf.tex
echo "The resulting file is in ${file}_frompdf.tex"

echo "Fixing up ${file}_frompdf.tex to ${file}_frompdf_fixedup.tex"
sed -i 's/\\par/\\newline/g' "${file}_frompdf.tex"
( cat ; tail -n +7 "${file}_frompdf.tex" ) > "${file}_frompdf_fixedup.tex"

echo "Converting ${file}_frompdf_fixedup.tex to ${file}_frompdf_fixedup.pdf for inspection..."
pdflatex -interaction nonstopmode "${file}_frompdf_fixedup.tex" > tex_to_pdf_errors_and_warnings.txt

echo "Opening ${file}_frompdf_fixedup.pdf with Evince - you can try another PDF viewer if you like..."
evince "$file"_frompdf_fixedup.pdf

The script uses one extra file,, which contains customizations:

\usepackage{a4wide}                     % Iets meer tekst op een bladzijde
\usepackage[dutch]{babel}               % Voor nederlandstalige hyphenatie (woordsplitsing) en het euro-symbool
\usepackage{amsmath}                    % Uitgebreide wiskundige mogelijkheden
\usepackage{amssymb}                    % Voor speciale symbolen zoals de verzameling Z, R...
\usepackage{url}                        % Om url's te verwerken
\usepackage{graphicx}                   % Om figuren te kunnen verwerken
\usepackage[small,bf,hang]{caption}    % Om de captions wat te verbeteren
\usepackage{xspace}                     % Magische spaties na een commando
\usepackage[utf8]{inputenc}           % Om niet ascii karakters rechtstreeks te kunnen typen
\usepackage{float}                      % Om nieuwe float environments aan te maken. Ook optie H!
\usepackage{flafter}                    % Opdat floats niet zouden voorsteken
\usepackage{listings}                   % Voor het weergeven van letterlijke text en codelistings
\usepackage{marvosym}                   % Om het euro symbool te krijgen
\usepackage{eurosym}                   % Om het euro symbool te krijgen
\usepackage{textcomp}                   % Voor onder andere graden celsius
\usepackage{fancyhdr}                   % Voor fancy headers en footers.
\usepackage{graphics}                   % Om figuren te verwerken.
\usepackage[a4paper,plainpages=false]{hyperref}    % Om hyperlinks te hebben in het pdfdocument.

% Definitie algemene macro's
\newcommand{\npar}{\par \vspace{0.2ex }}


\topmargin -0.5in 
\headheight 0.0in
\oddsidemargin -.25in 

[Update] You can also try the following method, using abiword, but the method above yields better results, in my opinion:

abiword --to=tex "filename.pdf"