Tips en Trucs 2018

Bluetooth apparaten scannen

Onlangs was ik toe aan een nieuwe horloge. Nog geen smartwatch, maar wel een multifunctioneel horloge gekocht, een Nokia Steel. Zoals een smartwatch, moet je de Nokia Steel configureren via een smartphone. Dit ging verschrikkelijk moeizaam, het duurde namelijk een eeuwigheid voor de smartphone en het horloge gekoppeld waren en een verbinding tot stand kwam.

Aangezien de smartphone en het horloge via bluetooth communiceren, moet je de bluetooth signalen ook via een computer kunnen scannen. Het Nokia Steel horloge gebruikt een nogal vreemd bluetoothgedrag. Het maakt zich namelijk bekend met steeds andere mac-adressen. Met de opdracht bluetoothctl kan je bluetooth-apparaten als volgt scannen:

dany@main:~> bluetoothctl
[NEW] Controller 7C:67:A2:C1:F8:F8 main.pindanet.home [default]
[bluetooth]# scan on
Discovery started
[CHG] Controller 7C:67:A2:C1:F8:F8 Discovering: yes
[NEW] Device 1A:73:83:5E:0C:CE Activite C8
[CHG] Device 1A:73:83:5E:0C:CE RSSI: -68
[bluetooth]# scan off
[CHG] Device 1A:73:83:5E:0C:CE RSSI is nil
[CHG] Controller 7C:67:A2:C1:F8:F8 Discovering: no
Discovery stopped
[bluetooth]# remove 1A:73:83:5E:0C:CE
[DEL] Device 1A:73:83:5E:0C:CE Activite C8
Device has been removed
[bluetooth]# exit
[DEL] Controller 7C:67:A2:C1:F8:F8 main.pindanet.home [default]
Bluetooth scan

De NEW regels tonen de opgemerkte bluetooth apparaten, waarbij de eerste NEW regel de computer is waarop bluetoothctl gestart is.

Via de blauwe bluetooth-prompt kan je bluetoothctl opdrachten ingeven en uitvoeren. Met de scan on opdracht start je het opsporen van bluetooth-apparaten.

De CHG regels tonen veranderende eigenschappen van reeds opgemerkte bluetooth apparaten. Bijvoorbeeld het in de scan mode zetten van de bluetooth interface waardoor andere bluetooth apparaten opgemerkt worden.

Bij het opmerken van een nieuw bluetooth apparaat (NEW regel) wordt het mac adres en de naam van het apparaat weergegeven. Mijn Nokia Steel horloge wordt hierboven opgemerkt met het mac adres 1A:73:83:5E:0C:CE en de naam Activite C8.

Met de opdracht scan off stop je het scannen naar bluetooth apparaten.

Met de opdracht remove verwijder je een bluetooth apparaat met een specifiek mac adres uit de lijst met gekende bluetooth apparaten.

Met de exit opdracht sluit je de bluetoothctl af.

De afbeelding hiernaast toont duidelijk dat bij een volgende scan wat later het mac adres van het Nokia Steel horloge is verandert.

Aanwezigheidsdetector

Ondanks dit wat eigenaardige gedrag kan je het Steel Nokia horloge toch gebruiken om de aanwezigheid van een drager te detecteren. Bijvoorbeeld om in te loggen, domoticasystemen, of gewoon omdat het leuk is. Het volgende script verwezenlijkt zoiets:

#!/bin/bash
# Het bestand absent bevat het aantal keer het horloge niet werdt opgemerkt
absent=$(cat absent)
# Aantal keer niet opgemerkt wordt verhoogt
((absent++))
# Voer een bluetooth scan uit en sla de resultaten op in het bestand bluetoothscan.txt
(sleep 1; echo "scan on"; sleep 50; echo "exit") | bluetoothctl > bluetoothscan.txt
# Teller voor het aantal nieuwe bluetooth apparaten
maccounter=0
# Analyseer regel per regel de bluetooth scan resultaten
while read line
do
  # Met welke soort regel hebben we te maken
  var2=$(awk '{ print $2 }'  <<< "$line")
  # We zijn enkel geïnteresseerd in NEW regels
  testvar=$(echo -e '\015\033[K[\033[0;92mNEW\033[0m]')
#  Kleurcodes zichtbaar maken
#  echo "$var2" | hexdump -C -b
  # Analyseer enkel de NEW regels
  if [ "$var2" == "$testvar" ]; then
    # Bepaal het mac adres van het gevonden bluetooth apparaat
    mac=$(awk '{ print $4 }'  <<< "$line")
    # Bepaal de naam van het gevonden bluetooth apparaat
    name=$(awk '{ print $5 " " $6 }'  <<< "$line")
    # Indien er geen naam werd ontvangen, wordt nogmaals het mac adres vermeld
    namemac=$(awk '{ print $5 }'  <<< "$line")
    # Het Nokia Steel horloge werd opgemerkt
    if [ "$name" == "Activite C8" ] || [ "$namemac" == "$mac" ]; then
      # Sla het mac adres van het Nokia Steel horloge op
      mac[$maccounter]=$mac
      # Verhoog de teller voor het volgende Nokia Steel mac adres
      ((maccounter++))
      # Het Nokia Steel horloge is in de buurt
      absent="0"
    fi
  fi
done < bluetoothscan.txt
# Verwijder alle Nokia Steel mac adressen uit de gevonden apparaten lijst
for i in "${mac[@]}"
do
  (sleep 1; echo "remove $i"; sleep 1; echo "exit") | bluetoothctl
done
# Sla het opeenvolgend aantal keer dat de Steel Nokia horloge niet werd gevonden op
echo $absent > absent
# Hieronder kan je absent testen (bijvoorbeeld groter dan 5 opeenvolgende keren niet gevonden)
# en op basis daarvan een actie starten
Bluetoothscan script

Via het pipen (|) van een aantal opdrachten gescheiden door punt komma's en tussen haken kan je opdrachten in bluetoothctl laten uitvoeren. De haken zorgen ervoor dat de opdrachten ertussen parallel worden uitgevoerd (gelijktijdig met het uitvoeren van bluetoothctl). De eerste sleep opdracht zorgt voor een wachttijd van 1 seconde, tijd genoeg om de bluetoothctl opdracht parallel te starten. Daarna wordt de scan opdracht 50 seconden uitgevoerd en wordt bluetoothctl afgesloten.

Meestal meldt het Nokia Steel horloge zich binnen deze 50 seconden. Maar soms ook niet. Start dus nooit geen niet aanwezig actie bij een enkele niet aanwezig melding (absent = 1). Bij mij wordt de afwezig actie pas bij 5 opeenvolgende niet aanwezig meldingen gestart (absent > 5). Je kunt de scan natuurlijk verlengen, maar dit heeft als nadeel dat de reactietijden op aanwezigheid (absent = 0) dan te traag wordt.

Het Nokia Steel horloge meld zich via bluetooth soms aan zonder naam, de naam wordt dan later via een CHG regel toegekend. Vandaar de || (Or) in de if test.

Constant scannen

Op een Raspberry Pi in mijn bureau wordt elke minuut de scan uitgevoerd. Bij aanwezigheid wordt het touchscreen actief, bij afwezigheid wordt het touch screen donker. Om het script elke minuut uit te voeren, gebruik ik een systemd timer. Dit bestaat uit twee bestanden, /etc/systemd/system/PindaNetBluetoothScan.service:

[Unit]
Description=Bluetooth Detection Scan
[Service]
Type=simple
ExecStart=/usr/sbin/bluetoothscan.sh

En /etc/systemd/system/PindaNetBluetoothScan.timer:

[Unit]
Description=Bluetooth Detection Scan
[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
Unit=PindaNetBluetoothScan.service
[Install]
WantedBy=multi-user.target

Het aangepaste script /usr/sbin/bluetoothscan.sh:

#!/bin/bash                                                                                                                          
absent=$(cat /var/www/html/data/absent)                                                                                              
((absent++))                                                                                                                         
(sleep 1; echo "scan on"; sleep 50; echo "exit") | bluetoothctl > /var/www/html/data/bluetoothscan.txt                               
maccounter=0                                                                                                                         
while read line                                                                                                                      
do                                                                                                                                   
  var2=$(awk '{ print $3 }'  <<< "$line")                                                                                            
  testvar=$(echo -e '\015[\033[0;92mNEW\033[0m]')                                                                                    
#  echo "$var2" | hexdump -C -b >> /var/www/html/data/bluetoothscandebug.txt                                                         
  if [ "$var2" == "$testvar" ]; then                                                                                                 
    mac=$(awk '{ print $5 }'  <<< "$line")                                                                                           
    name=$(awk '{ print $6 " " $7 }'  <<< "$line")                                                                                   
    namemac=$(awk '{ print $6 }'  <<< "$line")                                                                                       
    if [ "$name" == "Activite C8" ] || [ "$namemac" == "$mac" ]; then                                                                
      mac[$maccounter]=$mac                                                                                                          
      ((maccounter++))                                                                                                               
      absent="0"                                                                                                                     
    fi                                                                                                                               
  fi                                                                                                                                 
done < /var/www/html/data/bluetoothscan.txt                                                                                          
for i in "${mac[@]}"                                                                                                                 
do                                                                                                                                   
  (sleep 1; echo "remove $i"; sleep 1; echo "exit") | bluetoothctl                                                                   
done                                                                                                                                 
echo $absent > /var/www/html/data/absent                                                                                             
# Logboek aanvullen, vergeet niet regelmatig op te ruimen (rotate)                                                                                                                                     
if [ "$absent" -gt "0" ]; then                                                                                                       
  echo "Afwezig($absent) op $(date)" >> /var/www/html/data/bluetoothscandebug.txt                                                    
else                                                                                                                                 
  echo "Thuis($absent) op $(date)" >> /var/www/html/data/bluetoothscandebug.txt                                                      
fi                                                                                                                                   
                                                                                                                                     
if [ "$absent" -eq "0" ]; then # home
  # activate touchscreen and status led's
  echo 0 > /sys/class/backlight/rpi_backlight/bl_power
  echo 255 > /sys/class/leds/led0/brightness
  echo 255 > /sys/class/leds/led1/brightness
  exit
fi
if [ "$absent" -gt "5" ]; then # not home
  # deactivate touchscreen and status led's
  echo 1 > /sys/class/backlight/rpi_backlight/bl_power
  echo 0 > /sys/class/leds/led0/brightness
  echo 0 > /sys/class/leds/led1/brightness
fi

Let op het analyseren van de bluetoothscanctl regels. De gegevensposities zijn een plaats opgeschoven. Ook testvar, de manier waarop NEW weergegeven wordt, is verschillend. Blijkbaar is de uitvoer van de bluetoothctl uitvoer in systemd verschillend van deze in een klassieke terminal. Zit hier het gebruik van kleur voor iets tussen? Of gebruikt systemd anderen instellingen?

Maak het script uitvoerbaar met:

sudo chmod +x /usr/sbin/bluetoothscan.sh

Breng systemd op de hoogte van de aanpassingen:

sudo systemctl daemon-reload

Zorg dat de systemd timer bij het starten van de Raspberry Pi automatisch start:

sudo systemctl enable PindaNetBluetoothScan.timer

Start de systemd timer manueel, dus zonder complete herstart:

sudo systemctl start PindaNetBluetoothScan.timer

Bekijk de systemd timers:

systemctl list-timers

Bekijk of de systemd timer nog actief is:

sudo systemctl status PindaNetBluetoothScan.timer