Skip to content

Instantly share code, notes, and snippets.

@blakadder
Last active April 30, 2020 12:13
Show Gist options
  • Save blakadder/beb92969e5af570dfb34d0001dee4754 to your computer and use it in GitHub Desktop.
Save blakadder/beb92969e5af570dfb34d0001dee4754 to your computer and use it in GitHub Desktop.
User script for automatic 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%.
//-----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 = 60;
//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;
//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);
//override method
window.eb = function eb(s){
return document.getElementById(s);
}
window.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);
}
}
window.onload=la();
@bruvv
Copy link

bruvv commented Nov 9, 2019

"Invalide userscript" is the error I am getting with chrome. any idea why?

@kumy
Copy link

kumy commented Dec 14, 2019

@bruvv The first line is missing, script should be:

// ==UserScript==
// @name         tasmota-calibration
// @namespace    http://tampermonkey.net/
// @version      1.0
[…]

@blakadder May you amend the gist please. It would helps users coming from https://github.com/arendst/Tasmota/wiki/Power-Monitoring-Calibration#script-for-automatic-calibration

@blakadder
Copy link
Author

Sure thing!

@DDomnick
Copy link

DDomnick commented Feb 4, 2020

Would love to get this to work! Very useful script. Using Tasmota 8.1.0, Sonoff S31.
Here are my observations and results:

  1. TamperMonkey shows 'eb' is not defined' line 119
  2. TamperMonkey shows 'eb' is not defined' line 251
  3. TamperMonkey shows 'la' is not defined' line 263

Scripts seems to run after following instructions, but runs for several iterations of 50 cycles 'Calibration of power' with no results or Calibration Done! displayed. How long to let it run?

Entered backlog VoltRes 3; WattRes 3; CurrentSet 686.9 as instructed, same results.

Tried doing initial calibration, Method 1 from https://github.com/arendst/Tasmota/wiki/power-monitoring-calibration
first, with backlog PowerSet 75.0; VoltageSet 127.8; CurrentSet 586.9 - same results.

Any additional tips on running this script to completion?

My settings:
//-----Settings start-----
//match address
//Please specify part of the address to which this user script applies.
var matchAddress = "10.10.2"

//Rated voltage(unit:V)
//Rated voltage of light bulb used for test
var ratedVoltage = 120;

//Rated power(unit:W)
//Rated power consumption of the light bulb used in the test
var ratedPower = 75;

//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-----

@Ysbrand
Copy link

Ysbrand commented Feb 28, 2020

It can take a long time to run, seems that you were not even at 50% when you aborted.

First phase is: Calibration of Power...
Then you will go into: Calibration of Current...
Before you will get the message: Calibration Done!

@bruvv
Copy link

bruvv commented Apr 30, 2020

@DDomnick got the same issue with v8.2.0. The script has been running for over 1 hour and still nothing. Increased the samplingsPower by increments of 10, ended with 150 and still not calibrating it. Is the script still working?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment