Longlife: reading log history

Hello Ruuvi people,

i am wondering if the BLE broadcasts are needed to connect to a RuuviTag?

If have my custom python script to connect to my RuuviTags and reading the logged data. It works great with the default firmware, but with the longlife version i need many tries to get a connection.
Is this because of the long broadcast interval?

I flashed the longlife firmware (ruuvitag_b_armgcc_ruuvifw_longlife_v3.31.2-RC1_full.hex) on my RuuviTag, because i am only interested in the log and not the BLE broadcasts. My goal was initially to entirely disable the BLE broadcasts on some of my ruuvis, would it still be possible to connect to them if i do this?

Best regards, Chris

Hi, you have a Python script to read the log data?
Have you made it available on Github?
I am looking for something like that too.

Yes, the connection is made by replying to the broadcast. This is actually the original intention behind the broadcast, we are just using the payload of broadcast to skip connecting at all.

Yes

No, broadcasts are needed. This is one reason why we have a relatively rapid broadcast interval, it lets devices to discover sensors faster.

No, broadcasts are needed. This is one reason why we have a relatively rapid broadcast interval, it lets devices to discover sensors faster.

Ok thank you for the info!
I will try to tweak the BLE connection, hopefully there are some timeouts that can be set on the host side.

Have you made it available on Github?

It is not public (yet).

Hi, you have a Python script to read the log data?

This is the code i am currently using, give it a try. I would be happy to know if it works for you:

"""
Connect to RuuviTag using bluetooth and read sensor log data

# Code stack
* python (version 3.x, tested on python 3.10.7)
* bleak (python bluetooth library)
    https://bleak.readthedocs.io/en/latest/usage.html

Tested on Linux (64-Bit) with kernel 5.19.7 and bluez 5.65-3

# Setup
Some python libs (bleak) are required, install them using your package manager
OR
create a virtual environment:
  python -m venv env
  source env/bin/activate
  pip install bleak
  
# Run
  python ./ruuvitag_read_log_data.py

# What is this all about
  The RuuviTag is a Wireless Temperature, Humidity, Air Pressure and Motion Sensor.
    * https://ruuvi.com/ruuvitag/
  It stores measurement data (temperature, humidity and air pressure) to internal storage.
  The default log interval is 5 minutes.
  The longlife/longmem firmware for the RuuviTag increases the log interval to 15 minutes.
  
  To read the history log from the RuuviTag a bluetooth connection has to be established first.
  The RuuviTag exposes the NUS (Nordic UART Service) to read/write data from/to the device.
    * https://docs.ruuvi.com/communication/bluetooth-connection/nordic-uart-service-nus
  By sending the read log data command the tag sends back the logged data.
    * https://docs.ruuvi.com/communication/bluetooth-connection/nordic-uart-service-nus/log-read
  The data is _not_ deleted from the RuuviTag when reading the log. It is automatically
  overwritten when the storage space is used up (should be 10 days for the default firmware).
"""



## The sensor measurements which can be requested from the RuuviTag (used by DATATYPE_LOG below)
TEMPERATURE  = 0x30  # \x30 Temperature  [0,01°C]
HUMIDITY     = 0x31  # \x31 Humidity     [0,01%rH]
AIR_PRESSURE = 0x32  # \x32 Air pressure [1Pa]
ALL_SENSORS  = 0x3A  # \x3A All of the above

## Nordic UART Service UUIDs, do _not_ change these
UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

## Header datatype for advertisement data, do _not_ change
DATATYPE_ADV = 0x05



### SETTINGS AREA BEGIN ###

## Enter the MAC address of your RuuviTag
DEVICE_MAC = "00:00:00:00:00:00"

## Number of seconds to get log data
LOG_SECONDS = 7200  # 7200 = last 2 hours

## Choose the log data to read from the RuuviTag (TEMPERATURE or HUMIDITY or AIR_PRESSURE or ALL_SENSORS)
DATATYPE_LOG = ALL_SENSORS

### SETTINGS AREA END ###



import asyncio
import sys
import calendar
import time
import struct
import bleak 
from datetime import datetime, timezone

log_data_end_of_data = False

async def ruuvi_read_log_data():
    global log_data_end_of_data

    def handle_disconnect(_: bleak.BleakClient):
        print("Device was disconnected", flush=True)
        # cancelling all tasks effectively ends the program
        for task in asyncio.all_tasks():
            task.cancel()

    # data received from bluetooth client, requested by start_notify()
    def handle_rx(sender_handle: int, data: bytearray):
        global log_data_end_of_data

        if len(data) <= 0:
            print(f"no data (size == {len(data)})")
        else:

            if (data[0] == DATATYPE_LOG):
                if len(data) != 11:
                    print("Header = log data, but wrong data size", flush=True)
                else:
                    # unpack binary data
                    dat = struct.unpack('>BBBII', data)
                    if (dat[3] == 0xFFFFFFFF and dat[4] == 0xFFFFFFFF):
                        print("  Log: end of output received", flush=True)
                        log_data_end_of_data = True
                    else:
                        if (dat[1] == TEMPERATURE):
                            print(f"  Log data: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(dat[3]))} {dat[4]/100.0:.2f}°C", flush=True)
                        elif (dat[1] == HUMIDITY):
                            print(f"  Log data: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(dat[3]))} {dat[4]/100.0}%rH", flush=True)
                        elif (dat[1] == AIR_PRESSURE):
                            print(f"  Log data: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(dat[3]))} {dat[4]/100.0}mBar", flush=True)
      
            elif data[0] == DATATYPE_ADV:
                if len(data) != 18:
                    print("Header = advertisement data, but wrong size", flush=True)
                else:
                    dat = struct.unpack('>BhHHhhhHBH', data)

                    print(f"  Advertisement data received: "
                        f"T={dat[1]/200.0:.2f}°C "
                        f"H={dat[2]/400.0:.1f}%rH "
                        f"P={(dat[3] + 50000)/100.0:.2f}mBar "
                        f"[x={dat[4]:+5d} y={dat[5]:+5d} z={dat[6]:+5d}] "
                        f"U={((dat[7]/32)+1600)/1000.0:.2f}V "
                        f"Tx_strength={(dat[7]%32)*2 - 40:+d}dBm "
                        f"movement_counter={dat[8]:3d} "
                        f"sequence_counter={dat[9]:5d}", flush=True)

            else:
                print(f"unknown first byte: {hex(data[0])}")

    # This code will run when calling ruuvi_read_log_data()
    print(f"Searching for RuuviTag {DEVICE_MAC}", flush=True)

    async with bleak.BleakClient(DEVICE_MAC, disconnected_callback=handle_disconnect, timeout=30) as client:
        
        print(f"Connected to Ruuvi, starting notify on UART channel", flush=True)
        await client.start_notify(UART_TX_CHAR_UUID, handle_rx)
        
        # Build request data header
        data_tx = b''
        data_tx += bytes([DATATYPE_LOG])
        data_tx += bytes([DATATYPE_LOG])
        data_tx += b'\x11'
        # timestampts
        timenow = int(time.time())  # get current system time
        timeprv = timenow - LOG_SECONDS
        data_tx += struct.pack('>I', timenow)
        data_tx += struct.pack('>I', timeprv)

        print(f"Requesting log data for the last {LOG_SECONDS} seconds")
        await client.write_gatt_char(UART_RX_CHAR_UUID, data_tx)

        # wait for data
        while True:
            if log_data_end_of_data:
                print(f"All log data received, closing connection")
                client.close()
                break
            await asyncio.sleep(1)


if __name__ == "__main__":
    try:
        asyncio.run(ruuvi_read_log_data())
    except asyncio.CancelledError:
        # task is cancelled on disconnect, so we ignore this error
        pass
    except bleak.exc.BleakDBusError as error:
        ## bleak.exc.BleakDBusError: [org.bluez.Error.NotReady] Resource Not Ready
        ##   bluetoothctl power on
        ## bleak.exc.BleakDBusError: [org.freedesktop.systemd1.NoSuchUnit] Unit dbus-org.bluez.service not found.
        ##   systemctl start bluetooth.service
        print("bleak.exc.BleakDBusError")
        print(error)
    except bleak.exc.BleakError as error:
        ## bleak.exc.BleakError: Not connected
        print("bleak.exc.BleakError")
        print(error)
    except:
        print("unknown exception")
        raise  # re-raise exception for the user to debug

Danke vielmals dafür. Ich werde es ausprobieren und mich wieder melden.

I think you mean : To read the history log…

Yes you are right, thank you!

Does anyone know how I can edit my posts so i can fix it?

You should have option to edit your post by clicking the pencil in lower right corner,

I can only see these icons on my own posts (share / bookmark / delete):


Maybe its an admin thing?
Wait, now i can edit this post…
:thinking: Is it because i just got the Basic Badge or is there a time limit for how long messages can be edited? I can edit this post but not the ones further back.

Yes, new users have a 1-day limit for editing posts and others have a 30-day limit. I lifted your user account to a more trusted status, that should make sure that automoderator does not get in your way :slight_smile:

Thank you! It worked, i have edited the message now :blush: