Wednesday, October 11, 2017

Voyager Golden Record images - processing considerations

As you may have seen in my previous post, decoding the images on the "Golden Record" is not very difficult. The problems arise in timing and amplitude issues.

I've talked about timing in my previous post: there "sample rate" varies throughout the record (slowly increasing speed) and it also "flutters" throughout a frame.

The amplitude issues are caused by feeding digital signal into an unconditioned/untuned analog system (probably a recording needle).

Let's analyze the circle image:

I will use the [vertical] scanline represented by the red dot as illustration for how a uniform background (constant color) should look.

The blue trace is the actual waveform. My red line underlines the non-linearity of the recording. The yellow line signifies my expected value. So we have an inverse exponential gain that needs to be applied with respect to time, as well as a linear gain. The could be mixed into a single formula.

For reference, the actual image on the record should be this one:

Remember that the signals are inverted, so white would actually be a very low signal, close to the minimum. Hence my interpretation on where the yellow line should sit.

Back to the first image, analyzing the scanline next to the blue circle:

Highlighted in red are the level differences: after meeting a peak, the signal recovers to a lower level than the background one. Lower level = lighter pixels.

The opposite happens after a white portion (low signal level), exemplified in the following image:

The portion below ("after", in time) the white text is darkened. Picking out a random scanline from the waveform:

It's pretty hard to determine what difference is caused by the non-linear time-dependent gain distortion and what is caused by the peak/dip recovery.

Another non-linear distortion, perhaps related to all of the above, can be seen in the lead-out (postamble) of the frame:

After a wide peak (odd lines), the dip is more pronounced - longer recovery time with overshoot. After a narrow peak (even lines) the signal is more linear and with little overshoot.
The analog recording system can be characterized using the data and a "reverse" system can be designed, which corrects for at least some errors.

I can only speculate what the causes for those errors are: output vs input impedance (capacitance), needle bouncing, slow and non-linear amplifiers (remember this was the mid-70s). Whatever the cause should be, I have confidence that the engineers at that time did their best to create a signal as clean as possible. We also have to take into account that the digitization process might not have been perfect, it never is.

That's as far as I will go with this today. As a "cookbook", in order to decode better images one needs to:

  • apply a multiplier to the grayscale signal, like: signal *= a^time + time/b - where a is between 0.5 and 0.999 and b < 0
  • apply some sort of reverse impulse recovery: design a FIR filter that based on the slope of a positive/negative impulse it applies a proportional feedback. That is, a high-slope positive signal should provide positive feedback
  • take into consideration whether decoding an odd or even scanline
Unfortunately the extent of my digital signal processing skills stops well ahead of a proper implementation.

On an unrelated note, it would be interesting to find out if the encoded images carry some metadata: whether they are part of a color image, which channel, whether they are in portrait or landscape orientation, if they have an ID.

Decoding Voyager Golden Record images - in Java

My inspiration started when I first saw the associated video for this article:

Without studying much further, I decided to download the sound files and try my hand at decoding the images - more as a programming exercise.

I haven't studied the original article too much as I didn't want any spoilers. After struggling a bit with the data, I found that the article could not help anyway with the problems I was having. So there's the "cheater" disclaimer.

Watch the end of this post for updates.

Data preparation

Load the sound file into Audacity, perform a normalization (Effect -> Normalize) to 0dB.
Then click the black triangle above the track name and do "Split Stereo to Mono".
Select one channel (entire width), File -> Export Selected Audio. I used WAV 16-bit PCM as I couldn't get MP3 to work properly in Java.

That's how the waveform should look. Get familiar with navigating around it (Ctrl+Wheel to zoom) and measuring milliseconds or sample duration. The switch can be done by clicking the small dropdowns where the selection start/end/length is listed.

I will be using "Track 1" throughout this article (left channel) only, but the other channel can be analyzed in an identical way.

I will use samples, milliseconds and seconds interchangeably. Since the data is sampled at 48000Hz: 0.001s = 1.0 ms = 48 samples = 1000Hz.

Cold analysis

The sound file start off with a 33.63 seconds of silence, followed by what seems to be a square wave gone through a low-pass-filter. This lasts another 33s. Each square wave measures 399-400 samples in width, so the audible sound would be ~120Hz

This is followed by 4.7 seconds of an almost pure square wave, with a width of 40 samples, so a frequency of 1920Hz

The above data is a preamble for the whole disc (platter?), as we shall see. It allows the "aliens" to tune in on our frequencies and perhaps test the stability of their "vinyl player".

The first frame of data

I - doesn't seem like something significant, it's just a burst of 40 samples followed by a repetition of a 400 sample signal.

What's interesting is that the peaks at those 400 sample intervals vary in width: ~19 samples for each even peak, ~6 samples for each odd peak. This will be a recurring theme.

II - a train of squarewaves, each 25 samples wide. They seem to be grouped into 16 "bits", with the last bit being higher in amplitude.

In total there seem to be about 21x16 bits, but it's hard to recover more information from the audio sample I have access to. I will refer to this as the frame preamble.

III - frame data, will detail later

IV - frame postamble

V - start of next frame

The frame data is split into lines, I call them "scanlines", each 399-400 samples wide, so around 8.315ms.

Each scanline represents analog video intensity, from the lowest point (trough) up to the highest point (peak) into the waveform.
Analyzing the peaks we can see that they follow the same pattern: each odd line has a 6-sample-wide trailing peak, each even line has a 19-sample-wide one.

While it's easy to determine where each frame starts and ends using our eyes, getting a computer to do that for us is a bit more difficult. Intuitively, the frame should start at the lowest value. However, this gets a bit complicated with images that have a  high contrast:

The number of scanlines seems to be pretty constant, around 500 for each frame, however the postamble (IV-V above) varies quite a bit. It might contain some extra information. It follows the same layout and spacing as the scanline and is ~665 lines in some frames and even 807 lines in others

Friday, October 6, 2017

Zoom 9002 hardcore repair - basket case

Many years ago (circa 2004) I bought a used Zoom 9002. This worked fine for a while until the output became weaker and weaker. At some point it stopped amplifying completely. I looked online, there was at least one blog post mentioning that some electrolytic caps and the ICL7660 need to be replaced. I did that, nothing improved. I think the display also started failing.

"Smart" as I was at the time, I decided that perhaps the board needed some reflowing, so I stuck it in an oven for a few minutes. Packed it good. Fast forward 10 years later:

Initial condition

This was literally a "basket case" - all the parts were in a basket, with no ideea what goes where.

Thursday, September 21, 2017

Blackview A7 quick review

This a departure from my normal topics as there seems to be little information online about the phone. Paid ~32€ (38 USD)  for a shipped unit, so try to keep the price in mind. A unit with similar features would cost today at least 55€.

In the box

Phone, TPU shell, charger, cable, small leaflet.
No earphones, no extra screen protectors.

Compared to a Motorola Razr XT910, it looks like a monster.


Tuesday, August 15, 2017

A look inside the Samsung S7 camera implementation

This post is less of a guide and more of a collection of thoughts. It assumes some Java and camera operation knowledge.

My S7 has become my main camera since it handles low-light, uploading, water and time zones very well. It does have a few big weakness though, oversharpening being one of them and over-confidence the other. By over-confidence I mean it will gladly lower the shutter speed to 1/4s, thinking OIS will take care of the shake. It can't.

The Samsung camera app on Android Nougat has shed a lot of features in exchange of user-friendliness. Gone are the sharpness controls, OIS cannot be disabled, RAW mode is available only in the "Pro" camera mode. Quite a lot of artificial limitations which third-party apps don't seem to bring back.


To analyze the application we need to take a look at some binaries: SamsungCamera6.apk, semcamera.jar and seccamera.jar. There are some other binaries but they are written in native C ( or even for FPGA (e.g. fimc_is_fw2_2l1.bin).

For taking a look inside the jars I'm using dex-tools 2.1, jd-gui 1.4.0 and Eclipse. The classes.dex file is extracted from each app, sent to dex2jar, imported into jd-gui and saved as a zip collection of .java files, then imported into an Eclipse project. There's probably a better workflow for this.

From those various jar files a structure emerges: SemCamera is the bridge between the native camera binary and Android/Java.
FaceAreaManager seems to take care of face detection, HRMSensorFusion enables use of the HR sensor to take selfies.

The camera modes are defined inside the application, even though they need to be downloaded in order to be used:

Interestingly enough, there are a few camera modes which do not have an equivalent app in the Samsung App Store: Antifog, BurstPanorama, Night / NightScene, ProductSearch, ProLite, RichTone, TagShot.

Antifog might be targeted towards the Asian market, as a mode that reduces smog smear. Night might be a sub-mode for the Auto. ProLite might be a Pro implementation for phones with less features (Samsung A7). RichTone might be a sub-mode for Auto+HDR.

I find it both curious and disturbing that features are being hidden from the user at the cost of a "purchase". Not sure if the idea was to monetize camera modes or to simplify camera usage. A simple checkbox list would've sufficed.

Saturday, August 5, 2017

Failed project - WiFi to InfraRed to Coffee

In one of my most-viewed articles, from quite a few years ago, I've teared down a Saeco Talea Coffee machine and noticed a strange protrusion on top. I assumed it was an IrDa transceiver, then thought that maybe it was a coffee cup detector.

It indeed turned out to be an infrared port.

Armed with this knowledge - and some time to kill - I've decided to try and talk to the machine, wirelessly.
While it might sound easy, there are many steps involved: get hold of the service tool application, reverse engineer (RE) it, RE the service dongle, RE the machine protocol, write an IrDa implementation for Arduino, write the web app to serve the page - and coffee.

Step 1 - .NET reverse engineering

Ever since I've discovered JetBrains' DotPeek, my life has been changed. I don't know C#, in fact I barely know C, but I can pretend to be a .NET developer.
After getting hold of the SSC2 application (that's another topic in itself) we can try to see how it can talk to the machine.

The baud rate for talking to the serial tool is 19200, for my machine. Since the baud rate is different for other machines, I could make an educated guess and assume that the serial tool is just a transparent proxy between a USB to serial converter and either IR or RS232.
The serial protocol is 8N1 - 8 bits, no parity, 1 start and 1 stop bit - pretty standard.

Let's see what we can speak:

The program sends the "(MA)\r\n" command on a button press.
Let's see where Button17 is declared.

That button has a text called "BU up", which in slang means "Move brew unit to the upper position".

Similarly, we can find a pretty long list of commands that can be sent.

For convenience, here's a shortened version: (COMM), (MA), (MB), (GA), (TEST), (CONF), ERASE, S0, S1, (GB), (PA), (TA), (TB), (TC), (TD), (EXIT), (TEST).

Here's a interesting one
      this.Button4.TabIndex = 11;
      this.Button4.Text = "make Espresso";
      this.Button4.UseVisualStyleBackColor = true;
It just sends "(EXIT)" :)

Tuesday, August 1, 2017

Aggregated updates August 2017

I haven't had much time to write full articles but I can go through a quick rundown until I tackle each subject:

CTC 3D Dual (Bizer) Printer

Will create a separate article about the mods and results.

I've re-tightened all the screws and tried to take out the backlash of the Z carrier. This has improved the jagged edges a bit and also reduced the noise.

I made a small DIY enclosure: A4 plastic sheet on one side, the glass from a picture frame in the front, the particle wood panel from that frame in the other side. The glass frame was affixed with some 3D printed clamps as well as a modified Tic-Tac box.
On the top of the printer I've cut some Ikea Schottis blinds (3$) that fold with the moving head. I found out afterwards that I'm not the first one to do this:

I've cut a few pieces of 2.5-3mm glass to the bed dimensions. Rather, I've had a glass manufacturing company cut them for me (<10$). Then from the remains of another Tic-Tac box some springy corners were created that keep the glass in place. This allows the glass to be easily removed and replaced with great repeatability.

With the covers in place the printer gets warmer quicker and ABS can finally be printed, though still a bit fiddly. It also reduces dust a lot.

Some nice LED lights were also added, allowing to take better pictures of the things.

Lenovo Thinkpad Backlight control

I've partially reverse-engineered the Win10 app that controls the keyboard backlight and now I have a Macbook-like backlight behavior on my X230. Source code and binaries will be published as soon as I clean them up, there is already an alpha version available for testing. I've been using that for at least a month and it's been running great.

Saeco infrared port

The mystery part on my Saeco Saleo teardown article was indeed an IrDa transceiver. I thought that it was just a coffee cup sensor, to keep the cup warm. I've reverse engineered the Saeco Service Center tool (SSC) and figured out how to talk to the machine - in theory - including how the checksum is calculated.
I've seen a few pictures of the service unit and it looks to be just a USB to UART to IrDa converter. Hopefully.
The next step would be to build an ESP8266 Wifi-to-IrDa converter. I've read a bit on the IrDa SIR protocol and it looks manageable, even without interrupts, there are quire large tolerances specified.
I think that, with the ESP8266 build, the SSC tool should be able to directly talk to the coffee machine

Automotive-type projects

The DieselBooster is still in tests, the fuel savings are quite a lot lower than anticipated, but still tangible. I've had nobody contact me about publishing the latest sources, so I've delayed that.

I've designed a module that allows a 3S LiPo battery to be left in parallel with the lead-acid ones, especially for motorcycle use. It will boost the voltage on cranking, allow it to be recharged up to 12.4V and generally keep the LiPo safe from overcharge and discharge. I will still need to build the project and test it, but it looks fine so far.

I've built a module that interfaces with Hall speed sensors on motorcycle/scooter wheels. These become weak over time (either the transistors or the magnets, not sure), but it's basically a hysteretic comparator.

At least on my bike, the sensor and cable comes in a single piece and costs 140 EUR second-hand. The module above can be built for ~1$.


I've been meaning to do a test of low-cost batteries for a while now. But, for my German readers, the gist is: Varta, Eneloop, Energizer- they all have their rated capacities or more. Ja! - very close, for a quarter of the price. Lidl/Aldi - junk.
I read all these German reviews that the Lidl/Aldi batteries are being developed by Varta. They very well may be, but the capacity is less than half, shrinking to under a quarter under load (0.5A).
Haven't tested the IKEA batteries yet.
As a price breakdown: you can buy the 4-pack AA Energizer at Rewe for ~6E or more, the Ja! batteries are 1.59E. I've tested the Ja! AA batteries to provide at least 1800mAh under small load compared to 2500 for the Energizer.
Here's a snapshot from a work-in-progress:

Sunday, July 9, 2017

FiberHome AN5506-02-F router hack

I recently had to work with a home fiber router that was supplied by the ISP,  the FiberHome AN5506-02-F.

Compared to the previous internet access solution, which was based on a cable modem and required the user to use their own router, the new solution has both advantages and disadvantages. The advantages would be: integrated WiFi, security and firewall. The disadvantages: only one LAN port available (@100Mbps), only 2.4GHz (@150Mbps), outdated software, locked-down interface, no easy way to expose a second router.

The unit is very similar to the AN5506-04 model ( ), except it has only 2 UTP ports, only 1 phone port and no CATV interface.

Exposing the inner router

To get around the issue of the (old) router not being accessible from outside, the solution is to add that router into the DMZ setting. This is needed for things like web hosting, ftp server, some chat clients, torrents, etc.

You can log in with your supplied standard username and password, no need for admin rights for this. The usual link is . Write down your old router's MAC address, either from the 'Status -> LAN -> DHCP Clients List' or from its label.

Add the MAC address to the static leases list, just to be sure that the old router will always get the same IP. Might not be needed, but in case something happens you want to be sure that you don't expose the wrong host to the Internet.

Add the IP address from above to the DMZ zone.

Every time your IP will be accessed, the ports exposed to the outside will be the ones on your old router. Assuming the old one is more secure than the new router, this will also improve security.

There are also other ways to do this, but this one is the easiest. Not a hack, just poorly documented functionality.

Studying the firmware

The router home page uses a framed design, with the left frame (./left.asp) consisting on some hardcoded data and JS includes and the right frame being the active UI.

The hardcoded data is a crude state machine to select a different skin or menu structure based on the ISP values.
"checkResult" is the result of the login, with all values except 1,3,4,6,11 being accepted. So you can set it to 0 or 2 to signal the JS that the user is logged in. The check is only done in one place, utils.js, so you can set a breakpoint at the method entry point and override the value:

Each time the script pauses at that line, you can set the checkResult value to a valid one and press continue - most the pages will happily load. You can automate this process with a Tampermonkey script, which could override the security function with a dummy one:
web_access_check = function(i){}

We can already see two critical security problems: only client-side security and unique checkpoint.

Looking further into the request and responses (XHR) I could see that for this version of router/ISP an XML resource is being loaded:

Looks like the menu and submenu layout, this could have been deduced as well by looking at the JS code.
Changing 1.xml to 2.xml yields an advanced menu:

Tracing back how this XML is loaded, leads us back to another semi-hardcoded page:

Long story short, setting a breakpoint just before and setting curUserType to "2" will load the admin version of the UI.

The exposed menu items are not interesting for a normal user (they don't add features or increase speed or anything else) and can brick the router if modified.

I haven't played much more with this, but I suspect the unit might be susceptible to some basic attacks: directory traversal, RCE, privileges elevation, ...

There are some other topic that are left as an exercise for the reader: switching to different skins and languages, dumping the file system, finding out the admin username/password combo.

Quick DieselBooster update 2

As mentioned in the previous post, I tried to set the curve opposite of that the 'tuning' units on the market do. So the reported fuel rail pressure would be higher, causing the ECU to decrease the rail pressure, and, hopefully, fuel consumption.

After a long test test I can say that this is the way to go. Most numbers have been tweaked so that it would result in 15-20% lower pressure.

For mostly highway driving the fuel consumption is now down to 6.28L/100km 7L/100km, with an average speed of 102kph (cruise control set for 130kph). For the same driving conditions, the factory result would be around 7.4-8L/%. This is corroborated by the fact that I was able to drive >1200km with a full tank.

I don't have any numbers yet on city / heavy traffic consumption, it might be a different story.

I don't know the long-term effects on neither reliability nor pollution, the injector nozzle spray pattern might not be suitable for such low pressures.

TL;DR: >15% 6-7% fuel savings with the DieselBooster values set to 115-120%, opposite of the initial ones.

Edit: article was amended after I discovered some miscalculations. I forgot to take into account a mid-trip refueling. New calculation falls within statistical error. This project seems like a fail.

Sunday, May 28, 2017

Quick DieselBooster update

I've been lagging on the project for a long time, but the issue was that the long-term results were not correlated with the initial test data. That, and the fact that I don't use my car that much. Each test requires at least 25L of fuel.
Moreso, 25L (just above a quarter of my tank) is not enough to let the car calibrate its closed loop. Usually it requires a full tank and lots of driving, about 800km each test. It's hard to do a consistent driving cycle for that distance.

My aggregated data shows that the circuit DECREASES mileage now, perhaps with about 10%.  And that is with settings almost identical to the ones used by RaceChip, a bit more aggressive. In other words, with a REPORTED LOWER rail pressure and ACTUAL HIGHER rail pressure the CONSUMPTION INCREASES by ~10%.
I suspect that most of the people that report decreased consumption actually use on-board-computer data and do not perform precise measurements.

So I've changed my strategy now, The reported (fake) rail pressure was increased, so that it yields a lower actual pressure. This means that the software is now set to -20% instead of +20%, so the curve is reversed.
The on-board reported consumption is ~30% higher at idle (1.1L/h stock, 0.7L/h previous, 1.4L/h now). The car stays longer in high gears which also improves mileage. This makes sense, as the engine ECU now thinks the engine has 'more power' so it signals the automatic gearbox ECU to shift earlier up and retarded down. Or something....

Data from the start of the year:

  • 29 litres, 90km since refuel, 11.6/% shown, actual 11.6-12.4/% (tuning unit was not connected)
  • 72.64 litres, 935km since refuel, 7.4/% shown, actual 8.6/%
  • 64.55 litres, 667km since refuel, 8.2/% shown, actual 9.6/%
The driving conditions were not identical, but the actual figures are slightly higher than stock, taking into account the driving style.

I will need at least two more months of testing to draw a definitive conclusion. But the initial conclusion is that most of the aftermarket units that report a [fake] lower rail pressure do not increase mileage. Sorry to burst anyone's bubble, but I'm into that group as well.

The other conclusion is that the circuit and software can still be used as-is, no changes are required if you want to do your own testing. I do have an improved, unpublished, Android application, but it mostly increases usability. If anyone wants the apk for that just drop me a note.

Wednesday, February 22, 2017

Porting OpenWRT to ionik Wifi Cloud Hub

I will probably have to split this article into several parts, as I move along.

In the initial article (see I took a look at the hardware and started doing some basic hacking. However, I quickly ran into limitations and decided to try and port OpenWRT for the platform.

There are quite a few steps and preparations needed to do this, however, this is for documentary purposes only. You don't need to do any of the stuff below (except backup), you can just flash my OpenWRT firmware and be none the wiser. However it might be useful if you want to port it to a new device, I could not find any tutorial about this.

Step 0 - BACKUP

In case everything blows up you will want to be able to restore everything to the factory condition. I first reset the root password to 'admin' by going to this link:

This is not necessary now since some nice people have decoded the factory root password: 91657853.
Connect to the serial pads, log in as root.

I liked to have a Samba share so I could copy the entire file system. So type this inside the shell:

printf '\n[rootFS]\npath=/\nvalid users = admin\nbrowseable = yes\nwritable = yes\n' >>/etc/smb.conf

This creates a Windows share that can be accessed with admin/admin. Copy everything except /dev, /media and /sys to some local storage:
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         1/1/1980  12:00 AM                bin
d-----         1/1/1980  12:00 AM                etc
d-----         1/1/1980  12:00 AM                etc_ro
d-----         1/1/1980  12:00 AM                home
d-----         1/1/1980  12:00 AM                lib
d-----         1/1/1980  12:00 AM                sbin
d-----         1/1/1980  12:00 AM                tmp
d-----         1/1/1980  12:00 AM                usr
d-----         1/1/1980  12:00 AM                var
With a USB stick and an SD card inserted we can type 'mount' to see where these get mounted.

rootfs on / type rootfs (rw)
proc on /proc type proc (rw,relatime)
none on /var type ramfs (rw,relatime)
none on /etc type ramfs (rw,relatime)
none on /tmp type ramfs (rw,relatime)
none on /media type ramfs (rw,relatime)
none on /sys type sysfs (rw,relatime)
none on /dev/pts type devpts (rw,relatime,mode=600)
none on /proc/bus/usb type usbfs (rw,relatime)
mdev on /dev type ramfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,mode=600)
/dev/sdb1 on /media/UBUNTU_16_1 type vfat (rw,relatime,fmask=0000,dmask=0000,allow_utime=0022,codepage=cp950,iocharset=utf8,shortname=mixed,utf8,errors=remount-ro)
/dev/sda2 on /media/OS_X_Base_System type hfsplus (rw,relatime,umask=0,uid=0,gid=0,nls=utf8)

It seems sdb corresponds to the USB stick and sda to the SD card. There is an EFI partition (sda1) which gets ignored.
We need to do a block dump of the entire flash contents.

# dd if=/dev/mtdblock0 of=/media/UBUNTU_16_1/mtdblock0
16384+0 records in
16384+0 records out
# dd if=/dev/mtdblock1 of=/media/UBUNTU_16_1/mtdblock1
384+0 records in
384+0 records out
# dd if=/dev/mtdblock2 of=/media/UBUNTU_16_1/mtdblock2
128+0 records in
128+0 records out
# dd if=/dev/mtdblock3 of=/media/UBUNTU_16_1/mtdblock3
128+0 records in
128+0 records out
# dd if=/dev/mtdblock4 of=/media/UBUNTU_16_1/mtdblock4
3584+0 records in
3584+0 records out
# dd if=/dev/mtdblock5 of=/media/UBUNTU_16_1/mtdblock5
12160+0 records in
12160+0 records out

Now the flash contents are stored safely on our USB stick.
We could also copy the file system to the USB stick instead of using a network share: cp -RLf /etc_ro /media/UBUNTU_16_1/ionik/etc_ro

Entry points

We need to identify how the flash is partitioned:

# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00800000 00010000 "ALL"
mtd1: 00030000 00010000 "Bootloader"
mtd2: 00010000 00010000 "Config"
mtd3: 00010000 00010000 "Factory"
mtd4: 001c0000 00010000 "MiniSystem"
mtd5: 005f0000 00010000 "Kernel"

  • mtd0: 0x00000-0x800000 entire flash contents
  • mtd1: 0x00000-0x30000 is the uBoot bootloader
  • mtd2: 0x30000-0x40000 stores the user-defined configuration
  • mtd3: 0x40000-0x50000 stores some internal configuration (WiFi registers?)
  • mtd4: 0x50000-0x210000 is the failsafe (recovery) firmware
  • mtd5: 0x21000-0x800000 is the normal firmware

How booting works

When the CPU starts up it goes to the first address inside the flash memory to look for an executable. It finds uBoot at position 0x00 and hands over control to that. uBoot then does some housekeeping and decides where to jump next.
In the case of this device, if the reset button is kept pressed while powering on, uBoot will jump to 0x50000 (mtd4), otherwise it will jump to 0x21000 (mtd5).

Normal boot
3: System Boot system code via Flash.
## Booting image at bc210000 ...
raspi_read: from:210000 len:40
.   Image Name:   Linux Kernel Image
   Created:      2013-09-09   8:53:17 UTC
   Image Type:   MIPS Linux Kernel Image (lzma compressed)
   Data Size:    6025942 Bytes =  5.7 MB
   Load Address: 80000000
   Entry Point:  8000c310
raspi_read: from:210040 len:5bf2d6
 Failsafe (minisystem) boot:
7: System Boot mini system code via Flash.
## Booting image at bc050000 ...
raspi_read: from:50000 len:40
.   Image Name:   Linux Kernel Image
   Created:      2013-03-11   6:20:33 UTC
   Image Type:   MIPS Linux Kernel Image (lzma compressed)
   Data Size:    1649876 Bytes =  1.6 MB
   Load Address: 80000000
   Entry Point:  8000c310
raspi_read: from:50040 len:192cd4

I don't know much else to tell you, this is all pretty new to me as well. Think of uBoot as "grub" on your computer.


It would be nice to be able to read and write to GPIO pins. I haven't made much progress here, or perhaps the system is limited in this regard. Here are my notes:

blue power on, not charging (yellow off), blue activity off(?), reset off # gpio r
gpio 27~22 = 0x28   ->            10 1000
gpio 21~00 = 0x7c81 -> 111 1100 1000 0001
above + led yellow (charge on):
gpio 27~22 = 0x28
gpio 21~00 = 0x7c81
reset pressed:
gpio 27~22 = 0x28   ->            10 1000
gpio 21~00 = 0x7881 -> 111 1000 1000 0001
failsafe mode, magenta(?) led on, yellow on, blue off
gpio 27~22 = 0x28
gpio 21~00 = 0x7c81
red led blinking # gpio l 9 1 1 10 1 5
led=9, on=1, off=1, blinks,=10, reset=1, time=5

Conclusion: wifi led (blue), power led (blue), charge led (yellow) seem to be hardwired, Red led is bit 9+1 of GPIO; reset is pin 10+1
There's also a low battery indicator that I was not able to read or write to.

Getting dirty - OpenWRT configuration

The first step is to retrieve the repository, set up all the tools and dependencies. I will not go into those details as they are a moving target.

We already know from the teardown that the CPU is Ralink RT5350F. Luckily, OpenWRT already provides this platform for us under the name rt305x.

Add the following entry into /target/linux/ramips/base-files/lib/ :

+ *"i.onik Wi-Fi Cloud Hub")
+ name="ionik-cloud-hub"
+ ;;

Then this under /target/linux/ramips/base-files/lib/upgrade/, under the ip2202 entry :

+ ionik-cloud-hub|\

Not sure how correct is that. I think this file controls how the original (factory) firmware does checksumming in order to get your "trojan" firmware accepted as an upgrade.

Then this under /target/linux/ramips/image/ :

+Image/Build/Profile/IONIKCLOUDHUB=$(call BuildFirmware/Default8M/$(1),$(1),ionik-cloud-hub,IONIKCLOUDHUB,Linux Kernel Image) 

....and a bit lower inside the file...

+ $(call Image/Build/Profile/IONIKCLOUDHUB,$(1))

I found out that - after a test build - my custom firmware only recognized 32M of RAM instead of the 64M that are available. So modify the CONFIG_CMDLINE parameter inside target/linux/ramips/rt305x/config-4.4 b/target/linux/ramips/rt305x/config-4.4 :

+CONFIG_CMDLINE="rootfstype=squashfs,jffs2 mem=64M" 

Specifying the network interfaces, I probably goofed on this but it still works, add the platform inside target/linux/ramips/base-files/etc/board.d/02_network :
@@ -142,6 +142,7 @@ ramips_setup_interfaces()
  "0:lan" "1:wan" "6@eth0"
+ ionik-cloud-hub|\

I wanted to flash the red LED when booting but it did not work. target/linux/ramips/base-files/etc/ :
@@ -138,6 +138,7 @@ get_status_led() {
+ ionik-cloud-hub|\

I also want the wireless to be up after booting since the 'router' lacks an ethernet port. This could be done in a better way, with a default password or one based on MAC. So from package/kernel/mac80211/files/lib/wifi/  you need to comment out the line that says 'option disabled 1'.

Then we need to add a file inside target/linux/ramips/rt305x/profiles called
define Profile/IONIKCLOUDHUB
kmod-ledtrig-netdev kmod-ledtrig-timer kmod-leds-gpio \
kmod-usb-core kmod-usb-ohci kmod-usb2 kmod-usb-net usbutils \
kmod-scsi-core kmod-scsi-generic kmod-fs-ext4 kmod-fs-msdos \
kmod-usb-storage kmod-usb-storage-extras block-mount

define Profile/IONIKCLOUDHUB/Description
Package set for i.onik Wi-Fi Cloud Hub
$(eval $(call Profile,IONIKCLOUDHUB))

Getting dirtier - DTS

This was the most painful part for me. To start off, create a file called IONIKCLOUDHUB.dts inside target/linux/ramips/dts.

I started by looking at routers with similar features and copied that file. I think I used some Western Digital WiFi HDD or something.

The DTS files describes the devices available for the Linux subsystem. The most important part however is the flash configuration.

Let's go into a quick walkthrough, I won't pretend to know what everything does:


#include "rt5350.dtsi"

/ {
compatible = "IONIKCLOUDHUB", "ralink,rt5350-soc";
model = "i.onik Wi-Fi Cloud Hub";

Pretty easy, define the product name and compatible platform, include some ready-made devices inside rt5350.dtsi.

gpio-leds {
compatible = "gpio-leds";

status {
label = "ionikcloudhub:red:status";
gpios = <&gpio0 9 1>;

gpio-keys-polled {
compatible = "gpio-keys-polled";
#address-cells = <1>;
#size-cells = <0>;
poll-interval = <20>;

power {
label = "power";
gpios = <&gpio0 0 1>;
linux,code = <0x116>;

reset {
label = "reset";
gpios = <&gpio0 10 1>;
linux,code = <0x198>;

My attempt at specifying the GPIO devices.

&spi0 {
status = "okay";

en25q64@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "jedec,spi-nor";
reg = <0>;
linux,modalias = "m25p80", "en25q64";
spi-max-frequency = <10000000>;

partition@0 {
label = "u-boot";
reg = <0x0 0x30000>;

partition@30000 {
label = "u-boot-env";
reg = <0x30000 0x10000>;

factory: partition@40000 {
label = "factory";
reg = <0x40000 0x10000>;

partition@50000 {
label = "recover";
reg = <0x50000 0x1c0000>;

partition@210040 {
label = "firmware";
reg = <0x210000 0x5f0000>;

This is the most important part. It specifies the flash configuration, for example it tells that uBoot resides between addresses 0x0 and 0x30000. I don't know if this is correct, but it works for me. The important bit is the "firmware" partition at the end. Everything else was mostly guesswork and might be wrong. But it works.

&pinctrl {
state_default: pinctrl0 {
gpio {
ralink,group = "i2c", "jtag", "rgmii", "mdio", "uartf";
ralink,function = "gpio";

&ehci {
status = "okay";

&ohci {
status = "okay";

&wmac {
ralink,mtd-eeprom = <&factory 0>;

I have no idea what anything above does. But every other device seems to have them, so I added them in. EHCI and OHCI refer to USB ports, as far as I can tell.

Building the custom firmware

After making sure all the files above are modified/added, you just need to type "make menuconfig".
Then go wild adding options to your ROM. You will quickly run out of flash space.

An option marked as 'm' means module. The option is compiled but not included inside the image. Rather, you can add it later to your device, temporarily, via a USB stick or similar. It might trigger (on) some other features which will eat precious Flash space. So just add only what you need, check the size of the image, repeat.

To build run "make -j4". This builds the firmware using 4 threads. If anything fails, you will have to build single-threaded and with verbose mode one. Refer to the documentation.

The resulting image will be in the bin/rampis folder:

adminuser@virtualboximagescom-VirtualBox-14:~/openwrt/bin/ramips$ ls -l
total 46132
-rw-r--r--  1       740 Jan 20 16:45 md5sums
-rw-r--r--  1  5812101 Jan 20 16:45 openwrt-ramips-rt305x-ionik-cloud-hub-initramfs-uImage.bin
-rw-r--r--  1 6291460 Jan 20 16:45 openwrt-ramips-rt305x-ionik-cloud-hub-squashfs-sysupgrade.bin
-rw-r--r--  1 5111808 Jan 20 16:45 openwrt-ramips-rt305x-root.squashfs
-rw-r--r--  1 1129526 Jan 20 16:45 openwrt-ramips-rt305x-uImage.bin
-rw-r--r--  1  5810315 Jan 20 16:45 openwrt-ramips-rt305x-uImage-initramfs.bin
-rwxr-xr-x  1 3452468 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux.bin
-rwxr-xr-x  1 3457480 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux.elf
-rwxr-xr-x  1 8071108 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux-initramfs.bin
-rwxr-xr-x  1 8076120 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux-initramfs.elf
drwxr-xr-x 10     4096 Jan 11 22:36 packages
-rw-r--r--  1 1140 Jan 20 16:45 sha256sums

The uImage-initramfs file is the one you need to be looking at. It has to stay under 0x5F0000 bytes (6,225,920 bytes for those stuck in decimal).
But it should not get close to that value, as the remainder of the space is used by OpenWRT to keep user configuration. I recommend to stay under 6,100,000 bytes.
If your image exceeds that size you need to run 'make menuconfig' and play with the options until you get it to fit.

Flashing your first image

If inside the normal (factory) firmware:

# mtd_write write /media/sda1/mtdblock5 Kernel
Unlocking Kernel ...
The system is going down NOW!lock5 to Kernel ...  [w]
Sending SIGTERM to all processes
Requesting system reboot
Restarting system.

Replace the mtdblock5 file with the *squashfs-sysupgrade.bin from above.

Inside the failsafe firmware you can run this:

# mtd_write -r write /media/sda1/openwrt-ramips-rt305x-ionik-cloud-hub-squashfs-sysupgrade.bin mtd5
Unlocking mtd5 ...
The system is goingRestarting to mtd5 ...  [w]

I don't remember the details right now, the above might be reversed, but you can play around if you have a backup. Make sure you are writing to the correct partition and do not touch uBoot and minisystem.

If you mess up, you will have to use an SPI flasher to restore the flash contents. There are various tools and tutorials online, you could probably use a Raspberry PI or Arduino for this, but you would still have to solder the tiny wires or buy a SOIC-8 clip.

After the first OpenWRT image is flashed from the command line, and it works, subsequent images can be uploaded from the web interface.

Update - Pastebin link with the source changes


The build configuration files might be wrong.

Related to the above, the name of the device does not follow any convention. It might also require renaming to make an OpenWRT upgrade possible by uploading from the original firmware's web interface.

I haven't found a way to remove IPv6, it seems to be a bug in the OpenWRT build tools.

I don't know what I'm doing and this is not helped by the fact that I could not get any community support, at least not on the openwrt forums.

I will upload the configuration files and resulting firmware at some point in the near future, remind me of that if you need it before then.

Sunday, February 12, 2017

Designing a better diesel tuning box - part 5 - tweaks and updates

Living close to a densely populated area means that it's hard to do consistent testing without spending a lot of fuel, which is also expensive.

Nevertheless, I took the pen&paper approach and started working my way from the basics.

I studied all the Bosch sensors from this page, used a bit of common sense and figured out that my sensor is a Bosch 0281002691, or similar, with a 180 MPa (26000 psi) nominal rating. This might be wrong but It's a good place to start.

I've already used Torque and my multimeter to get some data and it seems to fit with the sensor I've chosen. It might be wrong, but so far it clicks into place.

Using the data I've gathered I've created a simple JS page that shows some logs and tries to simulate what my module (and sensor) does:,output
(Note that this is the HTML/JS result after I did the interpolation.)

So the basic function is: the ECU commands the pump, this delivers a pressure, my module receives the sensor data (voltage), offsets it, outputs a new voltage to the ECU, the ECU processes it. I missed a step the last time: the ECU will receive the adjusted pressure, output a new pressure, the pump delivers, a new pressure is measured. This can cause oscillations within the engine, as the tuning module attempts to adjust for the new value.

Thursday, February 2, 2017

Automated coffee machine troubleshooting chart

My old post on the Saeco Talea Giro machine teardown (including deprecated one) has had tremendous success and I frequently get questions on how to fix this or that.

I am no specialist on this stuff but I've managed to keep my unit running after it was used in an office environment (>50000 coffees). So for me it's mostly guesswork and some logic, but I'll display this so that you can help figure out the problem with your own unit. I'll try to make this accessible to non-technical people, let me know if some idioms are too advanced.

I will try to update this guide with usual questions, but this is not a replacement for professional servicing.

Before troubleshooting make sure that the unit is cleaned and descaled and has enough water. Use the manual for this, each unit is different. Also, try turn the unit off and on, perhaps leaving it 1h undisturbed. This works around some of the bugs in software (firmware).
Learn the sounds of the machine and try to understand what it does in its normal state. There are several motors and they are easy to identify by sound.

Refer to my original post (from years ago) if you want to understand more about how such a unit is constructed:

Wednesday, January 18, 2017

i.onik Cloud Hub

I bought one of these cheap 'wireless drives' from Amazon:

Currently (early 2017) selling for 15E, I think I paid 12E including shipping. This post will focus on basic features and some early data. Later posts will go into some reverse-engineering, featuring a great YouTuber, LiveOverFlow. If you like reverse-engineering you will surely enjoy his tutorials. By the time you read this he will have probably published his first video showing you how to get into the device.
He also discovered that a very similar device is being sold under the Strontium Mobile Wifi Cloud (Sri-CUBa-3KW) moniker in US and India. However that one is advertised as having a 3000mAh battery while this one only has a 1900mAh one.


The excelent video from LiveOverFlow is up:

Saturday, January 14, 2017

Quick note on Raspberry PI SDCARD reliability

I've been using a 4GB on an rPI "clone" - the Banana PI - and have been happy so far with everything the board has to offer, as a headless server. However, I started getting some errors after ~3 years of usage, with the card and at one point it stopped booting completely. Nice timing as well, it's not fun losing your music streaming server at Christmas time.

Long story short, the card has been used at 80-90% most of the time, which left very little space for wear leveling. I hooked it up to HDMI and could see a lot of error messages on the screen about mmc. So the card was dead and I feared the worst. last backup having been made a month prior to this.

Never fear, just writing imaging the card (Win32DiskImager) and copying the image to another 16GB card worked fine. So if you run into this it might be worth it to just try and duplicate the card to another one. In my case, no other changes were required.