Hi zusammen,
eigentlich sollte meine alte KS300 Wetterstation als letztes dran glauben, aber bei der Umstellung von der WU-API auf DarkSky bin ich auch auf einen Beitrag mit dieser Wetterstation gestoßen. In befreundeten Foren tauchten dann auch schon die ersten Programme zum Auslesen der Daten auf, was mich dazu bewegte zuzuschlagen
Dank einer hervorragenden Protokollbeschreibung im Hersteller-Forum war der Rest nur noch Fleißarbeit, welche ich Euch nicht vorenthalten möchte.
Anbei der erste Wurf für das Auslesen und Aufarbeiten der Daten. Laut Protokoll geht da noch mehr, aber fürs Erste sollte es reichen!
Ich lade gern alle ein es zu erweitern
<?php
################################################################################
# Scriptbezeichnung: Weather.WiFiStation.ips.php
# Version: 1.0.20190319
# Author: Heiko Wilknitz (@Pitti)
#
# Abruf von Wetterdaten von einer WS980WiFi Station
#
# Beschreibung des Kommunikationsprotokolls der Wetterstation von
# R. Petzoldt (R.Petzoldt@web.de)
# https://www.elv.de/topic/protokolldefinition-zum-datenaustausch-ws980-zum-pc.html
#
# ------------------------------ Installation ----------------------------------
#
# Dieses Skript richtet automatisch alle nötigen Objekte bei manueller
# Ausführung ein. Eine weitere manuelle Ausführung setzt alle benötigten Objekte
# wieder auf den Ausgangszustand.
#
# - Neues Skript erstellen
# - Diesen PHP-Code hineinkopieren
# - Skript abspeichern
# - Skript ausführen
#
# ------------------------------ Changelog -------------------------------------
#
# 19.03.2019 - Initalversion (v1.0)
#
# ----------------------------- Konfigruration ---------------------------------
#
#
################################################################################
// INSTALLATION
if ($_IPS['SENDER']=='Execute') {
$pos = 0;
// Variablen
$vid = CreateVariableByName($_IPS['SELF'], 'Innentemperatur', 2, '~Temperature', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Außentemperatur', 2, '~Temperature', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Gefühlte Temperatur', 2, '~Temperature', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Taupunkt', 2, '~Temperature', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Hitzeindex', 2, '~Temperature', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Innenluftfeuchtigkeit', 2, '~Intensity.1', $pos++);
IPS_SetIcon($vid, 'Drops');
$vid = CreateVariableByName($_IPS['SELF'], 'Außenluftfeuchtigkeit', 2, '~Intensity.1', $pos++);
IPS_SetIcon($vid, 'Drops');
$vid = CreateVariableByName($_IPS['SELF'], 'Absoluter Luftdruck', 2, '~AirPressure.F', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Relativer Luftdruck', 2, '~AirPressure.F', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Windgeschwindigkeit', 2, '~WindSpeed.kmh', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Windrichtung', 2, '~WindDirection.Text', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Windböe', 2, '~WindSpeed.kmh', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/h', 2, '~Rainfall', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Tag', 2, '~Rainfall', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Woche', 2, '~Rainfall', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Monat', 2, '~Rainfall', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Jahr', 2, '~Rainfall', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Gesamt', 2, '~Rainfall', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'Beleuchtungsstärke', 2, '~Illumination.F', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'UV Strahlung', 1, '', $pos++);
$vid = CreateVariableByName($_IPS['SELF'], 'UV Index', 1, '', $pos++);
// Service Update Event
$eid = CreateEventByName($_IPS['SELF'], "StationUpdate", 5);
IPS_SetHidden($eid, true);
IPS_SetPosition($eid, -1);
// Haben wir eine Station?
FindStation();
// Und wenn ja, welche Version
GetVersion();
}
// TIMER EVENT
else if($_IPS['SENDER'] == 'TimerEvent') {
// Aktuelle Daten
GetCurrentData();
}
# ----------------------------- Functions ---------------------------------
// Extrahiert die aktuellen Wetterdaten
function GetCurrentData()
{
// Sende-Befehl {0xff, 0xff, 0x0b, 0x00, 0x06, 0x06, 0x04, 0x19}
$cmd=chr(0xFF).chr(0xFF).chr(0x0B).chr(0x00).chr(0x06).chr(0x04).chr(0x04).chr(0x19);
// IP Adresse
$vid = CreateVariableByName($_IPS['SELF'], 'WS-ADDRESS', 3);
$ip = GetValue($vid);
$vid = CreateVariableByName($_IPS['SELF'], 'WS-PORT', 1);
$port = GetValue($vid);
// Command senden
if (false ===($data = ReceiveData($ip, $port, $cmd))) {
exit();
}
// Formatieren/Extrahieren
$format =
'x7/' . # Override first 7 bytes
'n1Innentemperatur/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1Aussentemperatur/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1Taupunkt/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1Gefuehlte/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1Hitze/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'C1Innenfeuchte/' . # Get the next 1 byte
'x1/' . # Override 1 byte
'C1Aussenfeuchte/' . # Get the next 1 byte
'x1/' . # Override 1 byte
'n1AbsDruck/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1RelDruck/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1Windrichtung/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1Windspeed/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'n1Windboe/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'N1RegenH/' . # Get the next 4 bytes
'x1/' . # Override 1 byte
'N1RegenD/' . # Get the next 4 bytes
'x1/' . # Override 1 byte
'N1RegenW/' . # Get the next 4 bytes
'x1/' . # Override 1 byte
'N1RegenM/' . # Get the next 4 bytes
'x1/' . # Override 1 byte
'N1RegenY/' . # Get the next 4 bytes
'x1/' . # Override 1 byte
'N1RegenS/' . # Get the next 4 bytes
'x1/' . # Override 1 byte
'N1Licht/' . # Get the next 4 bytes
'x1/' . # Override 1 byte
'n1UvRaw/' . # Get the next 2 bytes
'x1/' . # Override 1 byte
'C1UvIdx'; # Get the next 2 bytes
$array = unpack($format, $data);
// Daten manipulieren
if($array['Innentemperatur'] >= pow(2, 15)) $array['Innentemperatur'] -= pow(2, 16);
if($array['Aussentemperatur'] >= pow(2, 15)) $array['Aussentemperatur'] -= pow(2, 16);
if($array['Gefuehlte'] >= pow(2, 15)) $array['Gefuehlte'] -= pow(2, 16);
if($array['Taupunkt'] >= pow(2, 15)) $array['Taupunkt'] -= pow(2, 16);
if($array['Hitze'] >= pow(2, 15)) $array['Hitze'] -= pow(2, 16);
// Daten kopieren
$vid = CreateVariableByName($_IPS['SELF'], 'Innentemperatur', 2);
SetValue($vid, $array['Innentemperatur'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Außentemperatur', 2);
SetValue($vid, $array['Aussentemperatur'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Gefühlte Temperatur', 2);
SetValue($vid, $array['Gefuehlte'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Taupunkt', 2);
SetValue($vid, $array['Taupunkt'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Hitzeindex', 2);
SetValue($vid, $array['Hitze'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Innenluftfeuchtigkeit', 2);
SetValue($vid, $array['Innenfeuchte'] / 100.);
$vid = CreateVariableByName($_IPS['SELF'], 'Außenluftfeuchtigkeit', 2);
SetValue($vid, $array['Aussenfeuchte'] / 100.);
$vid = CreateVariableByName($_IPS['SELF'], 'Absoluter Luftdruck', 2);
SetValue($vid, $array['AbsDruck'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Relativer Luftdruck', 2);
SetValue($vid, $array['RelDruck'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Windgeschwindigkeit', 2);
SetValue($vid, ($array['Windspeed'] /10.) * 3.6);
$vid = CreateVariableByName($_IPS['SELF'], 'Windrichtung', 2);
SetValue($vid, $array['Windrichtung']);
$vid = CreateVariableByName($_IPS['SELF'], 'Windböe', 2);
SetValue($vid, ($array['Windboe'] /10.) * 3.6);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/h', 2);
SetValue($vid, $array['RegenH'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Tag', 2);
SetValue($vid, $array['RegenD'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Woche', 2);
SetValue($vid, $array['RegenW'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Monat', 2);
SetValue($vid, $array['RegenM'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Jahr', 2);
SetValue($vid, $array['RegenY'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Niederschlag/Gesamt', 2);
SetValue($vid, $array['RegenS'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'Beleuchtungsstärke', 2);
SetValue($vid, $array['Licht'] / 10.);
$vid = CreateVariableByName($_IPS['SELF'], 'UV Strahlung', 1);
SetValue($vid, $array['UvRaw']);
$vid = CreateVariableByName($_IPS['SELF'], 'UV Index', 1);
SetValue($vid, $array['UvIdx']);
}
// WiFi Wetter Station finden (UDP Broadcast)
function FindStation($ip = '255.255.255.255', $port = 46000)
{
// Sende-Befehl {0xff, 0xff, 0x12, 0x00, 0x04, 0x16}
$cmd=chr(0xFF).chr(0xFF).chr(0x12).chr(0x00).chr(0x04).chr(0x16);
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, 1);
// Befehl senden
socket_sendto($sock, $cmd, strlen($cmd), 0, $ip, $port);
// Antwort holen
socket_recvfrom($sock, $buf, 39, 0, $ip, $port);
// HexDump($buf);
socket_close($sock);
// Auslesen
$format =
'x5/' . # Get the first 2 bytes
'C6MAC/' . # Get the next 6 byte
'C4IP/' . # Get the next 4 byte
'n1PORT/' . # Get the next 2 byte
'x1/' . # Get the next 1 byte
'A20NAME/' . # Get the next 20 byte
'x1'; # Get the next 1 byte
$array = unpack($format, $buf);
$pos = 100;
$vid = CreateVariableByName($_IPS['SELF'], 'WS-NAME', 3, '', $pos++);
SetValue($vid, $array['NAME']);
IPS_SetHidden($vid, true);
$vid = CreateVariableByName($_IPS['SELF'], 'WS-MAC', 3, '', $pos++);
SetValue($vid, dechex($array['MAC1']).':'.dechex($array['MAC2']).':'.dechex($array['MAC3']).':'.dechex($array['MAC4']).':'.dechex($array['MAC5']).':'.dechex($array['MAC6']));
IPS_SetHidden($vid, true);
$vid = CreateVariableByName($_IPS['SELF'], 'WS-ADDRESS', 3, '', $pos++);
SetValue($vid, $array['IP1'].'.'.$array['IP2'].'.'.$array['IP3'].'.'.$array['IP4']);
IPS_SetHidden($vid, true);
$vid = CreateVariableByName($_IPS['SELF'], 'WS-PORT', 1, '', $pos++);
SetValue($vid, $array['PORT']);
IPS_SetHidden($vid, true);
}
// Ermittelt die Firmware Version
function GetVersion()
{
// Sende-Befehl {0xff, 0xff, 0x50, 0x03, 0x53}
$cmd=chr(0xFF).chr(0xFF).chr(0x50).chr(0x03).chr(0x53);
// IP Adresse
$vid = CreateVariableByName($_IPS['SELF'], 'WS-ADDRESS', 3);
$ip = GetValue($vid);
$vid = CreateVariableByName($_IPS['SELF'], 'WS-PORT', 1);
$port = GetValue($vid);
// Command senden
if (false ===($data = ReceiveData($ip, $port, $cmd))) {
exit();
}
$format =
'@5/' . # Override the first 5 bytes
'A17Name'; # Get the next 17 byte
$array = unpack($format, $data);
$pos = strrpos($array['Name'], "V");
if ($pos === false) {
$fw = $array['Name'];
} else {
$fw = substr($array['Name'], $pos);
}
$vid = CreateVariableByName($_IPS['SELF'], 'WS-VERSION', 3, '', 200);
SetValue($vid, $fw);
IPS_SetHidden($vid, true);
}
// Array daten als HEX Dump ausgeben; 16 Bytes pro Zeile
function HexDump($data, $newline=PHP_EOL)
{
static $from = '';
static $to = '';
static $width = 16; # number of bytes per line
static $pad = '.'; # padding for non-visible characters
if ($from==='') {
for ($i=0; $i<=0xFF; $i++) {
$from .= chr($i);
$to .= ($i >= 0x20 && $i <= 0x7E) ? chr($i) : $pad;
}
}
$hex = str_split(bin2hex($data), $width*2);
$chars = str_split(strtr($data, $from, $to), $width);
$offset = 0;
foreach ($hex as $i => $line) {
echo sprintf('%06X',$offset).' : '.strtoupper(implode(' ', str_split($line,2))) . ' [' . $chars[$i] . ']' . $newline;
$offset += $width;
}
}
// TCP Soccket Daten senden und Resultat zurückliefern
function ReceiveData($ip, $port, $cmd)
{
// TCP Socket
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
IPS_LogMessage('WetterStation', 'socket_create() fehlgeschlagen: Grund: ' . socket_strerror(socket_last_error()));
return false;
}
if (socket_connect($sock, $ip, $port) === false) {
IPS_LogMessage('WetterStation', 'socket_connect() fehlgeschlagen: Grund: ' . socket_strerror(socket_last_error($sock)));
return false;
}
if (socket_write($sock, $cmd, strlen($cmd)) === false) {
IPS_LogMessage('WetterStation', 'socket_write() fehlgeschlagen: Grund: ' . socket_strerror(socket_last_error($sock)));
return false;
}
if (false === ($buf = socket_read($sock, 2048, PHP_BINARY_READ ))) {
IPS_LogMessage('WetterStation', 'socket_read() fehlgeschlagen: Grund: '.socket_strerror(socket_last_error($sock)));
return false;
}
socket_close ($sock);
// Kommt ab und zu mal vor???
if(strlen($buf) == 0) {
IPS_LogMessage('WetterStation', 'socket_read() fehlgeschlagen: Grund: Länge Daten gleich Null');
return false;
}
return $buf;
}
// Erzeugt eine Variable unterhalb {id} mit dem Namen {name} vom Typ [type}
// Existiert die Variable schon wird diese zurückgeliefert.
// Type: 0 = Boolean, 1 = Integer, 2 = Float, 3 = String
// Profile: leer für kein Profil, sonst Profilnamen
function CreateVariableByName($id, $name, $type, $profile = '', $pos = null)
{
$vid = @IPS_GetVariableIDByName($name, $id);
if($vid===false) {
$vid = IPS_CreateVariable($type);
IPS_SetParent($vid, $id);
IPS_SetName($vid, $name);
if($profile !== '') {
IPS_SetVariableCustomProfile($vid, $profile);
}
if($pos !== null) {
IPS_SetPosition($vid, $pos);
}
}
return $vid;
}
// Erzeugt einen Timer unterhalb {id} mit dem Namen {name} um Zeit {time}
// Existiert das Event schon wird diese zurückgeliefert.
function CreateEventByName($id, $name, $time)
{
$eid = @IPS_GetEventIDByName($name, $id);
if($eid===false) {
// zyklisches Event
$eid = IPS_CreateEvent(1);
IPS_SetParent($eid, $id);
IPS_SetName($eid, $name);
}
// tägliches Event
IPS_SetEventCyclic($eid, 0, 1, 0, 0, 2, $time);
IPS_SetEventActive($eid, true);
return $eid;
}
?>
Ciao Heiko
PS: Der Monitor zu dem Teil ist echt h**lich