PHP-Code:
<?php
//----------------------------------------Version 1.6.3 Datum 28.12.2020 BestEx --------------------------------
namespace MachineLearning\Regression;
//---------------------------------------------- Parameter -------------------------------------------
$profil_update = true; // Bei True werden die Profile überschrieben und sichergestellt das die genutzen variablen über die Korrekten Profile verfügen, Das Programm wird nicht weiter ausgeführt
// Diese Funktion steht nur über die Ausführung durch die Konsole zur Verfügung und wird bei der Ausführung durch die Registervariable deaktiviert
$profile_prefix = 'Tempest_';
$max_age = 20; // Maximaler unterschied zwischen time() und Zeitstempel der Nachricht. Ältere Werte werden weggeworfen
//Das Skript verfügt jetzt über die Fähigkeit doppelte Nachrichten zu erkennen sowie alte Nachrichten in die Zeitlinie einzufügen
//Jeder Message Typ hat eine Nachricht mit einem Zeitstempel (Nicht zu verwechseln mit dem Zeitstempel im Archiv der die Zeit der speicherung festhält)
//Alle Zeitstempel von Tempest werden in einer Variablen gespeichert ('Time Epoch','timestamp','Rain Start Event')
// Beim Empfang einer neuen Nachricht schaut das Skript nach ob der übermittelte Zeitstempel bereits vorhanden ist. Falls das der Fall ist werden
// die Nachrichten des betreffenden Typs alle verworfen. Das Skript anylysiert auch ob der Zeitstempel des Typs älter ist als der zuletzt
//abgespeicherte Zeitstempel des Message Typs. Ist das der Fall werden alle Nachrichten des Typs in die Zeitlinien der variablen integriert
//anschließend werden die Variablen reaggregiert.
//Diese Funktion wird "check_time_stamp_for_message_type" genannt und nur für den Nachrichten Typ "obs_st" angewandt.
//Bei den Variablen im array "$NO_Check_Variable_Names" werden keine alten Werte eingefügt
// Das Skript wird auch beendet ohne die Daten abzuspeichern für den Fall das die Temparatur und die Luftfeuchtigkeit 0 sind.
$NO_Check_Variable_Names = array('Average','Battery','Battery Status','Counter Slope Datasets','Median','Report Interval','Rohdaten','Slope','time_delta','Time Epoch','timestamp','Rain Start Event','stamp_delta');
$debug = false;
// Kleines Schmankerl : Die Wetterstation schaltet abhängig von der Batterie Spannung bestimmte Funktionen ab bzw. verändert die Sendefrequenz
// um ein schwingverhalten beim laden zu vermeiden ist das verhalten beim laden anders als beim entladen (Hysterese). Abhängig vom zustand
// Laden oder Entladen verändert das Skript die assoziationen des Profils. Um festzustellen ob die Batterie lädt wird eine lineare
// Regression (y = mx+b) über die Datenpunkte (nr_of_datapoints_for_regression) gelegt. Abhängig von der Steigung (m) wird über die Einstellung trigger_value_slope
// das Profil auf laden geschaltet. Das ganze ist noch experimentell.
$experimental = true;
$trigger_value_slope = 0.0000007;
$nr_of_datapoints_for_regression = 90;
$var_id_testdata = 48343; // Wenn jemand testen will kann er die ID der Rohdaten Variablen hier eintragen oder
$var_id_testdata = 14820;
$var_id_testdata = 11726;
$var_id_testdata = 10489;
$var_id_testdata = 22052;
$var_id_testdata = 25581;
$var_id_testdata = 26321;
$var_id_testdata = 13526;
$var_id_testdata = 0;
$event_precip =
'
{
"serial_number": "SK-00008453",
"type":"evt_precip",
"hub_sn": "HB-00000001",
"evt":[1493322344]
}
';
$event_evt_strike =
'
{
"serial_number": "AR-00004049",
"type":"evt_strike",
"hub_sn": "HB-00000001",
"evt":[1493322445,27,3848]
}
';
$event_rapid_wind =
'
{
"serial_number": "SK-00008453",
"type":"rapid_wind",
"hub_sn": "HB-00000001",
"ob":[1493322445,2.3,128]
}
';
$observation_obs_st =
'
{
"serial_number": "ST-00000512",
"type": "obs_st",
"hub_sn": "HB-00013030",
"obs": [
[1588948615,0.18,0.22,0.27,144,6,1017.57,22.37,50.26,328,0.03,3,0.000000,0,0,0,2.410,1]
],
"firmware_revision": 129
}
';
$status_device_status =
'
{
"serial_number": "AR-00004049",
"type": "device_status",
"hub_sn": "HB-00000001",
"timestamp": 1510855923,
"uptime": 2189,
"voltage": 3.50,
"firmware_revision": 17,
"rssi": -17,
"hub_rssi": -87,
"sensor_status": 0,
"debug": 0
}
';
$status_hub_status =
'
{
"serial_number":"HB-00000001",
"type":"hub_status",
"firmware_revision":"35",
"uptime":1670133,
"rssi":-62,
"timestamp":1495724691,
"reset_flags": "BOR,PIN,POR",
"seq": 48,
"fs": [1, 0, 15675411, 524288],
"radio_stats": [2, 1, 0, 3, 2839],
"mqtt_stats": [1, 0]
}
';
$json_test_string = $observation_obs_st; /* einen json Test String aus der Dokumentation nutzen : https://weatherflow.github.io/Tempest/api/udp/v143/*/
//-----------------------------------------------Nichtgewünschte Variablen bitte auskommentieren -------
// Falls nicht alle variablen gewünscht sind einfach die Zeilen auskommentieren. Die Variablen und die Kategorien werden erst angelegt wenn eine
// entsprechende Nachricht von der Wetterstation empfangen wurde
$var_liste['obs_st'] = [
0 => 'Time Epoch',
1 => 'Wind Lull (minimum 3 second sample)',
2 => 'Wind Avg (average over report interval)',
3 => 'Wind Gust (maximum 3 second sample)',
4 => 'Wind Direction',
5 => 'Wind Sample Interval',
6 => 'Station Pressure',
7 => 'Air Temperature',
8 => 'Relative Humidity',
9 => 'Illuminance',
10 => 'UV',
11 => 'Solar Radiation',
12 => 'Precip Accumulated',
13 => 'Precipitation Type',
14 => 'Lightning Strike Avg Distance',
15 => 'Lightning Strike Count',
16 => 'Battery',
17 => 'Report Interval',
18 => 'Slope',
19 => 'Rohdaten',
20 => 'Battery Status',
21 => 'System Condition',
22 => 'Counter Slope Datasets',
23 => 'Average',
24 => 'Median'
// 25 => 'time_delta',
// 26 => 'stamp_delta'
];
$var_liste['device_status'] = [
0 => 'serial_number',
1 => 'type',
2 => 'hub_sn',
3 => 'timestamp',
4 => 'uptime',
5 => 'voltage',
6 => 'firmware_revision',
7 => 'rssi',
8 => 'hub_rssi',
9 => 'sensor_status',
10 => 'debug',
11 => 'Rohdaten'
// 12 => 'time_delta'
];
$var_liste['hub_status'] = [
0 => 'serial_number',
1 => 'type',
2 => 'firmware_revision',
3 => 'uptime',
4 => 'rssi',
5 => 'timestamp',
6 => 'reset_flags',
7 => 'seq',
8 => 'fs',
9 => 'radio_stats',
10 => 'mqtt_stats',
11 => 'Version',
12 => 'Reboot Count',
13 => 'I2C Bus Error Count',
14 => 'Radio Status',
15 => 'Radio Network ID',
16 => 'Rohdaten',
// 17 => 'time_delta
];
$var_liste['evt_strike'] = [
0 => 'Time Epoch',
1 => 'Distance',
2 => 'Energy',
3 => 'Rohdaten',
// 4 => 'time_delta
];
$var_liste['rapid_wind'] = [
0 => 'Time Epoch',
1 => 'Wind Speed',
2 => 'Wind Direction',
3 => 'Rohdaten'
// 4 => 'time_delta'
];
$var_liste['evt_precip'] = [
0 => 'Rain Start Event',
1 => 'Rohdaten',
// 2 => 'time_delta
];
$var_liste[IPS_GetName($_IPS['SELF'])] = [
0 => 'Rohdaten'
];
//-------------------------------------- Ab hier bitte nichts mehr verändern --------------------------
if ($_IPS['SENDER'] == "RegisterVariable")
{
$data = $_IPS['VALUE'];
$array = json_decode($data, true);
$execute = true;
$profil_update = false;
}
elseif($_IPS['SENDER'] == "Execute")
{
$data = $json_test_string;
$array = json_decode($data, true);
if( IPS_VariableExists($var_id_testdata))
{
$data = GetValueString($var_id_testdata); // Für Testzwecke ID der Variablen für Rohdaten eingeben. Die Variable wird in jeder Kategorie angelegt und gefüllt wenn das Script über die Registervariable gestartet wird.
$array = unserialize($data);
}
else
{
}
if(is_array($array))
{
if(array_key_exists('type',$array))
{
$execute = true;
}
else
{
$execute = false;
}
}
else
{
$execute = false;
}
}
else
{
return;
}
$config = initialize_framework($profile_prefix);
$prefix_profil_name = $config['prefix_profil_name'];
$variable_types = $config['variable_types'];
foreach ($config['profile_names'] as $key => $val)
{
check_profile($profil_update,$prefix_profil_name.$key,$val['type'],$val['nachkommastellen'],$val['prefix'],$val['suffix'],$val['association'],$val['minimalwert'],$val['maximalwert'],$val['schrittweite'],false);
}
if(!$execute)
{
if($profil_update)
{
echo "Profil Update durchgeführt ! \n";
}
else
{
echo "Keine Eingangsdaten Program vorzeitig beendet ! \n";
}
return;
}
$cat_id = check_category($array['type'],$_IPS['SELF']);
check_variable($profil_update,'NO CHECKS',serialize($array),$_IPS['SELF'],'Rohdaten',$variable_types['String'],'~TextBox','0',false,'1',time());
if($array['type'] == "obs_st")
{
$detailed_data = $array['obs'][0];
$data_descriptions = $config['data_descriptions']['obs_st'];
$temperature = $array['obs'][0][7];
$humidity = $array['obs'][0][8];
if(($temperature == 0) and ($humidity == 0))
{
IPS_LogMessage('TempestC1',' Werte korrupt und verworfen ');
return;
}
$check = check_time_stamp_for_message_type($cat_id,'Time Epoch',$array['obs'][0][0]);
if($check === 'INVALID' ) return;
foreach($detailed_data as $key => $val)
{
foreach($config['profile_names'] as $key1 => $val1)
{
if(in_array($data_descriptions[$key],$val1,true))
{
check_variable($profil_update,$check,$val,$cat_id, $data_descriptions[$key],$val1['type'], $prefix_profil_name.$key1 ,'0',true,'2',$array['obs'][0][0],$check);
}
else
{
}
}
}
check_variable($profil_update,'NO CHECKS',serialize($array),$cat_id,'Rohdaten',$variable_types['String'],'~TextBox','0',false,'3',time());
//check_variable($profil_update,'NO CHECKS',$array['obs'][0][0],$cat_id,'time_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'28',$array['obs'][0][0]);
system_state($profil_update,'NO CHECKS',$cat_id,$config,$trigger_value_slope,$variable_types,$prefix_profil_name,$nr_of_datapoints_for_regression,$experimental);
}
elseif($array['type'] == "device_status")
{
$detailed_data = $array;
$data_descriptions = $config['data_descriptions']['device_status'];
$check = check_time_stamp_for_message_type($cat_id,'timestamp',$detailed_data['timestamp']);
if($check === 'INVALID' ) return;
$check = 'NO CHECKS';
foreach($detailed_data as $key => $val)
{
$index = 0;
foreach($config['profile_names'] as $key1 => $val1)
{
if(in_array($key,$val1,true))
{
if($key == 'sensor_status')
{
$val = $val & bindec('111111111');
}
else
{
}
check_variable($profil_update,$check,$val,$cat_id, $key,$val1['type'], $prefix_profil_name.$key1 ,'0',true,'4',$detailed_data['timestamp']);
}
else
{
}
}
$index = $index+1;
}
//check_variable($profil_update,$check, $detailed_data['timestamp'],$cat_id,'time_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'28',$detailed_data['timestamp']);
check_variable($profil_update,$check,serialize($array),$cat_id,'Rohdaten',$variable_types['String'],'~TextBox','0',false,'5',time());
}
elseif($array['type'] == "hub_status")
{
$detailed_data = $array;
$data_descriptions = $config['data_descriptions']['hub_status'];
$array_variablen = array("radio_stats");
$profil_names = $config['data_descriptions']['radio_stats'];
$check = check_time_stamp_for_message_type($cat_id,'timestamp',$detailed_data['timestamp']);
if($check === 'INVALID' ) return;
$check = 'NO CHECKS';
foreach($detailed_data as $key => $val)
{
$index = 0;
foreach($config['profile_names'] as $key1 => $val1)
{
if(in_array($key,$val1,true))
{
if(!in_array($key,$array_variablen,true))
{
check_variable($profil_update,$check,$val,$cat_id, $key,$val1['type'], $prefix_profil_name.$key1 ,'0',true,'6',$detailed_data['timestamp']);
}
else
{
foreach($val as $key2 => $val2)
{
foreach($config['profile_names'] as $key3 => $val3)
{
if(in_array($profil_names[$key2],$val3,true))
{
check_variable($profil_update,$check,$val2,$cat_id, $profil_names[$key2],$val3['type'], $prefix_profil_name.$key3,'0',true,'7',$detailed_data['timestamp']);
}
}
}
}
}
else
{
}
}
$index = $index+1;
}
//check_variable($profil_update,$check, $detailed_data['timestamp'],$cat_id,'time_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'28',$detailed_data['timestamp']);
check_variable($profil_update,$check,serialize($array),$cat_id,'Rohdaten',$variable_types['String'],'~TextBox','0',false,'8',time());
}
elseif($array['type'] == "rapid_wind")
{
$time_stamp = $array['ob'][0];
$check = check_time_stamp_for_message_type($cat_id,'Time Epoch', $time_stamp);
if($check === 'INVALID' ) return;
$check = 'NO CHECKS';
check_variable($profil_update,$check,$array['ob'][0],$cat_id,'Time Epoch',$variable_types['Integer'],$prefix_profil_name.'~UnixTimestamp','0',true,'9',$time_stamp);
check_variable($profil_update,$check,$array['ob'][1],$cat_id,'Wind Speed',$variable_types['Float'],$prefix_profil_name.'km_pro_stunde','0',true,'10',$time_stamp);
check_variable($profil_update,$check,$array['ob'][2],$cat_id,'Wind Direction',$variable_types['Integer'],$prefix_profil_name.'degrees','0',true,'11',$time_stamp);
check_variable($profil_update,$check,serialize($array),$cat_id,'Rohdaten',$variable_types['String'],'~TextBox','0',false,'12',time());
// check_variable($profil_update,$check, $array['ob'][0],$cat_id,'time_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'28',$time_stamp);
}
elseif($array['type'] == "evt_precip")
{
$time_stamp = $array['evt'][0];
$check = check_time_stamp_for_message_type($cat_id,'Rain Start Event', $time_stamp);
if($check === 'INVALID' ) return;
$check = 'NO CHECKS';
check_variable($profil_update,$check,$array['evt'][0],$cat_id,'Rain Start Event',$variable_types['Integer'],$prefix_profil_name.'~UnixTimestamp','0',true,'13',$time_stamp);
check_variable($profil_update,$check,serialize($array),$cat_id,'Rohdaten',$variable_types['String'],'~TextBox','0',false,'14',time());
//check_variable($profil_update,$check, $array['evt'][0],$cat_id,'time_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'28',$time_stamp);
}
elseif($array['type'] == "evt_strike")
{
$time_stamp = $array['evt'][0];
$check = check_time_stamp_for_message_type($cat_id,'Time Epoch', $time_stamp);
if($check === 'INVALID' ) return;
$check = 'NO CHECKS';
check_variable($profil_update,$check,$array['evt'][0],$cat_id,'Time Epoch',$variable_types['Integer'],$prefix_profil_name.'~UnixTimestamp','0',true,'15',$time_stamp);
check_variable($profil_update,$check,$array['evt'][1],$cat_id,'Distance',$variable_types['Float'],$prefix_profil_name.'km','0',true,'16',$time_stamp);
check_variable($profil_update,$check,$array['evt'][2],$cat_id,'Energy',$variable_types['Integer'],$prefix_profil_name.'energy','0',true,'17',$time_stamp);
check_variable($profil_update,$check,serialize($array),$cat_id,'Rohdaten',$variable_types['String'],'~TextBox','0',false,'18',time());
//check_variable($profil_update,$check, $array['evt'][0],$cat_id,'time_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'28',$time_stamp);
}
else
{
check_variable($profil_update,'NO CHECKS',serialize($array),$cat_id,'Rohdaten',$variable_types['String'],'~TextBox','0',false,'19',time());
}
function initialize_framework($profile_prefix)
{
Global $debug;
$config['prefix_profil_name'] = $profile_prefix;
//----------------------------------------------------------------------------------------------
$green = hexdec('0x006400');
$red = hexdec('0xFF0000');
$orange = hexdec('0xFFA500');
$yellow = hexdec('0xcbcf00');
$blue = hexdec('0x0000FF');
$purple = hexdec('0x800080');
//----------------------------------------------------------------------------------------------
$config['data_descriptions']['obs_st'] = [
0 => 'Time Epoch',
1 => 'Wind Lull (minimum 3 second sample)',
2 => 'Wind Avg (average over report interval)',
3 => 'Wind Gust (maximum 3 second sample)',
4 => 'Wind Direction',
5 => 'Wind Sample Interval',
6 => 'Station Pressure',
7 => 'Air Temperature',
8 => 'Relative Humidity',
9 => 'Illuminance',
10 => 'UV',
11 => 'Solar Radiation',
12 => 'Precip Accumulated',
13 => 'Precipitation Type',
14 => 'Lightning Strike Avg Distance',
15 => 'Lightning Strike Count',
16 => 'Battery',
17 => 'Report Interval'
];
$config['data_descriptions']['device_status'] = [
0 => 'serial_number',
1 => 'type',
2 => 'hub_sn',
3 => 'timestamp',
4 => 'uptime',
5 => 'voltage',
6 => 'firmware_revision',
7 => 'rssi',
8 => 'hub_rssi',
9 => 'sensor_status',
10 => 'debug'
];
$config['data_descriptions']['hub_status'] = [
0 => 'serial_number',
1 => 'type',
2 => 'firmware_revision',
3 => 'uptime',
4 => 'rssi',
5 => 'timestamp',
6 => 'reset_flags',
7 => 'seq',
8 => 'fs',
9 => 'radio_stats',
10 => 'mqtt_stats'
];
$config['data_descriptions']['radio_stats'] = [
0 => 'Version',
1 => 'Reboot Count',
2 => 'I2C Bus Error Count',
3 => 'Radio Status',
4 => 'Radio Network ID'
];
//---------------------------------------------------------------------------------------------------------
$association_radio_stats['Text'] = [
0 => ' Radio Off',
1 => ' Radio On',
3 => ' Radio Active'
];
$association_radio_stats['Color'] =[
0 => $red,
1 => $blue,
3 => $green
];
$association_error_codes['Text'] = [
0 => 'Sensors OK',
1 => 'lightning failed',
2 => 'lightning noise',
4 => 'lightning disturber',
8 => 'pressure failed',
16 => 'temperature failed',
32 => 'rh failed',
64 => 'wind failed',
128 => 'precip failed',
256 => 'light/uv failed'
];
$association_error_codes['Color'] =[
0 => $green,
1 => $red,
2 => $orange,
4 => $yellow,
8 => $red,
16 => $red,
32 => $red,
64 => $red,
128 => $red,
256 => $red
];
$association_percerception['Text'] = [
0 => 'none',
1 => 'rain',
2 => 'hail'
];
$association_percerception['Color'] = [
0 => $green ,
1 => $blue,
2 => $red
];
$association_uv_index['Text'] = [
0 => 'Low',
3 => 'Medium',
6 => 'High' ,
8 => 'Very High',
11 => 'Extremly High'
];
$association_uv_index['Color'] = [
0 => $green ,
3 => $yellow ,
6 => $orange,
8 => $red,
11 => $purple
];
$association_battery_charge['Text'] = [
0 => ' + Hybernate',
1 => ' + Hybernate',
2 => ' + Wind 5 Min NO Lightning+Rain',
3 => ' + Wind 5 Min NO Lightning+Rain',
4 => ' + Wind sampling interval set to one minute' ,
5 => ' + Wind sampling interval set to one minute',
6 => ' + Wind every 6 sec.',
7 => ' + Wind every 6 sec.',
8 => ' + All sensors enabled and operating at full performance',
9 => ' + All sensors enabled and operating at full performance'
];
$association_battery_charge['Color'] = [
0 => $purple,
1 => $purple,
2 => $red,
3 => $red,
4 => $orange,
5 => $orange,
6 => $yellow,
7 => $yellow,
8 => $green,
9 => $green
];
$association_battery_charge['values'] = [
0 => '2,33',
1 => '2,33',
2 => '2,355',
3 => '2,355',
4 => '2,375',
5 => '2,39',
6 => '2,41',
7 => '2,415',
8 => '2,455',
9 => '2,455'
];
$association_battery_discharge['Text'] = [
0 => ' - Hybernate',
1 => ' - Hybernate',
2 => ' - Wind 5 Min NO Lightning+Rain',
3 => ' - Wind 5 Min NO Lightning+Rain',
4 => ' - Wind sampling interval set to one minute' ,
5 => ' - Wind sampling interval set to one minute',
6 => ' - Wind every 6 sec.',
7 => ' - Wind every 6 sec.',
8 => ' - All sensors enabled and operating at full performance',
9 => ' - All sensors enabled and operating at full performance'
];
$association_battery_discharge['Color'] = [
0 => $purple,
1 => $purple,
2 => $red,
3 => $red,
4 => $orange,
5 => $orange,
6 => $yellow,
7 => $yellow,
8 => $green,
9 => $green
];
$association_battery_discharge['values'] = [
0 => '2,33',
1 => '2,33',
2 => '2,355',
3 => '2,355',
4 => '2,375',
5 => '2,39',
6 => '2,41',
7 => '2,415',
8 => '2,455',
9 => '2,455'
];
$association_battery_status['Text'] = [
0 => ' Discharge',
1 => ' Charge'
];
$association_battery_status['Color'] = [
0 => $red,
1 => $green
];
$association_wind_direction['Text'] = [
0 => ' N',
1 => ' NNO',
2 => ' NO',
3 => ' ONO',
4 => ' O' ,
5 => ' OSO',
6 => ' SO',
7 => ' SSO',
8 => ' S',
9 => ' SSW',
10=> ' SW',
11=> ' WSW',
12=> ' W',
13=> ' WNW',
14=> ' NW',
15=> ' NNW'
];
$association_wind_direction['Color'] = [
0 => $blue,
1 => $green,
2 => $green,
3 => $green,
4 => $green,
5 => $red,
6 => $red,
7 => $red,
8 => $red,
9 => $yellow,
10=> $yellow,
11=> $yellow,
12=> $yellow,
13=> $blue,
14=> $blue,
15=> $blue
];
$association_wind_direction ['values'] = [
0 => 0,
1 => 22.5,
2 => 45,
3 => 67.5,
4 => 90,
5 => 112.5,
6 => 135,
7 => 157.5,
8 => 180,
9 => 202.5,
10=> 225,
11=> 247.5,
12=> 270,
13=> 292.5,
14=> 315,
15=> 337.5
];
//----------------------------------------------------------------------------------------------
$variable_types = [
'Boolean' => 0,
'Integer' => 1,
'Float' => 2,
'String' => 3
];
//----------------------------------------------------------------------------------------------
$config['profile_names']['Radio_Status'] = [
$config['data_descriptions']['hub_status'][9],
$config['data_descriptions']['radio_stats'][3],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => '',
'association' => $association_radio_stats,
'minimalwert' => 0,
'maximalwert' => 3,
'schrittweite' => 1
];
$config['profile_names']['seconds'] = [
$config['data_descriptions']['obs_st'][5],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' Seconds',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 999999999,
'schrittweite' => 1
];
$config['profile_names']['~UnixTimestamp'] = [
$config['data_descriptions']['obs_st'][0],
$config['data_descriptions']['device_status'][3],
$config['data_descriptions']['hub_status'][5],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' Seconds',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 999999999,
'schrittweite' => 1
];
$config['profile_names']['elapsed_time'] = [
$config['data_descriptions']['device_status'][4],
$config['data_descriptions']['hub_status'][3],
'type' => $variable_types['String'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => '',
'association' => NULL,
'minimalwert' => NULL,
'maximalwert' => NULL,
'schrittweite' => NULL
];
$config['profile_names']['meter_per_second'] = [
'type' => $variable_types['Float'],
'nachkommastellen' => 2,
'prefix' => '',
'suffix' => ' m/s',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 100,
'schrittweite' => 1
];
$config['profile_names']['km_pro_stunde'] = [
$config['data_descriptions']['obs_st'][1],
$config['data_descriptions']['obs_st'][2],
$config['data_descriptions']['obs_st'][3],
'type' => $variable_types['Float'],
'nachkommastellen' => 2,
'prefix' => '',
'suffix' => ' km/h',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 160,
'schrittweite' => 0.01
];
$config['profile_names']['degrees'] = [
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' Degree',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 360,
'schrittweite' => 1
];
$config['profile_names']['wind_direction'] = [
$config['data_descriptions']['obs_st'][4],
'type' => $variable_types['Float'],
'nachkommastellen' => 1,
'prefix' => '',
'suffix' => ' °',
'association' => $association_wind_direction,
'minimalwert' => 0,
'maximalwert' => 360,
'schrittweite' => 1
];
$config['profile_names']['milli_bar'] = [
$config['data_descriptions']['obs_st'][6],
'type' => $variable_types['Float'],
'nachkommastellen' => 2,
'prefix' => '',
'suffix' => ' hPa',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 9999,
'schrittweite' => 1
];
$config['profile_names']['celcius'] = [
$config['data_descriptions']['obs_st'][7],
'type' => $variable_types['Float'],
'nachkommastellen' => 2,
'prefix' => '',
'suffix' => ' °C',
'association' => NULL,
'minimalwert' => -40,
'maximalwert' => 45,
'schrittweite' => '0,01'
];
$config['profile_names']['percent'] = [
$config['data_descriptions']['obs_st'][8],
'type' => $variable_types['Float'],
'nachkommastellen' => 2,
'prefix' => '',
'suffix' => ' %',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 100,
'schrittweite' => '0,01'
];
$config['profile_names']['lux'] = [
$config['data_descriptions']['obs_st'][9],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' Lx',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 120000,
'schrittweite' => 1
];
$config['profile_names']['index'] = [
$config['data_descriptions']['obs_st'][10],
'type' => $variable_types['Float'],
'nachkommastellen' => 2,
'prefix' => '',
'suffix' => ' UVI',
'association' => $association_uv_index,
'minimalwert' => 0,
'maximalwert' => 15,
'schrittweite' => '0,01'
];
$config['profile_names']['energy'] = [
$config['data_descriptions']['obs_st'][11],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' W/m^2',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 1000,
'schrittweite' => 1
];
$config['profile_names']['mm'] = [
$config['data_descriptions']['obs_st'][12],
'type' => $variable_types['Float'],
'nachkommastellen' => 6,
'prefix' => '',
'suffix' => ' mm',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 100,
'schrittweite' => '0,000001'
];
$config['profile_names']['perception_type'] = [
$config['data_descriptions']['obs_st'][13],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => '',
'association' => $association_percerception,
'minimalwert' => 0,
'maximalwert' => 2,
'schrittweite' => 1
];
$config['profile_names']['km'] = [
$config['data_descriptions']['obs_st'][14],
'type' => $variable_types['Float'],
'nachkommastellen' => 2,
'prefix' => '',
'suffix' => ' km',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 100,
'schrittweite' => '0,01'
];
$config['profile_names']['count'] = [
$config['data_descriptions']['obs_st'][15],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' Lightning Strikes',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 100,
'schrittweite' => 1
];
$config['profile_names']['counter'] = [
$config['data_descriptions']['radio_stats'][1],
$config['data_descriptions']['radio_stats'][2],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => '',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 1000,
'schrittweite' => 1
];
$config['profile_names']['volt'] = [
$config['data_descriptions']['obs_st'][16],
$config['data_descriptions']['device_status'][5],
'type' => $variable_types['Float'],
'nachkommastellen' => 3,
'prefix' => '',
'suffix' => ' Volt',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 4,
'schrittweite' => '0,001'
];
$config['profile_names']['slope'] = [
'type' => $variable_types['Float'],
'nachkommastellen' => 9,
'prefix' => '',
'suffix' => ' mx+b',
'association' => NULL,
'minimalwert' => -10,
'maximalwert' => 10,
'schrittweite' => '0,00000001'
];
$config['profile_names']['system_condition'] = [
'type' => $variable_types['Float'],
'nachkommastellen' => 3,
'prefix' => '',
'suffix' => ' ',
'association' => $association_battery_discharge,
'minimalwert' => 0,
'maximalwert' => 2.9,
'schrittweite' => '0,001'
];
$config['profile_names']['battery_status'] = [
'type' => $variable_types['Boolean'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' Battery',
'association' => $association_battery_status,
'minimalwert' => 0,
'maximalwert' => 1,
'schrittweite' => '1'
];
$config['profile_names']['minutes'] = [
$config['data_descriptions']['obs_st'][17],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' Minutes',
'association' => NULL,
'minimalwert' => 0,
'maximalwert' => 60,
'schrittweite' => 1
];
$config['profile_names']['rssi'] = [
$config['data_descriptions']['device_status'][7],
$config['data_descriptions']['device_status'][8],
$config['data_descriptions']['hub_status'][4],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => ' db',
'association' => NULL,
'minimalwert' => -100,
'maximalwert' => 0,
'schrittweite' => 1
];
$config['profile_names']['status'] = [
$config['data_descriptions']['device_status'][9],
'type' => $variable_types['Integer'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => '',
'association' => $association_error_codes,
'minimalwert' => 0,
'maximalwert' => 256,
'schrittweite' => 1
];
$config['profile_names']['text'] = [
$config['data_descriptions']['device_status'][0],
$config['data_descriptions']['device_status'][1],
$config['data_descriptions']['device_status'][2],
$config['data_descriptions']['device_status'][6],
$config['data_descriptions']['device_status'][10],
$config['data_descriptions']['hub_status'][0],
$config['data_descriptions']['hub_status'][1],
$config['data_descriptions']['hub_status'][2],
$config['data_descriptions']['hub_status'][6],
$config['data_descriptions']['hub_status'][7],
$config['data_descriptions']['radio_stats'][0],
$config['data_descriptions']['radio_stats'][4],
'type' => $variable_types['String'],
'nachkommastellen' => 0,
'prefix' => '',
'suffix' => '',
'association' => NULL,
'minimalwert' => NULL,
'maximalwert' => NULL,
'schrittweite' => NULL
];
//----------------------------------------------------------------------------------------------
$config['variable_types'] = $variable_types;
$config['charge'] = $association_battery_charge;
$config['discharge'] = $association_battery_discharge;
return $config;
}
function check_category($name,$parent)
{
Global $debug;
$CatID = @IPS_GetCategoryIDByName($name,$parent );
if($CatID === false)
{
$CatID = IPS_CreateCategory();
IPS_SetName($CatID, $name);
IPS_SetParent($CatID,$parent );
}
else
{
}
return $CatID;
}
function calculate_average($arr)
{
Global $debug;
return array_sum($arr) / count($arr);
}
function manage_variable($profil_update,$check,$value,$parent,$name,$type,$profil,$aggregation_type,$logging,$ID,$time_stamp)
{
Global $var_liste;
$archive_id = IPS_GetInstanceListByModuleID ('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
if(array_key_exists(IPS_GetName($parent),$var_liste))
{
if(in_array($name,$var_liste[IPS_GetName($parent)]))
{
$var_id = @IPS_GetVariableIDByName ($name, $parent);
if($var_id === false)
{
$var_id = IPS_CreateVariable ($type);
IPS_SetVariableCustomProfile ($var_id, $profil );
IPS_SetName($var_id, $name);
IPS_SetParent($var_id, $parent);
if($logging or ($type != 3))
{
AC_SetLoggingStatus ($archive_id, $var_id, $logging);
AC_SetAggregationType ($archive_id, $var_id, $aggregation_type);
}
else
{
AC_SetLoggingStatus ($archive_id, $var_id, $logging);
}
IPS_ApplyChanges($archive_id);
}
else
{
if(IPS_GetVariable($var_id)['VariableCustomProfile'] != $profil)
{
if(IPS_GetVariable($var_id)['VariableType'] == $type)
{
IPS_LogMessage("Tempest", " Variablen Profil auf aktuellen Stand gebracht ".IPS_GetName($var_id)." Profil : ".$profil." Var ID".$var_id." CP ".IPS_GetVariable($var_id)['VariableCustomProfile']);
IPS_SetVariableCustomProfile ($var_id, $profil );
}
else
{
IPS_LogMessage("Tempest", " Variablen gelöscht ".IPS_GetName($var_id)." Profil : ".$profil." Var ID".$var_id." CP ".IPS_GetVariable($var_id)['VariableCustomProfile']);
IPS_DeleteVariable ($var_id);
check_variable($profil_update,$check,$value,$parent,$name,$type,$profil,$aggregation_type,$logging,$ID,$time_stamp);
return false;
}
}
else
{
}
}
return $var_id;
}
else
{
return false;
}
}
else
{
return false;
}
}
function check_variable($profil_update,$check,$value,$parent,$name,$type,$profil,$aggregation_type,$logging,$ID,$time_stamp)
{
Global $max_age,$NO_Check_Variable_Names,$debug;
$data_fields_elapsed_time = array('uptime');
$position = strpos($profil,"~");
if($position === false)
{
}
else
{
$profile_prefix = substr($profil,0,$position);
$profil = str_replace ($profile_prefix,"",$profil);
}
$position = strpos($profil,'km_pro_stunde');
if($position)
{
$value = $value*3.6;
}
else
{
}
$archive_id = IPS_GetInstanceListByModuleID ('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
$var_id = manage_variable($profil_update,$check,$value,$parent,$name,$type,$profil,$aggregation_type,$logging,$ID,$time_stamp);
if($var_id)
{
if(!in_array($name, $NO_Check_Variable_Names))
{
if(AC_GetLoggingStatus($archive_id,$var_id))
{
if( $check === 'OLD_TIME_STAMP')
{
$werte = AC_GetLoggedValues ($archive_id,$var_id ,0,0,1);
if(is_array($werte))
{
$datensatz[] = ['TimeStamp' => $time_stamp, 'Value' => $value];
AC_AddLoggedValues ($archive_id, $var_id, $datensatz);
AC_ReAggregateVariable($archive_id, $var_id);
IPS_LogMessage('TempestA1','Inserted Value with old timestamp and started to Reaggregate Variable '.IPS_GetName($var_id));
}
else
{
SetValue($var_id,$value);
if($debug) IPS_LogMessage('TempestA2',' Keine Historischen Werte für die Variable vorhanden '.$var_id);
}
}
elseif($check === 'NEW_VALUE')
{
if($debug) IPS_LogMessage('TempestB1',' Timstamp OK '.IPS_GetName($var_id));
SetValue($var_id,$value);
}
elseif($check === 'NO CHECKS')
{
if($debug) IPS_LogMessage('TempestB2',' No Checks '.IPS_GetName($var_id));
SetValue($var_id,$value);
}
elseif($check === 'NO HISTORY')
{
if($debug) IPS_LogMessage('TempestB3',' No History '.IPS_GetName($var_id));
SetValue($var_id,$value);
}
elseif($check === 'NOT LOGGED')
{
if($debug) IPS_LogMessage('TempestB4',' Not Logged '.IPS_GetName($var_id));
SetValue($var_id,$value);
}
elseif($check === 'ERROR')
{
if($debug) IPS_LogMessage('TempestB5',' Error '.IPS_GetName($var_id));
SetValue($var_id,$value);
}
else
{
if($debug) IPS_LogMessage('TempestB6',' Should not have happened '.IPS_GetName($var_id));
SetValue($var_id,$value);
}
}
else
{
if($debug) IPS_LogMessage('TempestB7',' Variable wird nicht geloggt '.$var_id);
SetValue($var_id,$value);
}
}
else
{
if($debug) IPS_LogMessage('TempestB8',' NO Checks on '.IPS_GetName($var_id ));
SetValue($var_id,$value);
}
}
else
{
if($debug) IPS_LogMessage('TempestB9',' Variable nicht vorhanden '.$var_id);
}
}
function check_profile($profil_update,$name,$type,$digits,$prefix,$suffix,$associations,$Minimalwert,$Maximalwert,$Schrittweite,$replace_association)
{
Global $debug;
if(strpos($name,"~" ) === false)
{
$profiles = IPS_GetVariableProfileListByType ($type);
if (IPS_VariableProfileExists ($name))
{
if($profil_update)
{
IPS_DeleteVariableProfile($name);
check_profile($profil_update,$name,$type,$digits,$prefix,$suffix,$associations,$Minimalwert,$Maximalwert,$Schrittweite,false);
}
elseif(IPS_GetVariableProfile ($name)['ProfileType'] == $type)
{
if($replace_association)
{
if(is_array($associations))
{
foreach($associations['Text'] as $key => $var)
{
if(array_key_exists('values',$associations))
{
$value = $associations['values'][$key];
}
else
{
$value = $key;
}
IPS_SetVariableProfileAssociation ($name, $value, $var,'',intval($associations['Color'][$key] ));
}
}
else
{
}
}
else
{
}
}
else
{
}
}
else
{
IPS_CreateVariableProfile ($name,$type);
IPS_SetVariableProfileText ($name,$prefix,$suffix);
if($type != 3)
{
IPS_SetVariableProfileDigits ($name,$digits);
IPS_SetVariableProfileValues ($name, $Minimalwert, $Maximalwert, $Schrittweite);
if(is_array($associations))
{
foreach($associations['Text'] as $key => $var)
{
if(array_key_exists('values',$associations))
{
$value = $associations['values'][$key];
}
else
{
$value = $key;
}
IPS_SetVariableProfileAssociation ($name, $value, $var,'',intval($associations['Color'][$key] ));
}
}
else
{
}
}
else
{
}
}
}
else
{
}
}
function system_state($profil_update,$check,$cat_id,$config,$trigger_value_slope,$variable_types,$prefix_profil_name,$nr_of_datapoints_for_regression,$experimental)
{
Global $debug;
if($experimental)
{
$archive_id = IPS_GetInstanceListByModuleID ('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
$var_id = IPS_GetObjectIDByName ('Battery',$cat_id);
$werte = AC_GetLoggedValues ($archive_id,$var_id , (time()-600000), time(),$nr_of_datapoints_for_regression);
$status_id = @IPS_GetObjectIDByName ('Battery Status',$cat_id);
if(!$status_id) $status_id = manage_variable($profil_update,$check,0,$cat_id,'Battery Status',$variable_types['Boolean'],$prefix_profil_name.'battery_status','0',true,'30',time());
$slope_id = @IPS_GetObjectIDByName ('Slope',$cat_id);
if(!$slope_id) $slope_id = manage_variable($profil_update,$check,0,$cat_id,'Slope',$variable_types['Float'],$prefix_profil_name.'slope','0',true,'31',time());
$slope_werte = AC_GetLoggedValues ($archive_id,$slope_id , (time()-6000000), time(),2);
if(is_array( $werte) and is_array($slope_werte))
{
if(array_key_exists(($nr_of_datapoints_for_regression-1),$werte) and array_key_exists(1,$slope_werte))
{
$var_name = 'System Condition';
$profil_name = 'system_condition';
$val = $config['profile_names'][ $profil_name];
$linearRegression = new \MachineLearning\Regression\LeastSquares();
foreach($werte as $key => $value)
{
$x[] = $value['TimeStamp'];
$y[] = $value['Value'];
}
$linearRegression->train($x, $y);
$slope = $linearRegression->getSlope();
$average = calculate_average($y);
$median = calculate_median($y);
if(GetValueBoolean($status_id)) // Batterie lädt
{
if(($slope >= $trigger_value_slope) and ($slope >= $slope_werte[0]['Value']) ) // Neue Steigung ist höher als vorheriger Wert : Batterie lädt weiter
{
if($debug) IPS_LogMessage('Tempest','+Charge Slope :'.$slope.' alter Slope Wert : '. $slope_werte[0]['Value']);
$val['association'] = $config['charge'];
$state = true;
}
else
{
if($debug) IPS_LogMessage('Tempest','+Discharge Slope :'.$slope.' alter Slope Wert : '. $slope_werte[0]['Value']);
$val['association'] = $config['discharge'];
$state = false; // Batterie lädt nicht mehr da neue Steigung geringer oder gleich ist mit dem vorherigen Wert
}
}
else // Batterie entlädt
{
if(($slope >= $trigger_value_slope) and ($slope >= $slope_werte[0]['Value']) ) // Batterie lädt
{
if($debug) IPS_LogMessage('Tempest','-Charge Slope :'.$slope.' alter Slope Wert : '. $slope_werte[0]['Value'].' Trigger Wert Slope : '.$trigger_value_slope);
$val['association'] = $config['charge'];
$state = true;
}
else
{
if($debug) IPS_LogMessage('Tempest','-discharge Slope :'.$slope.' alter Slope Wert : '. $slope_werte[0]['Value'].' Trigger Wert Slope : '.$trigger_value_slope);
$val['association'] = $config['discharge'];
$state = false;
}
}
$time_stamp = time();
check_variable( $profil_update,$check,$average,$cat_id,'Average',$variable_types['Float'],$prefix_profil_name.'volt','0',true,'22',$time_stamp);
check_variable( $profil_update,$check,$median,$cat_id,'Median',$variable_types['Float'],$prefix_profil_name.'volt','0',true,'23',$time_stamp);
check_variable($profil_update,$check,$slope,$cat_id,'Slope',$variable_types['Float'],$prefix_profil_name.'slope','0',true,'20',$time_stamp);
check_variable($profil_update,$check,count($x),$cat_id,'Counter Slope Datasets',$variable_types['Integer'],$prefix_profil_name.'counter','0',true,'21',$time_stamp);
check_variable($profil_update,$check,GetValueFloat($var_id),$cat_id,$var_name,$variable_types['Float'],$prefix_profil_name.$profil_name,'0',true,'24',$time_stamp);
check_profile(false,$prefix_profil_name.$profil_name,$val['type'],$val['nachkommastellen'],$val['prefix'],$val['suffix'],$val['association'],$val['minimalwert'],$val['maximalwert'],$val['schrittweite'],true,$time_stamp);
check_variable($profil_update,$check,$state,$cat_id,'Battery Status',$variable_types['Boolean'],$prefix_profil_name.'battery_status','0',true,'25',$time_stamp);
}
else
{
}
}
else
{
}
}
else
{
}
}
function check_time_stamp_for_message_type($cat_id,$name,$time_stamp) // Überprüft den Zeitstempel für alle Nachrichten des jeweiligen Typs
{
Global $max_age,$NO_Check_Variable_Names,$debug,$profil_update,$prefix_profil_name,$variable_types;
if($debug) IPS_LogMessage('TempestD8',$name);
$archive_id = IPS_GetInstanceListByModuleID ('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
$var_id = @IPS_GetVariableIDByName ($name,$cat_id);
if($var_id)
{
if(AC_GetLoggingStatus($archive_id,$var_id))
{
if($time_stamp < time())
{
$werte = AC_GetLoggedValues ($archive_id,$var_id ,($time_stamp-100),0,10000);
}
elseif($time_stamp > time())
{
if($debug) IPS_LogMessage('TempestD0',' Timestamp from Tempest is in the Future : Abort Program '.IPS_GetName($cat_id).' '.IPS_GetName($var_id).' '.$time_stamp);
return 'INVALID';
}
else
{
if($debug) IPS_LogMessage('TempestD10','Timestamp equals time() '.IPS_GetName($cat_id).' '.IPS_GetName($var_id));
return 'NEW_VALUE';
}
if(is_array($werte) and array_key_exists('0',$werte))
{
$delta = $time_stamp - $werte[0]['Value'];
check_variable($profil_update,'NO CHECKS',$delta,$cat_id,'time_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'28',$time_stamp);
$delta = time()-$time_stamp;
check_variable($profil_update,'NO CHECKS',$delta,$cat_id,'stamp_delta',$variable_types['Integer'],$prefix_profil_name.'seconds','0',true,'29',$time_stamp);
foreach($werte as $key => $value)
{
if($value['Value'] == $time_stamp)
{
IPS_LogMessage('TempestD1','Double Entry for all Messages of '.IPS_GetName($cat_id).' Script terminated ');
return 'INVALID';
}
else
{
}
}
if(( $werte[0]['Value']) > $time_stamp) // Der letzte Tempest Zeitstempel ist jünger als der aktuelle ergo es handelt sich um alte Nachrichten
{
if($debug) IPS_LogMessage('TempestD2',' Insert all Messages in timeline of '.IPS_GetName($cat_id).' '.($time_stamp - $werte[0]['Value']).' Var : '.IPS_GetName($var_id).' AZST '.$werte[0]['Value'].' NZST '.$time_stamp);
return 'OLD_TIME_STAMP';
}
else
{
if($debug) IPS_LogMessage('TempestD3',' New Value for all Messages of '.IPS_GetName($cat_id).' Delta of old Time Stamp minus last '.($werte[0]['Value'] - $time_stamp).' Var : '.IPS_GetName($var_id).' AZST '.$werte[0]['Value'].' NZST '.$time_stamp. ' Time DELTA '.(time()-$time_stamp));
return 'NEW_VALUE';
}
}
else
{
if($debug) IPS_LogMessage('TempestD4','No Timestamps stored yet for messages from '.IPS_GetName($cat_id).' '.$var_id);
return 'NO HISTORY';
}
}
else
{
if($debug) IPS_LogMessage('TempestD5','Variable wird nicht geloggt '.IPS_GetName($cat_id).' '.$var_id);
return 'NOT LOGGED';
}
}
else
{
if($debug) IPS_LogMessage('TempestD7',' No Variable for '.IPS_GetName($cat_id).' '.$name);
return 'NO CHECKS';
}
if($debug) IPS_LogMessage('TempestD8',' ERROR '.IPS_GetName($cat_id).' '.$name );
return 'ERROR';
}
function time_elapsed($secs)
{
Global $debug;
$bit = array(
'y' => $secs / 31556926 % 12,
'w' => $secs / 604800 % 52,
'd' => $secs / 86400 % 7,
'h' => $secs / 3600 % 24,
'm' => $secs / 60 % 60,
's' => $secs % 60
);
foreach($bit as $k => $v)
if($v > 0)$ret[] = $v . $k;
return join(' ', $ret);
}
function calculate_median($arr)
{
Global $debug;
$count = count($arr); //total numbers in array
$middleval = floor(($count-1)/2); // find the middle value, or the lowest middle value
if($count % 2) { // odd number, middle is the median
$median = $arr[$middleval];
} else { // even number, calculate avg of 2 medians
$low = $arr[$middleval];
$high = $arr[$middleval+1];
$median = (($low+$high)/2);
}
return $median;
}
/**
* Class LinearRegression
*
* Linear model that uses least squares method to approximate solution.
*
* @package MachineLearning
*/
class LeastSquares
{
/**
* @var float[]
*/
private $xCoords = [];
/**
* @var float[]
*/
private $yCoords = [];
/**
* Holds the y differences from the calculated regression line
*
* @var float[]
*/
private $yDifferences = [];
/**
* Holds the cumulative sum of yDifferences
*
* @var float[]
*/
private $cumulativeSum = [];
/**
* @var float
*/
private $slope;
/**
* @var float
*/
private $intercept;
/**
* @var float
*/
private $rSquared;
/**
* @var int
*/
private $coordinateCount = 0;
/**
* regression line points
*
* @var Point[]
*/
private $xy = [];
/**
* LinearRegression constructor.
*/
function __construct()
{
}
/**
* Append the data and compute the linear regression
* multiple calls to train in a row keep adding data and calculate the new regression
*
*
* @param array $xCoords the targets (e.g. degree days)
* @param array $yCoords the samples (e.g. energy used for heating)
*/
public function train(array $xCoords, array $yCoords)
{
$this->resetCalculatedValues();
$this->appendData($xCoords, $yCoords);
$this->compute();
}
/**
* @param array $xCoords
* @param array $yCoords
*/
private function appendData(array $xCoords, array $yCoords)
{
$this->xCoords = array_merge($this->xCoords, $xCoords);
$this->yCoords = array_merge($this->yCoords, $yCoords);
$this->countCoordinates();
}
/**
* clear the calculated values
*/
private function resetCalculatedValues()
{
$this->slope = null;
$this->intercept = null;
$this->rSquared = null;
$this->yDifferences = [];
$this->cumulativeSum = [];
$this->xy = [];
}
/**
* clear the series data
*/
private function clearData()
{
$this->xCoords = [];
$this->yCoords = [];
$this->coordinateCount = 0;
}
/**
* clear all data so new calls to train() start a fresh
*/
public function reset()
{
$this->resetCalculatedValues();
$this->clearData();
}
/**
* The amount of increase in y (vertical) for an increase of 1 on the x axis (horizontal)
*
* @return float
*/
public function getSlope(): float
{
return $this->returnOrThrowIfNull($this->slope);
}
/**
* The value at which the regression line crosses the y axis (vertical)
*
* @return float
*/
public function getIntercept(): float
{
return $this->returnOrThrowIfNull($this->intercept);
}
/**
* The "coefficient of determination" or "r-squared value"
* always a number between 0 and 1
* 1, all of the data points fall perfectly on the regression line. The predictor x accounts for all of the
* variation in y
* 0, the estimated regression line is perfectly horizontal. The predictor x accounts for none of the variation in
* y
*
* @return float
*/
public function getRSquared()
{
return $this->returnOrThrowIfNull($this->rSquared);
}
/**
* @return int
* @throws SeriesCountMismatch
* @throws SeriesHasZeroElements
*/
private function countCoordinates(): int
{
// calculate number points
$this->coordinateCount = count($this->xCoords);
$yCount = count($this->yCoords);
// ensure both arrays of points are the same size
if ($this->coordinateCount != $yCount) {
throw new SeriesCountMismatch("Number of elements in arrays do not match {$this->xCoords}:{$yCount}");
}
if ($this->coordinateCount === 0) {
throw new SeriesHasZeroElements('Series has zero elements');
}
return $this->coordinateCount;
}
/**
* @param float $value
*
* @return float
* @throws ParameterNotComputedYet
*/
private function returnOrThrowIfNull(float $value)
{
if (null === $value) {
throw new ParameterNotComputedYet('Parameter not compute yet');
}
return $value;
}
/**
* Linear model that uses least squares method to approximate solution.
*/
private function compute()
{
// calculate sums
$x_sum = array_sum($this->xCoords);
$y_sum = array_sum($this->yCoords);
$xx_sum = 0;
$xy_sum = 0;
$yy_sum = 0;
for ($i = 0; $i < $this->coordinateCount; $i++) {
$xy_sum += ($this->xCoords[$i] * $this->yCoords[$i]);
$xx_sum += ($this->xCoords[$i] * $this->xCoords[$i]);
$yy_sum += ($this->yCoords[$i] * $this->yCoords[$i]);
}
// calculate slope
$this->slope = (($this->coordinateCount * $xy_sum) - ($x_sum * $y_sum)) / (($this->coordinateCount * $xx_sum) - ($x_sum * $x_sum));
// calculate intercept
$this->intercept = ($y_sum - ($this->slope * $x_sum)) / $this->coordinateCount;
// Calculate R squared
// Math.pow((n*sum_xy - sum_x*sum_y)/Math.sqrt((n*sum_xx-sum_x*sum_x)*(n*sum_yy-sum_y*sum_y)),2);
$this->rSquared = POW(($this->coordinateCount * $xy_sum - $x_sum * $y_sum) / sqrt(($this->coordinateCount * $xx_sum - $x_sum * $x_sum) * ($this->coordinateCount * $yy_sum - $y_sum * $y_sum)),
2);
}
/**
* predict for a given y value (sample) the x value (target)
*
* @param float $y
*
* @return float
*/
public function predictX(float $y): float
{
return ($y - $this->getIntercept()) / $this->getSlope();
}
/**
* predict for a given x value (target) the y value (sample)
*
* @param float $x
*
* @return float
*/
public function predictY(float $x): float
{
return $this->getIntercept() + ($x * $this->getSlope());
}
/**
* Get the differences of the actual data from the regression line
* This is the differences in y values
*
* @return \float[]
*/
public function getDifferencesFromRegressionLine()
{
if (0 === count($this->yDifferences)) {
for ($i = 0; $i < $this->coordinateCount; $i++) {
$this->yDifferences[] = $this->yCoords[$i] - $this->predictY($this->xCoords[$i]);
}
}
return $this->yDifferences;
}
/**
* Get the cumulative some of the differences from the regression line
*
* @return float[]
*/
public function getCumulativeSumOfDifferencesFromRegressionLine()
{
if (0 === count($this->cumulativeSum)) {
$differences = $this->getDifferencesFromRegressionLine();
$this->cumulativeSum = [$differences[0]];
for ($i = 1; $i < $this->coordinateCount; $i++) {
$this->cumulativeSum[$i] = $differences[$i] + $this->cumulativeSum[$i - 1];
}
}
return $this->cumulativeSum;
}
/**
* Mean of Y values
*
* @return float|int
*/
public function getMeanY()
{
return array_sum($this->yCoords) / $this->coordinateCount;
}
/**
* return an array of Points corresponding to the regression line of the current data
*
* @return Point[]
*/
public function getRegressionLinePoints()
{
if (0 == count($this->xy)) {
$minX = min($this->xCoords);
$maxX = max($this->xCoords);
$xStepSize = (($maxX - $minX) / ($this->coordinateCount - 1));
$this->xy = [];
for ($i = 0; $i < $this->coordinateCount; $i++) {
$x = $minX + ($i * $xStepSize);
$y = $this->predictY($x);
$this->xy[] = new Point($x, $y); // add point
}
}
return $this->xy;
}
}