Skip to content

Instantly share code, notes, and snippets.

@impankratov
Created May 22, 2019 11:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save impankratov/e7f91881d42d35716cb77236ba9499fb to your computer and use it in GitHub Desktop.
Save impankratov/e7f91881d42d35716cb77236ba9499fb to your computer and use it in GitHub Desktop.
Tasmota power calibration
// ==UserScript==
// @name tasmota-calibration
// @namespace http://tampermonkey.net/
// @version 1.0
// @description try to take over the world!
// @author yu_ya45
// @match http://*/
// @grant none
// ==/UserScript==
//----- Read me -----
//This is a user script that automatically and accurately calibrates.
//(The voltage needs to be adjusted manually.)
//Device and version used for testing
//Sonoff S31 / Tasmota 6.4.1.21
//
//Step1
//Set each variable according to your environment in the Settings section.
//
//Step2
//Apply this user script.
//After that, "Calibration Start" is added to the top page.
//
//Step3
//If you have a multimeter, use the VoltageSet command to adjust the voltage.
//
//Step4
//When you are ready and run "Calibration Start", calibration will be performed automatically.
//When starting the calibration, execute the following commands necessary to
//obtain the correct calibration value as an initial setting.
//Backlog VoltRes 3; WattRes 3; CurrentSet (expected current + 100mA)
//(The reason for executing the CurrentSet command is that when the power factor
//is 1, the power value is truncated.)
//Calibration is performed in the order of Power and Current.
//If you want to cancel the process, either press Start again or refresh the page.
//
//Step5
//It is complete when "Calibration Done!" Is displayed.
//
//The reason why the power factor is not 100%
//The power factor of the incandescent bulb is 1, but adjusting the current to match it may result
//in a slight downward correction of the power.
//This is because when the measured active power is larger than the apparent power multiplied
//by the voltage value and the current value, the smaller apparent power value is treated as the active power.
//Therefore, this user script adjusts the current so that the probability of becoming
//"active power == apparent power" is around 50%.
(function() {
//-----Settings start-----
//match address
//Please specify part of the address to which this user script applies.
var matchAddress = '192.168';
//Rated voltage(unit:V)
//Rated voltage of light bulb used for test
var ratedVoltage = 220;
//Rated power(unit:W)
//Rated power consumption of the light bulb used in the test
var ratedPower = 95;
//Calibration accuracy power range (unit:W)
//In the case of 0.05, adjust it to fall within
//the range of -0.05W to +0.05W compared to the expected value.
var accuracyPowerRange = 0.05;
//Calibration accuracy current range (unit:%)
//In case of 10, adjust the probability that "active power == apparent power" to be 40%-60%.
var accuracyCurrentRange = 10;
//Number of samplings
//How many sampling numbers do you use to calculate the average of expected error?
var samplingsPower = 50;
var samplingsCurrent = 50;
//-----Settings end-----
if (location.href.indexOf(matchAddress) == -1) {
return;
}
var x = null,
lt,
to,
tp,
pc = '';
var isInitPower = true;
var diffPowerSum = 0;
var diffPowerCnt = 0;
var expectedCurrent = ratedPower / ratedVoltage;
var expectedResistance = ratedVoltage / expectedCurrent;
var averageDiffPower;
var prevVal = '';
var calibrationStatus = 0;
var isDelay = false;
var powerSetCal = 0;
var isInitCurrent = true;
var activePower = 0;
var apparentPower = 0;
var activePowerMinCnt = 0;
var apparentPowerMinCnt = 0;
var activePowerRate = 0;
var apparentPowerRate = 0;
var allCnt = 0;
//override method
var eb = function eb(s) {
return document.getElementById(s);
};
var la = function la(p) {
var a = '';
if (la.arguments.length == 1) {
a = p;
clearTimeout(lt);
}
if (x != null) {
x.abort();
}
x = new XMLHttpRequest();
x.onreadystatechange = function() {
if (x.readyState == 4 && x.status == 200) {
var s = x.responseText;
var actualVoltage;
if (calibrationStatus == 1) {
//Power Calibration
if (isInitPower) {
isInitPower = false;
x.open(
'GET',
'cs?c2=&c1=Backlog VoltRes 3; WattRes 3; CurrentSet ' +
(expectedCurrent * 1000 + 100),
true
);
x.send();
}
if (x.responseURL.indexOf('cs') != -1) {
var arr = s.split('\n');
var cal = parseFloat(
arr[arr.length - 1].replace(/"\{PowerSetCal":([0-9]+)\}/, '$1')
);
isDelay = true;
if (powerSetCal != 0 && averageDiffPower < 0 && cal > powerSetCal) {
x.open(
'GET',
'cs?c2=&c1=PowerSet ' +
(Math.round((actualPower + averageDiffPower) * 1000) / 1000 +
(0 - accuracyPowerRange)),
false
);
} else if (
powerSetCal != 0 &&
averageDiffPower > 0 &&
cal < powerSetCal
) {
x.open(
'GET',
'cs?c2=&c1=PowerSet ' +
(Math.round((actualPower + averageDiffPower) * 1000) / 1000 +
accuracyPowerRange),
false
);
} else {
powerSetCal = cal;
return;
}
x.send();
return;
}
//get actual value
actualVoltage = parseFloat(
s.replace(/.*Voltage{m}(.*)V{e}{s}.*/, '$1').trim()
);
var actualPower = parseFloat(
s.replace(/.*Power{m}(.*)W{e}{s}.*/, '$1').trim()
);
if ('' + actualVoltage + actualPower != prevVal) {
prevVal = '' + actualVoltage + actualPower;
var calculatedCurrent = actualVoltage / expectedResistance;
var calculatedPower = actualVoltage * calculatedCurrent;
var diffPower = calculatedPower - actualPower;
diffPowerSum += diffPower;
diffPowerCnt++;
averageDiffPower = diffPowerSum / diffPowerCnt;
//Specified number of times
if (diffPowerCnt >= samplingsPower) {
if (Math.abs(averageDiffPower) <= accuracyPowerRange) {
calibrationStatus = 2;
} else {
isDelay = true;
x.open(
'GET',
'cs?c2=&c1=PowerSet ' +
Math.round((actualPower + averageDiffPower) * 1000) / 1000,
false
);
x.send();
diffPowerSum = 0;
diffPowerCnt = 0;
}
}
}
} else if (calibrationStatus == 2) {
//Current Calibration
if (x.responseURL.indexOf('cs') != -1) {
return;
}
actualVoltage = parseFloat(
s.replace(/.*Voltage{m}(.*)V{e}{s}.*/, '$1').trim()
);
activePower = parseFloat(
s.replace(/.*Power{m}(.*)W{e}{s}.*/, '$1').trim()
);
apparentPower = parseFloat(
s.replace(/.*Apparent Power{m}(.*)VA{e}{s}.*/, '$1').trim()
);
if (isInitCurrent) {
isDelay = true;
isInitCurrent = false;
x.open(
'GET',
'cs?c2=&c1=CurrentSet ' +
(Math.round((activePower / actualVoltage) * 1000) / 1000) *
1000,
false
);
x.send();
}
if (activePower < apparentPower) {
activePowerMinCnt++;
} else {
apparentPowerMinCnt++;
}
allCnt = activePowerMinCnt + apparentPowerMinCnt;
activePowerRate = activePowerMinCnt / allCnt;
apparentPowerRate = apparentPowerMinCnt / allCnt;
if (allCnt >= samplingsCurrent) {
if (activePowerRate < 0.5 - accuracyCurrentRange / 100) {
isDelay = true;
x.open(
'GET',
'cs?c2=&c1=CurrentSet ' +
((Math.round((activePower / actualVoltage) * 1000) / 1000) *
1000 +
1),
false
);
x.send();
} else if (activePowerRate > 0.5 + accuracyCurrentRange / 100) {
isDelay = true;
x.open(
'GET',
'cs?c2=&c1=CurrentSet ' +
((Math.round((activePower / actualVoltage) * 1000) / 1000) *
1000 -
1),
false
);
x.send();
} else {
calibrationStatus = 3;
}
activePowerMinCnt = 0;
apparentPowerMinCnt = 0;
allCnt = 0;
}
}
//Display
var status = '</BR>';
var calibration = '';
if (calibrationStatus == 1) {
status = '</BR><b>Calibration of Power...</b></BR>';
calibration =
'Count:' +
diffPowerCnt +
'</br>Average diff power:' +
Math.round(averageDiffPower * 1000) / 1000;
} else if (calibrationStatus == 2) {
status = '</BR><b>Calibration of Current...</b></BR>';
calibration =
'Count:' +
allCnt +
'</BR>Actual apparent power:' +
Math.round(apparentPower * 1000) / 1000 +
'</BR>Actual active power :' +
Math.round(activePower * 1000) / 1000 +
'</BR>active < apparent:' +
Math.round(activePowerRate * 100) +
'%' +
'</BR>active == apparent:' +
Math.round(apparentPowerRate * 100) +
'%';
} else if (calibrationStatus == 3) {
status = '<b>Calibration Done!</b></BR>';
}
s = calibration + status + '</BR>' + s;
s = s
.replace(/{t}/g, "<table style='width:100%'")
.replace(/{s}/g, '<tr><th>')
.replace(/{m}/g, '</th><td>')
.replace(/{e}/g, '</td></tr>')
.replace(/{c}/g, "%'><div style='text-align:center;font-weight:");
eb('l1').innerHTML = s;
}
};
x.open('GET', '.?m=1' + a, false);
x.send();
if (isDelay) {
lt = setTimeout(la, 3000);
isDelay = false;
} else {
lt = setTimeout(la, 200);
}
};
//Add a button to start calibration.
var btn1 = document.createElement('button');
btn1.textContent = 'Calibration Start';
btn1.onclick = function() {
if (calibrationStatus == 0) {
calibrationStatus = 1;
diffPowerSum = 0;
diffPowerCnt = 0;
} else {
calibrationStatus = 0;
}
};
var p1 = document.createElement('p');
p1.appendChild(btn1);
var node = eb('l1');
node.parentNode.insertBefore(p1, node);
window.onload = la();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment