Hi there,
there was no possibity to access my descaling system’s status information. Even the manufacturer didn’t provide any usful api or whatsoever.
So this is my attempt on getting all the necessary information from my system (made by the german manufacturer “Judo”). This has only been tested with my “Softwell K” system and working fine for the last couple of weeks.
Please configure your knm token and serial number as mentioned in the code.
The code is using the same interface as the official judo app or webpage (reverse engineered). So it’s very unlikly that it could break (but not impossible).
Cu
PHP-Script for your server
##
## judo // JSON state fetcher
## v0.3a
## written by theLex, April 2020
##
## This has been developed for the Judo - SOFTwell K descaling system - and has only been tested with this one!
##
## Feel free to use this code and change it - but keep my credentials in the code!
## This code as been produced by reengineering the code behind www.myjudo.eu. This might stop working if the page is ever been changed.
## Use at your own risk. The script might run a while (has to request data first)
## Have fun :-)
##
## Usage:
## 1. http://192.xxx.xxx.xx/judo.php?load=day&do=preload
## 2. Wait 10 seconds
## 3. http://192.xxx.xxx.xx/judo.php?load=day
$version = "0.3a";
# Setup
$knm_token = ""; # your knm-token: you'll find it by logging into http://www.myjudo.eu. After that you'll find it in the browsers url
$serialnumber = ""; # there are different ways - the best is to browse your Softwell K's IP. There you'll find it on the front page
$day_to_load = date("jmY"); # you can change this if you want to retrieve the information for a different day
$week_to_load = date("WY"); # you can change this if you want to retrieve the information for a different week
$month_to_load = date("nY"); # you can change this if you want to retrieve the information for a different month
$year_to_load = date("Y"); # you can change this if you want to retrieve the information for a different year
# let's begin
# Building links
$url_getchartdata_day = "https://www.myjudo.eu/interface/?token=".$knm_token."&group=register&command=get_chart_data&serialnumber=".$serialnumber."&date=".$day_to_load."¶meter=day";
$url_getchartdata_week = "https://www.myjudo.eu/interface/?token=".$knm_token."&group=register&command=get_chart_data&serialnumber=".$serialnumber."&date=".$week_to_load."¶meter=week";
$url_getchartdata_year = "https://www.myjudo.eu/interface/?token=".$knm_token."&group=register&command=get_chart_data&serialnumber=".$serialnumber."&date=".$year_to_load."¶meter=year";
$url_getchartdata_month = "https://www.myjudo.eu/interface/?token=".$knm_token."&group=register&command=get_chart_data&serialnumber=".$serialnumber."&date=".$month_to_load."¶meter=month";
$url_getdevicedata = "https://www.myjudo.eu/interface/?token=".$knm_token."&group=register&command=get%20device%20data";
$loadNow = true;
$storeJSON = true;
$result = ["now" => date("d.m.Y H:i:s"), "fetcher_version" => $version, "msg" => "", "state" => "failed", "cached" => false, "urls" => []];
# which consumption informations do we want load?
$load = (isset($_GET['load']) ? strtolower(trim($_GET['load'])) : "day,month,week,year,device");
$do = (isset($_GET['do']) ? strtolower(trim($_GET['do'])) : "load");
# prepare result array
$result['msg'] = "okay";
$result['do'] = $do;
$result['state'] = "okay";
$result['load'] = $load;
$result['device'] = array();
if ($loadNow || $do == "preload") {
$chartdata_week_string = "";
$chartdata_day_string = "";
$chartdata_month_string = "";
$chartdata_year_string = "";
# Chart data // year
if (strpos($load, "year") !== false) {
$lastState = "";
$result['runner']['year'] = 0;
while($lastState != "ok") {
$chartdata_year_string = file_get_contents($url_getchartdata_year);
$chartdata_year = json_decode($chartdata_year_string);
$lastState = $chartdata_year->status;
$result['runner']['year']++;
if ($do == "preload") break;
sleep(2);
if ($result['runner']['year'] > 20) {
break;
}
}
$result['urls']['chart_year'] = $url_getchartdata_year;
if ($storeJSON) file_put_contents("chartdata_year.json", $chartdata_year_string);
}
# Chart data // month
if (strpos($load, "month") !== false) {
$lastState = "";
$result['runner']['month'] = 0;
while($lastState != "ok") {
$chartdata_month_string = file_get_contents($url_getchartdata_month);
$chartdata_month = json_decode($chartdata_month_string);
$lastState = $chartdata_month->status;
$result['runner']['month']++;
if ($do == "preload") break;
sleep(2);
if ($result['runner']['month'] > 20) {
break;
}
}
$result['urls']['chart_month'] = $url_getchartdata_month;
if ($storeJSON) file_put_contents("chartdata_month.json", $chartdata_month_string);
}
# Chart data // day
if (strpos($load, "day") !== false) {
$lastState = "";
$result['runner']['day'] = 0;
$result['urls']['chart_day'] = $url_getchartdata_day;
while($lastState != "ok") {
$chartdata_day_string = file_get_contents($url_getchartdata_day);
$chartdata_day = json_decode($chartdata_day_string);
//ep($chartdata_day, $url_getchartdata_day);
$lastState = $chartdata_day->status;
$result['runner']['day']++;
if ($do == "preload") break;
sleep(2);
if ($result['runner']['day'] > 20) {
break;
}
}
if ($storeJSON) file_put_contents("chartdata_day.json", $chartdata_day_string);
}
# Chart data // week
if (strpos($load, "week") !== false) {
$lastState = "";
$result['runner']['week'] = 0;
while($lastState != "ok") {
$chartdata_week_string = file_get_contents($url_getchartdata_week);
$chartdata_week = json_decode($chartdata_week_string);
$lastState = $chartdata_week->status;
$result['runner']['week']++;
if ($do == "preload") break;
sleep(2);
if ($result['runner']['week'] > 20) {
break;
}
}
$result['urls']['chart_week'] = $url_getchartdata_week;
if ($storeJSON) file_put_contents("chartdata_week.json", $chartdata_week_string);
}
# Device data
if (strpos($load, "device") !== false && $do != "preload") {
$getdevicedata_string = file_get_contents($url_getdevicedata);
file_put_contents("getdevicedata.json", $getdevicedata_string);
$result['urls']['deviceData'] = $url_getdevicedata;
}
if ($do == "preload") {
$result['do'] = $do;
}
} else {
$chartdata_day_string = @file_get_contents("chartdata_day.json");
$chartdata_week_string = @file_get_contents("chartdata_week.json");
$chartdata_month_string = @file_get_contents("chartdata_month.json");
$chartdata_year_string = @file_get_contents("chartdata_year.json");
$getdevicedata_string = @file_get_contents("getdevicedata.json");
$result['cached'] = true;
}
# Start decoding
if ($do != "preload") {
if (strpos($load, "device") !== false) {
$deviceData = json_decode($getdevicedata_string);
if (is_object($deviceData) && isset($deviceData->status) && $deviceData->status == "ok") {
if (isset($deviceData->data[0])) {
# Standard information
$result['device']['token'] = $deviceData->token;
$result['device']['serialnumber'] = $deviceData->data[0]->serialnumber;
$result['device']['status'] = $deviceData->data[0]->status;
$result['device']['hardware_version'] = $deviceData->data[0]->data[0]->hv;
$result['device']['software_version'] = $deviceData->data[0]->data[0]->sv;
$result['device']['ewac_hardware_version'] = $deviceData->data[0]->hv;
$result['device']['ewac_software_version'] = $deviceData->data[0]->sv;
$result['device']['last_update'] = $deviceData->data[0]->data[0]->data->{"lu"};
# Installation date
$string = hexdec($deviceData->data[0]->data[0]->data->{"6"}->data);
$result['device']['installation_date'] = date("d.m.Y H:i", $string);
$result['device']['installation_date_timestamp'] = $string;
# Device errors
$result['device']['hasDeviceErrors'] = (isset($deviceData->data[0]->errors) && is_array($deviceData->data[0]->errors) && count($deviceData->data[0]->errors) > 0);
$result['device']['deviceErrors'] = $deviceData->data[0]->errors;
# device status (regeneration or not)
$string = $deviceData->data[0]->data[0]->data->{"791"}->data;
$string = explode(":", $string);
$flag = hexdec(substr($string[1], 0, 2));
if ($flag == 128) {
$result['device']['device_status'] = "normal";
} else {
$result['device']['device_status'] = "regeneration";
}
# device number_format
$string = $deviceData->data[0]->data[0]->data->{"3"}->data;
$v1 = substr($string, 0, 2);
$v2 = substr($string, 2, 2);
$v3 = substr($string, 4, 2);
$v4 = substr($string, 6, 2);
$result['device']['device_number'] = hexdec("$v4$v3$v2$v1");
# Hours til next service
$string = $deviceData->data[0]->data[0]->data->{"7"}->data;
$v1_high = substr($string, 0, 2);
$v1_low = substr($string, 2, 2);
$result['device']['next_service_in_days'] = floor(hexdec($v1_low.$v1_high)/24);
# service counter
$string = $deviceData->data[0]->data[0]->data->{"7"}->data;
$v2_high = substr($string, 4, 2);
$v2_low = substr($string, 6, 2);
$result['device']['service_counter'] = hexdec("$v2_low$v2_high");
# amount of handled water
$string = $deviceData->data[0]->data[0]->data->{"9"}->data;
$v1 = substr($string, 0, 2);
$v2 = substr($string, 2, 2);
$v3 = substr($string, 4, 2);
$v4 = substr($string, 6, 2);
$result['device']['liters_of_handled_water_total'] = hexdec("$v4$v3$v2$v1");
# regeneration counter
$string = $deviceData->data[0]->data[0]->data->{"791"}->data;
$string = explode(":", $string);
$lo = substr($string[1], 60, 2);
$hi = substr($string[1], 62, 2);
$result['device']['regeneration_counter'] = hexdec("$hi$lo");
} else {
$result['msg'] = "unable to find device data";
$result['state'] = "error";
}
} else {
$result['msg'] = "State not found / okay (".(isset($deviceData->status) ? $deviceData->status : "").")";
$result['state'] = "error";
}
}
$result['waterConsumption']['total'] = [];
$result['waterConsumption']['loaded'] = [];
# Water consumption // day
if (strpos($load, "day") !== false) {
$chartdata_day = json_decode($chartdata_day_string);
$result['waterConsumption']['total']['day'] = 0;
$result['waterConsumption']['loaded']['day'] = $day_to_load;
$labels = ["00:00-03:00", "03:00-06:00", "06:00-09:00", "09:00-12:00", "12:00-15:00", "15:00-18:00", "18:00-21:00", "21:00-00:00"];
$result['waterConsumption']['day'] = array();
if ($chartdata_day->status == "ok") {
if (strlen($chartdata_day->data) == 64) {
foreach($labels as $index => $label) {
$subIndex = ($index*8);
$result['waterConsumption']['day'][$index] = [
"label" => $label,
"consumption" => hexdec(substr($chartdata_day->data, $subIndex, 8))
];
$result['waterConsumption']['total']['day'] += $result['waterConsumption']['day'][$index]['consumption'];
}
} else {
# something is wrong!
$result['msg'] = "Unable to decode day water consumption. String should be 64 characters long, but is ".strlen($chartdata_day->data);
$result['state'] = "error";
}
}
}
# Water consumption // week
if (strpos($load, "week") !== false) {
$chartdata_week = json_decode($chartdata_week_string);
$result['waterConsumption']['total']['week'] = 0;
$result['waterConsumption']['loaded']['week'] = $week_to_load;
$labels = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
$result['waterConsumption']['week'] = array();
if ($chartdata_week->status == "ok") {
if (strlen($chartdata_week->data) == 56) {
foreach($labels as $index => $label) {
$subIndex = ($index*8);
$result['waterConsumption']['week'][$index] = [
"label" => $label,
"consumption" => hexdec(substr($chartdata_week->data, $subIndex, 8))
];
$result['waterConsumption']['total']['week'] += $result['waterConsumption']['week'][$index]['consumption'];
}
} else {
# something is wrong!
$result['msg'] = "Unable to decode week water consumption. String should be 56 characters long, but is ".strlen($chartdata_week->data);
$result['state'] = "error";
}
}
}
# Water consumption // month
if (strpos($load, "month") !== false) {
$chartdata_month = json_decode($chartdata_month_string);
$result['waterConsumption']['total']['month'] = 0;
$result['waterConsumption']['loaded']['month'] = $month_to_load;
$labels = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"];
$result['waterConsumption']['month'] = array();
if ($chartdata_month->status == "ok") {
if (strlen($chartdata_month->data) == 248) {
foreach($labels as $index => $label) {
$subIndex = ($index*8);
$result['waterConsumption']['month'][$index] = [
"label" => $label,
"consumption" => hexdec(substr($chartdata_month->data, $subIndex, 8))
];
$result['waterConsumption']['total']['month'] += $result['waterConsumption']['month'][$index]['consumption'];
}
} else {
# something is wrong!
$result['msg'] = "Unable to decode month water consumption. String should be 248 characters long, but is ".strlen($chartdata_month->data);
$result['state'] = "error";
}
}
}
# Water consumption // year
if (strpos($load, "year") !== false) {
$chartdata_year = json_decode($chartdata_year_string);
$result['waterConsumption']['total']['year'] = 0;
$result['waterConsumption']['loaded']['year'] = $year_to_load;
$labels = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"];
$result['waterConsumption']['year'] = array();
if ($chartdata_year->status == "ok") {
if (strlen($chartdata_year->data) == 96) {
foreach($labels as $index => $label) {
$subIndex = ($index*8);
$result['waterConsumption']['year'][$index] = [
"label" => $label,
"consumption" => hexdec(substr($chartdata_year->data, $subIndex, 8))
];
$result['waterConsumption']['total']['year'] += $result['waterConsumption']['year'][$index]['consumption'];
}
} else {
# something is wrong!
$result['msg'] = "Unable to decode year water consumption. String should be 96 characters long, but is ".strlen($chartdata_year->data);
$result['state'] = "error";
}
}
}
}
header("Content-Type: application/json");
echo json_encode($result);
Rule
rule "Entkalkungsanlage"
when
Time cron "5 20,40,1 6-22 * * ?"
or System started
then
sendHttpGetRequest("http://192.xxx.xxx.xxx/judo.php?load=device,day&do=preload");
shortTimer = createTimer(now.plusSeconds(10), [|
var String json = sendHttpGetRequest("http://192.xxx.xxx.xxx/judo.php?load=device,day");
postUpdate(Entkalk_Verbrauch_Heute, transform("JSONPATH", "$.waterConsumption.total.day", json));
postUpdate(Entkalk_Fetcher_Status, transform("JSONPATH", "$.state", json));
postUpdate(Entkalk_Fetcher_Now, transform("JSONPATH", "$.now", json));
postUpdate(Entkalk_Info_token, transform("JSONPATH", "$.device.token", json));
postUpdate(Entkalk_Info_status, transform("JSONPATH", "$.device.status", json));
postUpdate(Entkalk_Info_LastUpdate, transform("JSONPATH", "$.device.last_update", json));
postUpdate(Entkalk_Info_InstallDate, transform("JSONPATH", "$.device.installation_date", json));
postUpdate(Entkalk_Info_NextServiceInDays, transform("JSONPATH", "$.device.next_service_in_days", json));
postUpdate(Entkalk_Info_ServiceCounter, transform("JSONPATH", "$.device.service_counter", json));
postUpdate(Entkalk_Info_LitersTotal, transform("JSONPATH", "$.device.liters_of_handled_water_total", json));
postUpdate(Entkalk_Info_RegenerationCounter, transform("JSONPATH", "$.device.regeneration_counter", json));
var String value = transform("JSONPATH", "$.device.hasDeviceErrors", json);
if (value == "false") {
postUpdate(Entkalk_Info_HasErrors, OFF);
} else {
postUpdate(Entkalk_Info_HasErrors, ON);
}
logInfo("Entkalkungsanlage_hourly", "Entkalkungsanlage aktualisiert. Wasserverbrauch: " + transform("JSONPATH", "$.waterConsumption.total.day", json) + " Liter. Fehler vorhanden: " + value);
shortTimer = null;
]);
end
Items
Group Entkalkungsanlage
String Entkalk_Verbrauch_Heute "Wasserverbrauch (heute) [%s l]" <water> (Entkalkungsanlage)
String Entkalk_Verbrauch_Woche "Wasserverbrauch (Woche) [%s l]" <water> (Entkalkungsanlage)
String Entkalk_Verbrauch_Monat "Wasserverbrauch (Monat) [%s l]" <water> (Entkalkungsanlage)
String Entkalk_Fetcher_Status "Fetcher Status [%s]" (Entkalkungsanlage)
String Entkalk_Fetcher_Now "Zeitpunkt [%s]" (Entkalkungsanlage)
String Entkalk_Info_LastUpdate "Letztes Update [%s]" (Entkalkungsanlage)
String Entkalk_Info_token "Token [%s]" (Entkalkungsanlage)
String Entkalk_Info_status "Status [%s]" (Entkalkungsanlage)
String Entkalk_Info_InstallDate "Installationsdatum [%s]" (Entkalkungsanlage)
Switch Entkalk_Info_HasErrors "Fehler gefunden [%s]" (Entkalkungsanlage)
Number Entkalk_Info_NextServiceInDays "Nächster Service [%d Tage]" (Entkalkungsanlage)
Number Entkalk_Info_ServiceCounter "Service counter [%d]" (Entkalkungsanlage)
Number Entkalk_Info_LitersTotal "Liter insgesamt [%d l]" <water> (Entkalkungsanlage)
Number Entkalk_Info_RegenerationCounter "Regeneration counter [%d]" (Entkalkungsanlage)
Sitemap
Text item=Entkalk_Verbrauch_Heute
Text item=Entkalk_Verbrauch_Woche
Text item=Entkalk_Verbrauch_Monat
Group label="Weitere Informationen" item=Entkalkungsanlage