Hallo miteinander,
ich habe lange versucht ein sehr zuverlässiges System für die Anwesenheitssteuerung zu finden. Es gibt hier im Forum verschiedene Ansätze um Beacons zu nutzen. Die Scripte sind teilweise einige Jahre alt und ich wollte das hier nochmal zusammenfassen.
Danke an @TomW für die Hilfe und fürs Teilen seiner Scripte.
Mehr Infos gibts vor allem hier:
https://www.symcon.de/forum/threads/30936-Raspberry-Pi-als-iBeacon-Scanner-f%C3%BCr-Schl%C3%BCsselbunddongle/page8?highlight=Beacon
Das Konzept sieht bei mir so aus, dass ich mehrere Pis (am liebsten Zero W) einsetze, um nach den G-Tags zu scannen. Auf diesen Pis läuft ein Script, welches im Hintergrund in Echtzeit alle G-Tags sieht. Das Script wird zusätzlich jede Minute als cronjob neu gestartet um festzustellen, ob der Dienst noch läuft und sich für den Fall, dass das nicht so ist, neu zu starten. Das ist dann notwendig, wenn der Empfänger nicht erreichbar ist, also unser Symcon System.
Es gibt zwei Möglichkeiten. Entweder man installiert auf den Scanner Pis auch Symcon und schickt die Ausgabe an 127.0.0.1 also localhost oder eben an jeden anderen Empfänger im Netzwerk. Die Ausgabe an das Haupt Symcon System zu senden empfiehlt sich nicht, denn es kommen bei meinen 5 Tags ca. 300 000 Meldungen pro Tag an. Das macht das ganze etwas unübersichtlich wenn man andere Meldungen erkennen möchte. Für Symcon selbst wäre das natürlich kein Problem.
Mein Setup sieht im Moment 4 Scanner vor. Auf diesen läuft nur das Script und diese schicken ihre Meldungen an ein Symcon System, welches nur für den Empfang und die Verwaltung der Tags verantwortlich ist. Dieses überträgt bei Änderung dann den Status der Anwesenheit an mein Hauptsystem. Dieses Scanner Empfangs Symcon kann natürlich auf einem der Scanner Pis laufen.
Jetzt gehts los:
Pi mit integriertem Wlan und Bluetooth LE. Also ab 3B. Bestens geeignet Zero W.
Raspbian Buster, Upgrades, Mit eigenem Wlan verbinden und per SSH erreichbar machen.
Anlegen eines init Scripts:
sudo nano /etc/init.d/BLEScan
#! /bin/sh
### BEGIN INIT INFO
# Provides: skeleton
# Required-Start: $syslog
# Required-Stop: $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: BLE Scanner by DarthWeber
# Description:
### END INIT INFO
case "$1" in
start)
echo "BLEScan wird gestartet"
var=`ps -eaf | grep BLEscan0 | wc -l`
if [ $var -lt "2" ]; then
su - root -c "/usr/local/bin/BLEscan0.sh&"
else
echo "BLEscan0 läuft bereits - Bus:"`hciconfig hci0 | grep Bus | cut -d " " -f5`
fi
;;
stop)
echo "BLEScan wird beendet"
# Beende Programm
killall BLEscan0.sh
killall hcitool
;;
restart)
echo "BLEScan wird beendet"
killall BLEscan0.sh
killall hcitool
echo "BLEScan wird gestartet"
# Starte Programm
su - root -c "/usr/local/bin/BLEscan0.sh&"
;;
status)
var=`ps -eaf | grep BLEscan0 | wc -l`
if [ $var -lt "2" ]; then
echo "Error - BLEscan0 läuft nicht"
else
echo "OK - BLEscan0 läuft - Bus:"`hciconfig hci0 | grep Bus | cut -d " " -f5`
fi
;;
*)
echo "Benutzt: /etc/init.d/BLEScan {start|stop|status}"
exit 1
;;
esac
exit 0
ausführbar machen
sudo chmod +x /etc/init.d/BLEScan
Das Script welches scannt anlegen. Hier muss man sich nun die Ziel IP Adresse des Scanner Empfangs Symcon eintragen. Ausserdem ist es wichtig, für jeden Scanner einen eigenen beliebigen ID zu vergeben, so kann man später unterscheiden, welcher Scanner den G-Tag gesehen hat. In dem Beispiel ist die Ziel IP Adresse 192.168.1.20 und der ID des Scanners ist ble1
sudo nano /usr/local/bin/BLEscan0.sh
#!/bin/bash
# BLEscan0.sh
hciconfig hci0 down > /dev/null && hciconfig hci0 up > /dev/null
if [ $? = 0 ]; then
stdbuf -i0 -o0 -e0 hcitool -i hci0 lescan --duplicates | grep --line-buffered "Giga" | sed -u 's/^/ble1 /' > /dev/udp/192.168.1.20/8173
else
echo "hci0 Error" > /dev/udp/192.168.1.20/8173
fi
wieder ausführbar machen
sudo chmod +x /usr/local/bin/BLEscan0.sh
Gestartet wird der Dienst dann per
sudo /etc/init.d/BLEScan start
stop und restart und status gehen auch. Der Empfänger ist aber im Moment noch nicht erreichbar. Deshalb wird da gleich ERROR als status angezeigt.
Jetzt noch in den crontab mit minütlicher Ausführung
sudo crontab -e
Unten die Zeile einfügen:
-
-
-
-
- /etc/init.d/BLEScan start
-
-
-
Speichern. Fertig mit den bash Scripten auf dem Sende Pi.
Der Empfang in Symcon:
I/O Instanzen. Neue Instanz. Multicast Socket
Sendehost: bei mir steht da localhost, spielt aber keine Rolle, es kommt von überall her an.
Sendeport: 8173
Empfänger Host: die IP Adresse des Symcon Scanner Empfangsystems oder All. Das ist das auf dem ihr gerade arbeitet.
Empfangsport: 8173
Socket öffnen
Den Rest so lassen. Änderungen übernehmen.
Damit empfängt Symcon die Nachrichten des Scanners. Wenn ihr jetzt auf dem Sende Pi den Dienst startet oder diesen rebootet, könnt ihr im Debug Fenster die Nachrichten sehen.
Weiter geht es mit einer Register Variable. Diese verarbeitet die eingehenden Nachrichten. Da mein Empfangs Symcon jungfräulich ist, erstelle ich die Register Variable im Hauptverzeichnis und die folgenden Scripte alle unterhalb der Variable. Das hat den Vorteil, dass man sehr einfach die IDs nach Namen bei gleichem Parent finden kann.
IP-Symcon, Hinzufügen Instanz, Register Variable
Diese wird erstellt, kann aber nicht gespeichert werden, solange kein Script hinterlegt ist. Also zurück zum Objektbaum und unterhalb der erstellten Register Variable ein script erstellen. Das Script legt entsprechend der vergebenen Namen Variablen für die Tags an. Unterhalb der Variablen wird eine weitere Variable pro Scanner angelegt. Diese heisst wie der Scanner (ihr erinnert euch an die ID die ihr im Scanner Script vergeben solltet?) und enthält den timestamp des letzten Empfangs. Praktisch für eine watchdog Funktion oder zum Auswerten eines bestimmten Scanners.
Name register
<?php
$s = IPS_GetScript(IPS_GetObjectIDByName ('Tags', IPS_GetParent($_IPS['SELF'])));
include($s['ScriptFile']);
$Verzeichnis = IPS_GetParent($_IPS['SELF']);
function CheckMacs($Data) {
global $Macs;
$Returnvalue = Array();
$Returnvalue[0]=false;
foreach($Macs as $Name => $Address)
{
$pos = strpos($Data,$Address);
if ($pos === false) continue;
$Returnvalue[0]=true;
$Returnvalue[1]=$Name;
$Returnvalue[2]=substr($Data,0,4);
}
return $Returnvalue;
} // end function CheckMacs
function CreateVar($Path,$Name,$Typ) {
global $Verzeichnis;
$Id = @IPS_GetVariableIDByName($Name,$Path);
if ($Id === false)
{
$Id=IPS_CreateVariable($Typ);
IPS_SetParent($Id,$Path);
IPS_SetName($Id,$Name);
}
return $Id;
}
if ($_IPS['SENDER'] == 'RegisterVariable')
{
$str = $_IPS['VALUE'];
$R = CheckMacs($str);
if ($R[0] === true )
{
$id = CreateVar($Verzeichnis,$R[1],0);
SetValue($id,true);
$idx = CreateVar($id,$R[2],1);
SetValue($idx,time());
}
}
?>
Speichern und zurück zur Register Variable. Dort das Script register als Ziel auswählen. Dann oben unter Gateway ändern unseren vorhin erstellen Mulitcast Socket auswählen. Änderungen übernehmen.
Damit das register Script weiss, nach was es suchen muss legen wir wieder unterhalb der Register Variable ein weiteres Script an mit dem Namen
Tags
<?php
$Macs = Array(
'Buzz2' => '58:9E:C6:0E:EC:6F',
'Buzz' => '58:9E:C6:0E:EE:3F',
'Elli' => '58:9E:C6:0E:EF:68',
'Car2' => '58:9E:C6:0E:ED:53',
'Car1' => '58:9E:C6:0E:EC:CB'
);
?>
Jetzt sollten Eure Tags angelegt werden. Falls ihr die MAC Adressen eurer Tags nicht kennt, könnt ihr diese im Debug Fenster des Mulicast Socket sehen. Dazu stellt ihr von HEX auf Text um.
Damit diese auch auf abwesend gesetzt werden, brauchen wir ein weiteres Script. Hier sind zwei Minuten eingestellt und ich rufe das Script mit einem Timer alle 30s auf. Das ist aber natürlich beliebig.
Wieder unterhalb der Register Variable
Name setfalse
<?php
$now = time();
$s = IPS_GetScript(IPS_GetObjectIDByName ('Tags', IPS_GetParent($_IPS['SELF'])));
include($s['ScriptFile']);
function setfalse()
{
global $Macs, $now;
foreach($Macs as $Name => $Address)
{
$eins=GetValue(IPS_GetObjectIDByName ($Name, IPS_GetParent($_IPS['SELF'])));
$eins_info = IPS_GetVariable(IPS_GetObjectIDByName ($Name, IPS_GetParent($_IPS['SELF'])));
$time_eins = $eins_info["VariableUpdated"];
if ($now - $time_eins > 120) if (GetValue(IPS_GetObjectIDByName ($Name, IPS_GetParent($_IPS['SELF'])))) SetValue(IPS_GetObjectIDByName ($Name, IPS_GetParent($_IPS['SELF'])),false);
}
}
setfalse();
?>
Damit sollte alles laufen.
Die Übertragung ins Hauptsystem mache ich per webhook. Das ist im ursprünglichen Thread sehr gut erklärt. Gleich auf der ersten Seite.
Hier mein Script dazu. Der Webhook muss natürlich im Hauptsystem eingerichtet werden und zwar für jeden Beacon. Dazu die passende Variable und ein hook Script darunter. Damit das script funktioniert, müssen die hooks im Empfanhssystem so aussehen:
/hook/Buzz
also /hook/‚Name des Tags‘ so wie er im Script Tags hinterlegt wurde.
Wieder unterhalb der Register Variable
webhhook
<?php
$now = time();
$s = IPS_GetScript(IPS_GetObjectIDByName ('Tags', IPS_GetParent($_IPS['SELF'])));
include($s['ScriptFile']);
function webhook()
{
global $Macs, $now;
foreach($Macs as $Name => $Address)
{
$status = GetValue(IPS_GetObjectIDByName ($Name, IPS_GetParent($_IPS['SELF'])));
$string = "http://192.168.1.21:3777/hook/{$Name}?Status={$status}";
file_get_contents($string);
}
}
webhook();
?>
Auf der Empfangsseite sieht mein Script unterhalb der Empfangsvariablen so aus
hook
<?
if ($_GET['Status'] == 1)
{
SetValue(IPS_GetParent($_IPS['SELF']), true);
}
if ($_GET['Status'] == 0)
{
SetValue(IPS_GetParent($_IPS['SELF']), false);
}
?>
Viel Spass und danke an alle für die großartige Vorarbeit!
Sebastian