Ruuvi Firmware version 3.x alpha preview

After some 18 months of hard work, Ruuvi Firmware 3.x is getting close to be ready for beta testers. Here’s a short explanation on how it might affect you.

For the users
Update to 3.x firmware brings no new features to most of the users right now, and you don’t need to update at this time unless you’re using Ruuvi Station on iOS and want to have charts and alerts.

It should be noted that the update will break compatibility with existing Eddystone and Espruino firmwares, and it’s not possible to return the tag to 1.x or 2.x firmwares without a wired connection to the tag. While we haven’t heard of bricked tags, if something goes wrong with the SDK update only way to restore the tag is with a wired connection.

The green led blinks on device activity as before, but currently the button doesn’t have a function.

For the service providers and library developers
The Ruuvi Firmware will no longer have the RAWv1 data format. Already all the new tags shipping with 2.5.9 from our factory broadcast in the RAWv2 format, so if your service works well with the latest RuuviTags from Ruuvi Shop, you don’t have to take any action.

The current RuuviTags still have the RAWv1 mode for backwards compatibility, and you can enter it by pressing the button “B” and observing that the red led starts blinking instead of the default green led. However the 3.x firmware will not have such easy access to the RAWv1 mode, so please update your data parser if you have done something custom.

If your service would benefit from data logged into the RuuviTag itself, you can now read up to 3 days of logged data over GATT. The preview documentation of the GATT communication protocol can be found at but please note that the docs are still a work in progress and might contain errors.

For an example implementation of log reads you can explore @Rinat’s BTKit.

For firmware developers
The firmware has been designed to be modular and with well defined interfaces between modules. This makes it easier for you to support new boards or board variants as well as add new sensors or actuators to the firmware.

The firmware supports 2-way communication over BLE GATT, using Nordic UART Service. The GATT protocol also support reading basic device information using BLE standard Device Information Service and buttonless firmware updates using Nordic UART Service. As mentioned above,

The preview documentation of the GATT communication protocol can be found at but please note that the docs are still a work in progress and might contain errors.

In future the GATT connection will have more extensive options, such as configuring logging intervals etc. However Ruuvi will provide precompiled firmware variants for different use cases so you can just flash your desired firmware and start using them in your application without worrying about setting up the configuration system.

The firmware logs 3.5 days worth of environmental data and the environmental data can be read out via GATT as described above. In future more data, such as acceleration events will be logged.

Multiple board support
The board definitions are in their own repository at The board definition file has information of the pinout and sensors available. The application has responsibility of translating the definitions of the board files to different modules to avoid dependencies between modules, for example maximum I2C data rate on boards is translated to drivers with function task_i2c_init

    switch (RUUVI_BOARD_I2C_FREQ)
        case RUUVI_BOARD_I2C_FREQUENCY_100k:
            config.frequency = RUUVI_INTERFACE_I2C_FREQUENCY_100k;

        case RUUVI_BOARD_I2C_FREQUENCY_250k:
            config.frequency = RUUVI_INTERFACE_I2C_FREQUENCY_250k;

        case RUUVI_BOARD_I2C_FREQUENCY_400k:
            config.frequency = RUUVI_INTERFACE_I2C_FREQUENCY_400k;

            config.frequency = RUUVI_INTERFACE_I2C_FREQUENCY_100k;
            ruuvi_interface_log (RUUVI_INTERFACE_LOG_WARNING,
                                 "Unknown I2C frequency, defaulting to 100k\r\n");

Firmware variants
As of 3.28.13 there’s 4 firmware variants:

Default Test Longlife Longmem
Advertisement interval 1285 ms 211 ms 9900 ms 1285 ms
Logging interval 5 minutes 6 seconds 5 minutes 12 hours
Memory length 3.5 days 1.7 hours 3.5 days 1.5 years
Battery on RuuviTag B TBD, medium TBD, short TBD, long TBD, medium
Runs system tests No Yes No No
Prints status to RTT No Yes No No

Each of these variants serve a different need, and the individual values might be fine tuned later.

The firmware is written with abstractions to implementations via function pointers. This enables the same firmware to use for example SHTC3 and BME280 sensors, the source of data is abstracted away and firmware only knows that it has temperature and humidity data available.

This also allows the drivers module to import sensor manufacturer’s official drivers, as those require only function pointers to data transfers functions. The driver module provides manufacturer driver the information it needs to communicate with the sensors and transforms the information from manufacturer driver to a common format of all Ruuvi drivers. Right now sensors from STM, Bosch and Sensirion are supported.

Continuous Integration
To prevent the firmware collapsing into unmaintainable spaghetti, we use several Continous Integration tools such as Jenkins for producing firmware builds, Ceedling for unit tests, PVS Studio for static code analysis, Doxygen for building documentation, Travis for running and publishing the results of the previous tools and SonarCloud tracking the CI status.

In future the power profile of each board will be taken as well as some integration tests will be run automatically.


Excellent! So it should be relatively “safe” to use that in the RuuviTag device?
I have now gone through the very laborous toolchain installation sequence by Nordic :sweat_smile: , and looks like I can build the firmware with SES. There are several projects: “Kaarle”, “Kalervo”,“Keijo”, and “Ruuvitag_b”. I suppose the latter one is the one I want to use (I am using the master branch)?

Edit: Also as I now can make a hex-file, how I can make a packet for OTA flashing (that is my only option)?

I don’t recommend installing the new firmware unless you have a Ruuvi DevShield available. We haven’t heard of bricked tags, but we don’t have automated test which verifies the SDK12->SDK15 update ready yet. has example on how to create DFU packet, or you can use one from .

If you decide to try out the update it’s a good idea to use one of the ready ruuvitag_..._sdk12.3_to_15.3_packages for the SDK update, as a malformed SDK update will brick a tag. After the SDK update it’s safe to update the apps, as app update won’t break the software radio or bootloader.

1 Like

Ok, do you happen to have a Windows example also?

I understand the reason for this but I am a bit lost with different DFUs. I have flashed MCU devices before but they had only one rom image, which contained all. So in RuuviTag you can flash SDK, apps, (and bootloader) separately? So, different flash images go to different ROM addresses?

It should be pretty similar. Download Nordic’s Command Line Tools for Windows, download bootloader linked in the and run nRF Util commands on your Windows machine.

Yes, the application is made of three parts:

  • Softdevice, which is Nordic’s software radio which implements BLE.
  • Bootloader, which allows updating new applications but requires the Softdevice.
  • App.

Internally the Softdevice is at the lowest memory address, app is right after Softdevice, then there’s blank space which can be used for flash filesystem of the app, then there’s bootloader and finally bootloader settings stored to flash.

The _full -hex image contains softdevice, bootloader, bootloader settings and application. These are merged into one .hex file with the mergehex in You can erase the entire tag and flash the _full -version.

If you want to upload the app with wires it’s usually better to use only softdevice+app without bootloader, as bootloader checks app’s CRC before boot and the bootloader won’t start if the CRC in bootloader settings doesn’t match the CRC of the app. DFU handles updating the CRC in bootloader settings when a new app is installed.

1 Like

I finally got the RuuviTag device :slight_smile:
To make sure:

  1. First, I should flash “the Sofdevice” (i.e. “SDK”?) by flashing over-the-air.
  2. Then I should flash the application by flashing the "" (which I have changed and compiled) over-the-air

Right? That’s all?

How about the CRC check in the bootloader, does it need something?


That’s right. You don’t need to worry about the CRC if you’re flashing over the air. If there’s an issue with flashing your DFU package please let us know your exact nrfutil command

1 Like

One more check: I suppose I should build the release binaries of the RuuviTag app sw, not the debug binaries?

Depends on what you’re doing.
Test binaries broadcast and log faster, they’re perfect for testing the application in a fast pace.
Release binaries offer longer battery and log memory runtimes.

For my fork, I would like to significantly
increase the time period a tag can retain log data, i.e. number of samples.

Did you determine this by experimentation, calculation or is it a preliminary size.

  1. Could you break out log functions from


  1. Ideas I have include:
    3a) Storing temperature in integer tenths and humidity in integer percent in int16 rather than as floats
    3b) Not storing insignificantly different values. For example: if current temperature reading is same as previous reading + or - one tenth degree and humidity is same +/- and …
    3c) Already available: reduce the sampling interval.

I’d appreciate you feedback.

It’s 2^14, the ringbuffer implementation requires a power of two as the storage size and 2^15 would be difficult to fit in.

Yes, it’s on todo to split the logging out and add unit test coverage to that file. It is the ugliest piece of code by a wide margin.

I’d like to implement the Sprintz on RuuviTag, nice thing about it is that it can be done in pieces.

I will look into Spritz. From the first glance it looks it will take a while to digest.

Ring buffer implementation dependent on power of 2 size seems awkward. Perhaps an alternate implementation would work,