4. Script Creation

This section wants to introduce some useful tools to search for Bluetooth devices and to communicate with them. In particular, we consider the tools provided by the BlueZ stack. Then we explain, as an example, some details on the scripts created for the management of the Eq3 Eqiva radiator valves discussed in the Introduction.

4.1. Bluez Stack

BlueZ is the offical Linux Bluetooth protocol stack and is part of the Linux kernel since version 2.4.6 was released [4]. It provides the necessary modules to manage both classic and low energy Bluetooth devices [5]. Along with these modules, there is a series of command-line tools (bluez-tools) that interact with the main core.

Debian provides all the necessary packages in its repositories. The installation can be performed through the apt package manager:

$ sudo apt install bluez bluez-tools

Many tools are available. Below we describe the use of those needed to send a command to a BLE device and to search for its MAC address.

4.1.1. Hcitool

As specified in its manual [6], hcitool is used to configure Bluetooth connections and send some special command to Bluetooth devices. It allows, among other things, to send HCI commands, make connections and manage the authentication phases.

Furthermore, it is able to scan for both BLE and non-BLE devices. This allows us to identify the MAC address of the device we want to work on. However, it’s not possible to search both types of devices simultaneously:

  • to start a classic scan, use the command:

    $ sudo hcitool scan
    
  • to start a BLE scan, use the command:

    $ sudo hcitool lescan
    

The following image shows the result of a BLE scan. Each line contains the MAC address of the identified device followed by its name (if readable).

scan for BLE devices

Scan for BLE devices

Note

The scan and lescan commands ignore advertising packages received from devices they already know about. To prevent those packages from being ignored (in some cases it may be necessary) use the --duplicate parameter.

4.1.2. Hcidump

Hcidump is a tool that can read the contents of the HCI packets sent and received by any Bluetooth device.

Despite the BlueZ stack integrates this tool (since version 5), it requires to be installed separately. As always, in Debian this can be done using the apt package manager:

$ sudo apt install bluez-hcidump

Among other things, we will use this software to read the contents of the advertising packages during the scan phase. To do this:

  1. start a scan through hcitool:

    $ sudo hcitool lescan --duplicate
    
  2. while hcitool is running, start hcidump:

    $ sudo hcidump --raw
    

The --raw parameter allows to obtain data in the “original” format. Other parameters (--hex, --ascii) can be used to get them in other formats. Refer to the manual [8] for these details.

hcidump running while scanning

Hcidump (right) running while scanning (left)

The image above shows the execution of the previous commands. The terminal on the right lists the intercepted HCI packets. They are all advertising packages, because the first bytes are 04 3E. As indicated by the Bluetooth specifications, 04 stands for HCI Event while 3E means LE Advertising Report.

An example of the use of hcitool and hcidump is given in the Laica PS7200L Scripts section.

4.1.3. Gatttool

Gatttool is designed for Bluetooth Low Energy. It therfore works exploiting the concept of GATT and its ATT protocol, which is adopted only by BLE devices. Basically, it allows you to connect to a device, discover its characteristics, write/read attributes and receive notifications. Optionally, it can also be used in interactive mode through a CLI.

Possible uses are described in the manual [7] and through the command gatttool --help. Below we show how to write a value on a characteristic, which is what we want to do in order to send commands to BLE devices.

To this end, you need:

  • the MAC address of the target device
  • the Handle that identifies the characteristic.

Given this data, the following command sends the value represented by <value> to the device:

$ gatttool -b <mac_address> --char-write-req -a <handle> -n <value> --listen

Note

The --listen parameter requires gatttool to wait for a notification. However, this puts the process in a waiting state until it is manually terminated.

The following image shows the output obtained from the command execution. As you can see, the characteristic has been written correctly and information about the received notification is shown.

gatttool characteristic write

Gatttool Characteristic Write


[4]BlueZ - Common Questions
[5]Ubuntu Bluez Documentation
[6]hcitool manpage
[7]gatttool manpage
[8]hcidump manpage

4.2. Eq3 Eqiva Script

An english translation of the software is available here (while here is the original) and consists of:

  • a series of functions useful to interact with a single valve
  • some scripts for the simultaneous management of multiple valves

Everything has been developed and tested on a bash shell and this should ensure portability on most Unix systems without having to make many changes. However, the BlueZ stack dependency limits its use to GNU/Linux operating systems.

Updated:

We have created an updated version of the scripts. It corresponds to the one implemented in the eq3eqiva .deb package. The next sections will refer to the “original” (old) version. Changes and additions of the new implementation will be reported with the Updated tag.

4.2.1. GPLv3 License

As mentioned in the Introduction, the project is (among other things) aimed at providing the possibility to integrate these valves into free home automation systems.

The code is then released under the GPLv3 license (GNU General Public License), the conditions of which have to guarantee the four fundamental freedoms [1] defined by the Free Software Foundation:

  1. The freedom to run the program as you wish, for any purpose (freedom 0).
  2. The freedom to study how the program works, and change it so it does your computing as you wish (freedom 1). Access to the source code is a precondition for this.
  3. The freedom to redistribute copies so you can help others (freedom 2).
  4. The freedom to distribute copies of your modified versions to others (freedom 3). By doing this you can give the whole community a chance to benefit from your changes. Access to the source code is a precondition for this.

Basically, it is a noticeably copyleft license: anyone who wants to distribute copies of a software bound to these conditions, whether free or behind the payment of a price, is obliged to recognize the same rights that it received to the recipient. He must also guarantee access to the source code.

4.2.2. Single Valve Management

Everything necessary to manage a single valve is contained in two files:

  • basic_functions.sh deals with sending and receiving data, translating notifications and also contains some frequently used functions.
  • valve_commands.sh deals with the syntactic/semantic composition of each command

Both files are commented and describe each function, therefore only some relevant aspects will be discussed below.

It is interesting to note that, although the CalorBT application requires a pairing procedure in order to be able to communicate with the valve, this procedure is not necessary outside the Android/iOS context. This is clearly a security flaw because it allows communication with the device while knowing only the MAC address, which can easily be obtained through external tools such as the already described hcitool

4.2.2.1. basic_functions.sh File

send_command()

input: <device_address> <command>
output: value of the notification received after execution

It writes the value of the command argument on the “send command” characteristic, thus causing the execution of the corresponding command.

The transmission is based on the use of the Gatttool tool, as discussed in the previous section, through the following line of code:

output=$(timeout $TIMEOUT_SEC gatttool -b $1 --char-write-req -a 0x0411 -n $2 --listen)

The parameters -b and -n allow to specify respectively the address of the device and the value to send; at the time of execution they will be replaced by <device_address> and <command>. The use of -listen puts the tool in a locked state, waiting for one or more notifications from the counterpart.

However, there is no way to indicate a temporal term to this condition, and it is necessary to use timeout $ TIMEOUT SEC to store what is received in the output variable after a certain period of time and move on to the next instruction. The TIMEOUT SEC variable is defined in the config.sh file and will be detailed in the Field Test section.


parse_return_value()

input: <notification_value>
output: notification translated into readable content

It translates the notification content and brings it to the standard output through a series of echo commands. The parameter <notification_value> must be composed of the received bytes, each separated by a space: the same format used by Gatttool.

The translation is performed by interpreting the value of each byte consistently with the information in section Notifications. If it is a Holiday Mode notification, the parsing is done with the help of the parse_holiday_params() function, which is also contained in basic_functions.sh, using bytes 7, 8, 9 and 10.

profile_req.sh example

profile_req.sh example

As an example, the profile_req.sh script allows to request information on a day schedule using the appropriate command. The output produced is an example of using the parse_return_value() function. The above figure shows the translation of the notification received following the request relating to the day of Tuesday (indicated by the parameter 02) to the valve called “camera”.


calculate_temp()

input: temperature in decimal base
output: (temperature*2) in hexadecimal base and rounded

It allows to encode the temperature according to the format used by the valve. The result is obtained by multiplying by 2 and then rounding to a value equal to XX.0 or XX.5.

Notes

This function must not be used for the Select Holiday Mode command. It uses the rule (temperature*2)+128. For this purpose there is the function calculate_temp_128(), which is also contained in basic_functions.sh.


search_by_name()

input: <valve_name> <file_name>
output: valve MAC address (-1 if it does not exists)

It allows to find the MAC address of a single device.

For this purpose, each valve is identified by a user-assigned name within the file file_name. Within this file, each valve must be represented on a new line according to the format: NAME/ADDRESS. The function does not distinguish between uppercase and lowercase letters; therefore, the line valve1/00:11:22:33:44:55 is completely equivalent to VALVE1/00:11:22:33:44:55.

The file is scanned line by line: if there are duplicates, the first occurrence will be selected.

Updated:

The following functions have been included in the updated version.

check_bt_status()

input: -
output: an error message if Bluetooth isn't active

Refer to this section for more details.

validate_mac()

input: <MAC_address>
output: 0 if valid, -1 otherwise

Check if a MAC address is syntactically valid. To be valid, an address must be composed of a succession of six pairs of hexadecimal values, separated by “:”. The check is done through a regular expression, using the following code.

#check if $1 is a valid mac address
if [[ "$1" =~ ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ ]]; then
    echo "0"
else
    echo "-1"
fi

In particular, it requires that a pair {2} of hexadecimal values [a-fA-F0-9] be repeated five times {5}, each time followed by “:”. There must then be a sixth pair, again identified by [a-fA-F0-9]{2}, which must not be followed by the separator “:” (because it is the last pair).

4.2.2.2. valve_commands.sh File

As already disclosed, valve_commands.sh manages the syntactic composition of every possible command and requires its execution through the following functions. Note that all functions automatically round the entered temperature to values of the type XX.0 or XX.5.

  • send_init() <device_address>

    Send the current date and time, automatically calculated through the command date. It is not essential, but it is useful to start the communication in order to guarantee the synchronization between the central device and the valve and to receive a notification that reports the status.

  • boost_mode() <device_address>

    Causes boost mode activation.

  • stop_boost_mode() <device_address>

    Causes boost mode deactivation.

  • auto_mode() <device_address>

    Activate the automatic mode and adjust the temperature accordingly (as selected in the weekly schedule).

  • manual_mode() <device_address>

    Activate the manual mode.

  • set_temperature() <device_address> <temperature>

    Set the temperature to the value indicated by the second parameter.

  • set_comfort_reduction_temp() <device_addr> <comf_temp> <red_temp>

    Changes the “comfort temperature” and “reduced temperature” values within the valve settings.

  • holiday_mode() <device_address> <DD/MM/YYYY> <hh:mm> <temperature>

    Activate the holiday mode; therefore maintains the same temperature until the end indicated by the parameters.

  • read_profile() <device_address> <day>

    Require the daily schedule. Days of the week are counted starting from Saturday (00 is Saturday, .., 06 is Friday)

  • set_profile() <device_address> <day> <int1> [int2] [int3] [int4] [int5] [int6] [int7]

    Set the daily schedule. Days of the week are counted starting from Saturday (00 is Saturday, .., 06 is Friday). Each interval intX must be in the form TEMPERATURE/hh:mm and together they must guarantee coverage for the whole day following the guidelines in the section Set Daily Profile. As a result, the intervals 2-7 may turn out to be unnecessary and certainly the last one specified must have the time 24:00 or 00:00.

  • lock() <device_address>

    Locks the physical keys on the valve.

    NOTE: This does not prevent interaction through Bluetooth.

  • unlock() <device_address>

    Unlocks the physical keys on the valve.

  • set_window() <device_address> <temperature> <duration>

    Set window mode temperature and duration.

  • set_offset() <device_address> <temperature>

    Set the offset temperature. It must be between -3.5 and +3.5.

The following is a general example of how these functions work, since they are all quite similar.

1
2
3
4
5
6
7
8
9
read_profile() {
    if [ $2 -lt 0 -o $2 -gt 6 ]; then
        echo "Week goes from 00 (saturday) to 06 (friday)."
        return
    fi

    day=$(printf "%02x" $2) # $2 = day
    echo $( send_command $1 20$day ) # $1 = device_address
}

The first part, represented here by lines 2-7 but not always necessary, checks the correctness of the inputs and calculates the coding of the parameters according to the format required by the valve. The second part (line 8) sends the command using the send_command function and prints the notification received.

Parsing/Translation

As shown in the example, the default behavior does not provide parsing and translation of what has been received. For this to happen you need to use the parse_return_value method, in two possible alternative ways:

  1. replacing it with the echo command in the last line of each function (line 8 in the example):

    parse_return_value $( send_command $1 20$day )
    
  2. moving the parse_return_value() call outside of the requested function (read_profile() in the example), thus obtaining the following code:

    parse_return_value $( read_profile ....... )
    

4.2.3. Multiple Valve Management

The functions described in the previous section are useful to create larger scripts that automate the management of multiple valves. Below are listed the scripts made, which want to form a simple guideline in the development of larger projects.

Configuration

For simplicity, using the scripts requires assigning a name chosen by the user to the MAC address of each valve. It is therefore necessary to indicate which will be the file containing the name-address associations through the VALVE_FILE variable inside the config.sh file.

The file referred to by VALVE_FILE must be compiled according to the syntax required by the documentation of search_by_name, thus using the format NAME/MAC_ADDRESS.

MAC addresses can be found as described in the Bluez Stack section through the hcitool lescan command.

In order to run the scripts you need execution permissions:

$ cd /path/to/scripts/directory
$ chmod u+x *.sh

Then, to run a script:

$ ./script_name.sh parameter1 parameter2
Updated:

For each script, except for profile_req, you can use the -p or --parse option to activate the parsing of the received notifications. Otherwise, the value of notifications is shown in hexadecimal base.

Example: ./auto_mode.sh -p bathroom kitchen

  • auto_mode <valve_name> [valve_name ...]

    Set the “auto mode” on all the valves identified by the names provided by command line. At least one parameter is required, while the subsequent ones are optional.

    NOTE: the temperatures set on each valve after the execution of the script are dependent on how they have been programmed individually.

  • manual_mode <temperature> <valve_name> [valve_name ...]

    Set the “manual mode” on all the valves identified by the names provided by command line. At least one parameter is required, while the subsequent ones are optional.

  • set_temperature <temperature> <valve_name> [valve_name ...]

    Rounds the <temperature> value and sends it to all the valves identified by the names provided by command line. Requires the first and second parameters, while the subsequent ones are optional.

  • set_all_temp <temperature>

    Rounds the <temperature> value and sends it to all the valves. Names and addresses of the valves to which the data are sent are taken from the file referenced by VALVE_FILE.

    NOTE: The file is scanned line by line. The presence of duplicates implies a double execution of the command on the same valve

  • set_profile <profile_file> <valve_name> [valve_name ...]

    Set profiles for one or more days on all the valves identified by the names provided by command line. The values to be set are supplied via a profile_file. It must contain the schedule for one or more days in the following format:

    day (1=monday, ..., 7=sunday)
    base_temperature
    HH:MM-HH:MM-TEMP (first interval)
    HH:MM-HH:MM-TEMP (secondo optional interval)
    HH:MM-HH:MM-TEMP (third optional interval)
    end
    

    NOTE: within the same file, you can specify the schedule for several days separating each block by an empty line.

    As an example, the Monday schedule with a base temperature of 18°C and 20°C in the two ranges 06:30-08:00 and 17:00-20:00 is carried out in this way:

    01
    18
    06:30-08:00-20
    17:00-20:00-20
    end
    

    Updated:

    In the new implementation days are indicated by their names and not numerically. They must be written in English in complete or abbreviated form (e.g. “Saturday” is equivalent to “sat”). They are not case sensitive.

    The previous example becomes:

    Monday
    18
    06:30-08:00-20
    17:00-20:00-20
    end
    
  • profile_req <day> <valve_name> [valve_name ...]

    It requires and prints the daily schedule for all the valves identified by the names provided by command line. The <day> parameter is counted starting from 01 (Monday) up to 07 (Sunday).

    Updated:

    In the new implementation days are indicated by their names and not numerically. They must be written in English in complete or abbreviated form (e.g. “Saturday” is equivalent to “sat”). They are not case sensitive.

    Example: ./profile_req Sunday bathroom kitchen

The operating principle of each script is almost identical. First of all, the presence of the parameters required to run it is checked. Then each parameter representing a device activates the search function of the MAC address. Once this is done, the commands are sent to the valves. The following lines of code, extracted from the auto_mode.sh script, clarify the functions used.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/bin/bash
. ./valve_commands.sh

if [ -z $1 ]; then
    printf "Usage: ./auto_mode.sh <valve_name> [valve_name ...]\n"
    exit
fi

for name in "$@"; do
    address=$( search_by_name $name $VALVE_FILE )

    if [ "$address" != "-1" ]; then
        auto_mode $address
    else
        printf "%s valve not found\n" "$name"
    fi
done

The inclusion of valve_commands.sh` (line 2) makes available all the primitives present in the section Single Valve Management. This causes the implicit inclusion of basic_functions.sh and config.sh and makes usable the search_by_name function and the VALVE_FILE variable.

Lines 4-7 check for the required parameters and cause the script to exit if the check is not passed. The for loop (lines 9-17) allows to go by all the names of the valves supplied as an argument to the script. From each of these the MAC addresses are obtained. Now sending the request to the valve is simple and is based on the call to an already known function (line 13): valve_commands.sh provides the necessary to carry out all the operations made available by the CalorBT application.

The part of the code that deals with the transmission of data to the valve is located within the for cycle. For this reason the requested command is sent to one device at a time, in the order in which the names are supplied to the script at invocation time (see Sequence Diagram of auto_mode.sh). The output produced corresponds to the notifications received from time to time, after the execution of each command.

If the address of a valve is not found, a control (line 12) causes an error message to be printed (line 15). Then starts the search for the next address (if required by the entered parameters).

auto_mode script sequence diagram

Sequence Diagram of auto_mode.sh

The above Sequence Diagram [2] shows the exchange of messages between three valves and the central device during the execution of the auto_mode.sh script, activated as follows:

./auto_mode.sh bedroom sitting_room office

Correctly, as we have just seen, the value 0x0400 (Select Auto Mode) is sent in succession for each valve. The next command is sent only after receiving the notification of the previous one.

The set_profile.sh script is the only exception to this behavior, because it has to read the supplied file. This can contain instructions related to the schedule of several days, making it necessary to send several commands to the same valve. For this purpose it was decided to minimize the number of calls to the search_by_name() function by immediately searching for the requested MAC addresses and storing the result in an array (the VALVES_ADDR variable, inside the script).

Updated:

In the new implementation, in each script was added a control on Bluetooth activation, syntax validation of MAC addresses and the possibility to use the -p or --parse option (see the previous note).

The first two points were obtained through the functions:

The third point was obtained through the following code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#check if the -p or --parse option is present
case $1 in
        -p|--parse)
        parsing=1
        shift #discard the argument

        ..  #do things
        ;;

        *)
        parsing=0
        ;;
esac

If the first parameter (represented by $1) corresponds to -p or --parse, the variable parsing is set to 1 and the parameter already used is discarded through the shift command. Now $1 contains the next parameter supplied by the user. In all other cases (identified by*)”), parsing is set to 0 and no parameters need to be discarded.

As a result, the overall structure of each script is a little changed. The new structure is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#check if the parameter -p is present
.. #code above

check_bt_status  #check if bluetooth is active

for name in "$@"; do
  address=$( search_by_name $name $VALVE_FILE )

  if [[ "$address" != "-1" && $(validate_mac $address) != "-1" ]]; then

    notification=$(auto_mode $address)

    if [[ ! -z $notification && $parsing = 0 ]]; then
      printf "\n%s: %s\n" "$name" "$notification"
    elif [[ ! -z $notification && $parsing = 1 ]]; then
      printf "\n%s:\n" "$name"
      parse_return_value $notification
    else
      #received empty notif
      printf "\n%s: error. try to increase the timeout or move close to the valve\n" "$name"
  fi
  else
    #mac address error or valve not found
    printf "\n%s: not found in '%s' or invalid MAC address\n" "$name" "$VALVE_FILE"
  fi
done

First of all we check the presence of the -p|--parse option and the activation of the Bluetooth (line 4). Then there is the usual for cycle, in which there are some differences with respect to the previous implementation.

The line 9 not only verifies that the MAC address has been found, but also that it is correct at syntax level (through the validate_mac function).

Furthermore, in this version the notification received from the valve is saved in the notification variable (line 11). This is because depending on whether the parsing variable is set to 0 or 1 (respectively, line 13 and line 15), the notification must be shown in hexadecimal values (line 14) or translated (line 17).

The errors are substantially divided into two cases:

  • something went wrong in the communication and the notification received is empty (line 20)
  • no valve was found with that name or the MAC address entered by the user is not syntactically correct (line 24)

In both cases error messages are printed.

4.2.4. Field Test

The information in the previous sections was tested using:

  • a Bluetooth 4.0 Class 2 adapter on the central device
  • Ubuntu 14.04 LTS GNU/Linux
  • the 4.101 version of the BlueZ stack (included by default in the chosen OS)

However, the scripts have also been tested on the latest BlueZ versions.

Range And Related Problems

The EQ3 company, which produces the valves, declares a maximum range of 10 meters outdoors (and therefore in the absence of obstacles) [3]. Despite this, the use via smartphone (through the appropriate application) allowed us to reach the distance of 12.5 meters without drops in reception.

The signal’s range and strength directly influence the content of the TIMEOUT_SEC variable, defined in config.sh. This variable is used in the send_command function. Its value represents the maximum time (in seconds) within which it is certain to be able to carry out the following operations:

  1. connect to the valve
  2. send the command
  3. receive the subsequent notification

DISTANCE AVERAGE TIME STD. DEV.
2 meters 02.99 sec. 0.44 sec.
4 meters 03.78 sec. 0.59 sec.
6 meters 13.73 sec. 1.80 sec.

The value (time) of TIMEOUT_SEC is set by default to 5 seconds, given the results obtained with the Class 2 adapter used in the tests. As can be seen from the table, this adapter has signal losses even at a distance of 6 meters, making it impossible to deepen the analysis. As a consequence, the most appropriate value was selected with respect to the results obtained under good reception conditions (2 and 4 meters). In all likelihood, these values will not suffer large increases over medium to long distances using a more powerful Bluetooth connector (i.e. Class 1).

Note

TIMEOUT_SEC is in any case a fundamental parameter that needs to be manually adapted depending on the application context. The goal is to find a good compromise between waiting times and errors (due to not receiving the notification within the short time available).

The calculation of the elapsed time between connection, sending and receiving was done using the time command in the following way:

time gatttool -b $ADDRESS --char-write-req -a 0x0411 -n 4000

This sends, by way of example, the Select Auto Mode instruction to the valve using gatttool. The average times and standard deviations shown in the table result from the execution of this instruction on three different valves. In total, 20 trials were performed for each distance.

Each test was carried out by the same starting state: valves disconnected from the central device. The disconnection automatically occurs 45 seconds after execution of a command.

Parallel Connection

The gatttool tool used for the connection does not allow parallel sending of commands. The central device is able to communicate with only one device at a time.

Indeed, after a write operation of a characteristic, gatttool does not allow the execution of operations on other devices until the actual confirmation of success (in the form of a notification) is received. This makes every attempt useless.

4.2.5. Similar Projects

Recently a Python library that allows the use of these valves has been integrated into the home-assistant platform. The library can be used “stand alone” (i.e. without home-assistant) through a CLI interface. Here are some differences with respect to our implementation.

Pros:

  • Better Bluetooth management, probably thanks to bluepy. It seems more reliable when something goes wrong and on average requires less waiting time.
  • Provides information on the status of the valve battery.

Cons:

  • Requires python and bluepy (not present in Debian repositories).
  • With the CLI interface it is possible to memorize only one valve (by exporting the MAC address to an environment variable or by specifying it manually at each command). Through the home-assistant platform apparently you can pair “name-address” (more than one), but it is a feature implemented in home-assistant itself.
  • There is no way to set up daily schedules. It also returns strange values (mixed with correct information) when they are read.

Note

The repository does not provide information about the Eq3 Eqiva protocol: it is not documented. Although this is not a real “downside” in use, we think it is important to spread the result of a reverse engineering activity.


[1]The Free Software Foundation. What is free software?
[2]Sequence Diagram - Wikipedia
[3]Bluetooth Smart Radiator Thermostat - EQ3

4.3. Laica PS7200L Script

Scripts related to the Laica PS7200L BLE scale are available here. They have been tested on a bash shell and the code is released under the GPLv3 License.

In addition to the BlueZ stack, they require the hcidump tool. Refer to the previous section for clarification on use and installation.

Basically, the scripts are composed of two files:

  • basic_functions.sh contains a series of functions to receive information from the BLE scale and convert them to a readable format
  • get_weight.sh automates calls to basic functions in order to get the weight in kg

4.3.1. basic_functions.sh File

check_bt_status()

input: -
output: an error message if Bluetooth isn't active

Check if the Bluetooth is active on the central device. Otherwise, prints an error message and the list of each identified Bluetooth adapter (with its status).

For this purpose, the function uses the output generated by the hciconfig tool [9], which is included in the Bluez Stack. An example of output is present in the following image. As reported in the manual, hciX is the name of a Bluetooth device/adapter installed in the system. (only one, in this example).

hciconfig output

Output of hciconfig tool

The check_bt_status() function works by identifying each adapter through the following code, which match lines that starts with a string like “hciX”:

if [[ "$line" =~ ^hci[0-9] ]]; then
    ..
fi

For each line identified (i.e. a BT adapter), it’s possible to understand if it represents a working or a disabled adapter by checking the presence of UP or DOWN (on and off respectively) in the following two lines. This is done through the following code:

read #skip a line
read adapter_status

if [[ $adapter_status == *"UP"* ]]; then
    #bluetooth is active
else
    #bluetooth is disabled (related to this adapter)
    #save the adapter' status (for log purpose)
fi

If an active adapter is detected, it means that Bluetooth should be working. The function then terminates without providing output. Otherwise a list of detected devices and their status is printed.


scan_address()

input: <device_name> <file_name> [timeout]
output: MAC address of <device_name> (-1 if not found)

It scans for [timeout] seconds (15, if no value is provided) looking for a BLE device called <device_name> and saves the contents of the advertising packages in <file_name>.

After a first check on the presence of the necessary parameters, the following line of code carries out the essential part of the work.

scan_results=$(sudo timeout $timeout hcitool lescan --duplicate &
    sudo timeout $timeout hcidump --raw > $2)

Note that:

  • Hcitool starts scanning for BLE devices. The output produced is saved in the scan_results variable
  • Hcidump intercepts the Advertising packages and redirects everything in a file identified by the second parameter $2 (which corresponds to <file_name>)
  • The & operator allows to run hcitool in the background (but still producing output) while running hcidump
  • using timeout is convenient for killing both tools, which otherwise need to be stopped manually

At the end of the timeout, a while loop scrolls through the hcitool output (saved in the variable scan_results), looking for the MAC address of the BLE device <device_name>. If this is found then the loop is interrupted and the address is returned through an echo command. Otherwise the value -1 is returned.

Note

Calling this function generates a <file_name> file. It contains the raw output of hcidump.


format_address()

input: <MAC_address>
output: formatted address

It formats the MAC address provided as input to make it conform to the format used by hcidump. To do this it is necessary to replace each : character with a white space and reverse the order of each byte (the first becomes the last, the second becomes the penultimate etc..)

As an example, the address 00:11:22:33:44:55 becomes 55 44 33 22 11 00.


find_weight()

input: <MAC_address> <hcidump_file>
output:
    weight in Kg
    (-1 if <hcidump_file> does not contain adv packages sent from the scale)

It calculates the weight in Kg, given the MAC address of the scale and the name of the file previously generated by hcidump (and thus obtained with scan_address).

The function checks for the presence of the required parameters and the existence of the file. Then it starts analyzing the file package by package. This is the example structure of the packages we want to identify (formatted according to the hcidump standard):

> 04 3E 29 02 01 03 01 D0 FF FF FF FF FF 1D 0F FF 02 A1 09 FF
  02 F8 FF FF 82 FF FF 21 71 AA 02 01 06 09 09 59 6F 48 65 61
  6C 74 68 B6

The beginning of a package is marked with the > symbol and in the first line it contains the MAC address of the device that sent it (D0 FF FF FF FF FF in the example). This allows us to identify the packages we are potentially interested in. Within these, the information concerning the weight is contained in the first two bytes of the second row (02 F8 in the example).

Based on these considerations, the script reads the file looking for lines containing both the correct MAC address and the > character through this code:

while read line
do
    if [[ $line = *'>'* && $line = *"$address"* ]]; then

        ..
        ..
    fi
done < "$hcidump_file"

If a consistent line is found, the next one is saved in a variable. However, our goal is to identify the second line of the last consistent package, because it is the one containing the most accurate weight. For this reason, the cycle does not break at the first detected occurrence, but continues the inspection until the end of the file.

Once the cycle is complete, we have the desired line stored in the $last_occurrence variable. If no packet has been detected, the variable contains an empty string and -1 is returned.

Having the desired line available, the function extracts the first two bytes and carries out the operations required to obtain the weight (concatenation, conversion to base 16, division by 10). The result is returned with an echo command.

4.3.2. get_weight.sh File

This script exploits the functions previously exposed to automate the reading of the weight from the BLE scale. To do this two variables are defined:

hcidump_file="hcidump_output"
available_time=15

The first one indicates the name of the hcidump file that will be generated by the function scan_address. The second allows you to select the time you have available to weigh yourself from the moment the script is started or the password is entered (if required).

The order in which the operations are carried out is as follows:

  1. import the functions defined previously:

    ./basic_functions.sh
    
  2. check Bluetooth status, scan for the MAC address and save the output in a variabile (it also generates the hcidump file)

    check_bt_status
    MAC_address=$(scan_address YoHealth $hcidump_file $available_time)
    

    NOTE: YoHealth is the name of our target BLE device

  3. if the MAC address has been found, analyze the hcidump file:

    if [[ $MAC_address = "-1" ]]; then
         echo "get_weight.sh error: MAC address not found"
    else
         find_weight $MAC_address $hcidump_file
    fi
    
  4. remove the $hcidump_output file (now useless)

Note

In order to run the scripts you need execution permissions:

$ cd /path/to/scripts/directory
$ chmod u+x *.sh

Then to run the script:

$ ./get_weight.sh

[9]hciconfig manpage