donderdag 6 april 2017

Linux shell script to fix LibreOffice 5.1's docx "unknown error" word/document.xml issues

I investigated some issues that caused LibreOffice version to error out when opening certain docx files created with Microsoft Office.

Here's the error:

File format error found at # SAXParseException: '[word/document.xml line 2]: unknown error', Stream 'word/document.xml', Line 2, Column 928831(row,col).

After a deep debugging session, it turns out this is caused by some values of the relativeHeight attributes in the word/document.xml file of the docx.

I made a script to workaround the relativeHeight issue by setting all relativeHeight attributes to zero which, according to the docx specification, means infinite.

After fixing this, I ran into another problem where LibreOffice would sometimes duplicate the w:themeColor attribute upon saving in docx format, thereby invalidating the XML. That is also checked and fixed by the code below.

I figured other people might find this useful, so here's my script:

# Fix to workaround LibreOffice 5 docx issues
# Copyleft 2017 (c) Tom Van Braeckel <>

# This fixes these errors I've been getting:

# File format error found at 
# SAXParseException: '[word/document.xml line 2]: unknown error', Stream 'word/document.xml', Line 2, Column 928831(row,col).
# Problematic LibreOffice version:
# --------------------------------
# Version:
# Build ID: 1:5.1.6~rc2-0ubuntu1~xenial1
# CPU Threads: 4; OS Version: Linux 4.11; UI Render: default; 
# Locale: en-US (en_US.UTF-8); Calc: group

if [ -z "$tofix" ]; then
echo "Usage: $0 <filetofix>"
echo "Example: $0 bla.dockx"
exit 1
tofixreal=$(readlink -f "$tofix")

tempdir=$(mktemp -d)
cd "$tempdir"

unzip "$tofixreal"

# Fix relativeHeight issue
sed -i "s/relativeHeight=\"[^\"]\+\"/relativeHeight=\"0\"/g" word/document.xml
# and then after saving in LibreOffice 5.2 docx format, we sometimes need this fix:
sed -i 's/w:themeColor="text1" w:themeColor="text1"/w:themeColor="text1"/g' word/document.xml

zip -r "$tofixreal" *

cd "$cwd"

echo "Done! The file $tofixreal has been cleaned from relativeHeight and themeColor issues."

To use this script, make sure it is executable and do:

./ filename.dockx

donderdag 13 oktober 2016

First successful static firing of tiny steel GALCIT rocket motor

We're happy to report that we have made our first tiny steel GALCIT rocket motor and successfully static fired it to thrust!

For this experiment session we had 3 goals:
  1. build and validate prototype of small scale GALCIT melter based on a deep fryer: success!
  2. build simple steel GALCIT rocket motor and cast the propellant: success!
  3. static firing of the test motor: success!

Small scale GALCIT melter 

For this prototype of a small scale GALCIT propellant melter we used a standard deep fryer.

In the deep fryer we placed a used, cleaned metal paint can to use as the crucible of the melter. We left the metal net of the fries in to facilitate the handling of the crucible and to provide an extra barrier between it and the heating elements.

Below is a picture of the melting of the asphalt/bitumen, the fuel component of the propellant.

Melting of bitumen fuel
Before adding the oxidizer, we made a simple wooden cover plate around the crucible to prevent the oxidizer from falling in the oil. There is a small gap between the crucible and the cover plate which we plan not to have in the final version of the melter.

The oxidizer had clumps in it, as expected. We ground and sieved the oxidizer with a standard powder sugar sieve and that worked very well.

Adding the KClO4 oxidizer 

When measuring the temperature of the oil with an independent sensor, it was found to be 176 degrees Celcius rather than the indicated maximum of 190 degrees Celcius. This gives the propellant slightly more viscosity and makes casting in tiny motors (made out of narrow pipes) more challenging.

Tiny steel rocket motor

For the motor we wanted to keep things as simple as possible.

We used a standard 12cm pipe with threads at the ends so it can be closed off with 2 end caps.

The cylinder needs insulation and/or cooling from the heat of the reaction. We used thick paper rolled into a cylinder to achieve ablative cooling and this worked nicely. Surprisingly, the material stood up during the incredibly long burn time. 

The diameter of the propellant grain is 18mm for the right one and 16mm for the left, which has thicker insulation. 

We need a nozzle, of course! Again using the simplest design possible, we went for an end cap with a hole drilled into the center.

To be able to drill the hole into the exact center, we fixated the end cap with a reusable wooden holder. We drilled using a drill press.

Nozzle cap holder
We chose the 3mm hole knowing that it is actually way too big rather than risking a RUD on the first firing test.
Resulting nozzle cap
It is expected that using this simple steel nozzle exit will result in severe erosion because the steel would not be able to resist to the heat of the exhaust for long.

Casting the solid rocket propellant

Casting takes some skill due to the aforementioned high propellant viscosity but we managed to cast it by letting it ooze down from a metal rod into the motor and pushing it down further with the rod.

Casting the propellant
A standard electronic match was installed in the propellant, described previously.

Final configuration without end cap nozzle

Static test firing

For the static firing, we mounted the motor inside a soft plastic bucket, full of with hard sand. The sand serves as a first sound, shock and shrapnel muffler and holds a small low-thrust motor in place very well.

The motor was ignited remotely at a safe distance from behind with a 25cm thick brick wall.

Checkout the slightly long and slightly underwhelming video...

That's a burn time of 3 minutes 29 seconds for a 12cm motor, which is huge!

From this low burn rate (0.57mm/s) is clear that the motor chamber was severely under-pressurized, as intended.  

Rudimentary thrust measurement

We measured the thrust with a simple scale. Not very accurate, but enough to verify that the motor was able to generate thrust. From image analysis, we estimate the thrust to be around 1-2 Newton.



Some thrust was measured, estimated at 1-2 Newton. In the future we'll measure the thrust more accurately and we obviously intend on generating a lot more thrust.

Propellant burn rate

As mentioned, we burned through 120mm of propellant in 209 seconds so that's a burn rate of around 0.57mm/s and we are aiming for a burn rate that's at least 40 times higher.

Burn completeness

All of the GALCIT burned away and the motor was found to be empty and almost clean.

Nozzle erosion

Only minor nozzle erosion was observed, which may be due to the low pressure and exhaust velocity.

Chamber cap thread leakage

It was observed that threads of the nozzle end cap had a slightly darker color up until halfway of the thread and some black residue was present. This leads to the conclusion that some gas slowly crept through the threads over the duration of the burn.

This is expected because we are not using any o-rings or sealing mechanisms for the pressure vessel other than the thread of the pipe. The same pipe filled with water would also start leaking without some seal (such as hemp fibers) on the threads.

Residue buildup

We observed an interesting (and unexpected) residue buildup at the nozzle exit. We're not sure yet what the residue is composed of.

It may be leftover or incompletely burnt propellant, eroded steel, ash or a mixture of a bit of everything.

The sharp edges of the nozzle hole and the small diameter of 3mm might contribute to the buildup of this material at the nozzle exit so this may go away by itself as we transition to smooth nozzles and/or bigger motors.

Residue buildup at nozzle exit


The experiments went very well and being able to make a basic functional steel GALCIT motor was a milestone that we really needed to pass.

The prototype small scale GALCIT melter also worked adequately well so we will be able to use it for future small scale experiments and tests.

dinsdag 27 september 2016

Successful GALCIT 61-C ignition using standard electrical matches

We recently experimented with standard electrical matches to ignite GALCIT 61-C propellant samples in combination with an off-the-shelf 433Mhz wireless pyrotechnic ignition system.

This work is part of our effort to obtain a reliable, cheap and easy method for igniting GALCIT propellant for both small and large scale GALCIT solid propellant rocket motors.

We learned from the experiments that these igniters work very well when used correctly.

The correct way to use these igniters with GALCIT 61-C is to make sure they are embedded in GALCIT 61-C in such a way that they have a layer of at least 1 cm of GALCIT 61-C propellant extending from the tip of the igniters.

This is the wrong way:

Wrong way to embed the igniter in the GALCIT propellant with only a few millimeters of GALCIT at the tip of the igniter

When this igniter is used, a small hole is blown through the GALCIT and the hot flames are just heating up air.

Here's the brief video of the failed ignition:

 Here a closeup of the GALCIT sample that failed to ignite:

Burned through rather than ignited GALCIT sample

This is the corrected way to embed the igniter in GALCIT:

Correct way to embed the igniter in the GALCIT propellant with around 2 cm of GALCIT located at the tip of the igniter

The reason that this works a lot better is that the 2-3 cm long hot flame of the igniter is propelled forward to the tip of the igniter and not (or barely) to the sides. Any GALCIT that is placed will not ignite, although it can still be useful to have it there for practical purposes such as ensuring the igniter adheres properly to the GALCIT.

Below is a picture of the receiver of the standard RF pyrotechnic igniter that we used. We later added an extension cord to ensure it would not get too hot from being close to the burning propellant.

Receiver of the 433Mhz RF pyrotechnic igniter 

In the end, we were able to attain 100% reliable ignition of GALCIT 61-C in 3 out of the 3 tests where we had properly installed the igniter in the propellant.

We were also successful in achieving contact ignition, where a small burning chunk of propellant ignites a larger one that is in contact with it rather than being embedded in it.

Below is a close-up of the contact ignition set-up. The igniter, embedded in a small chunk of GALCIT, is placed underneath the big GALCIT sample:

Contact ignition setup

Contact ignition has the benefit that the igniters do not necessarily need to be embedded in the main propellant grain. Instead, they can be embedded into a small chunk of propellant that is in contact with the main propellant grain, which might reduce the complexity of casting propellant and installing igniters into large scale motors.

Finally, below is a video of one of our successful ignitions:

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