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).
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:
start a scan through hcitool:
$ sudo hcitool lescan --duplicate
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.
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.
[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.
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:
- The freedom to run the program as you wish, for any purpose (freedom 0).
- 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.
- The freedom to redistribute copies so you can help others (freedom 2).
- 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.
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.
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.
- send_init()
- boost_mode()
<device_address>
Causes boost mode activation.
- boost_mode()
- stop_boost_mode()
<device_address>
Causes boost mode deactivation.
- stop_boost_mode()
- auto_mode()
<device_address>
Activate the automatic mode and adjust the temperature accordingly (as selected in the weekly schedule).
- auto_mode()
- manual_mode()
<device_address>
Activate the manual mode.
- manual_mode()
- set_temperature()
<device_address>
<temperature>
Set the temperature to the value indicated by the second parameter.
- set_temperature()
- set_comfort_reduction_temp()
<device_addr>
<comf_temp>
<red_temp>
Changes the “comfort temperature” and “reduced temperature” values within the valve settings.
- set_comfort_reduction_temp()
- 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.
- holiday_mode()
- read_profile()
<device_address>
<day>
Require the daily schedule. Days of the week are counted starting from Saturday (
00
is Saturday, ..,06
is Friday)
- read_profile()
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 intervalintX
must be in the formTEMPERATURE/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 time24:00
or00:00
.- lock()
<device_address>
Locks the physical keys on the valve.
NOTE: This does not prevent interaction through Bluetooth.
- lock()
- unlock()
<device_address>
Unlocks the physical keys on the valve.
- unlock()
- set_window()
<device_address>
<temperature>
<duration>
Set window mode temperature and duration.
- set_window()
- set_offset()
<device_address>
<temperature>
Set the offset temperature. It must be between -3.5 and +3.5.
- set_offset()
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:
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 )
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
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.
- auto_mode
- 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.
- manual_mode
- 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_temperature
- 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 byVALVE_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_all_temp
- 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
- set_profile
- 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 from01
(Monday) up to07
(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
- profile_req
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).
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).
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:
- connect to the valve
- send the command
- 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 inhome-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 formatget_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).
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:
import the functions defined previously:
./basic_functions.sh
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
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
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 |