DIY Smart Meter
Pretty much all Digital Electricity meters (smart or not) have a light that blinks every time a certain amount of energy is used - often once for every Watt-hour (Usually labelled as 1000 imp/kWh
).
You can easily detect this with a simple Light Dependent Resistor and use it to measure and record your energy usage over time.
You'll Need
- A Puck.js
- A standard Light Dependent Resistor (eg. GL5537) - see the end for information about other options.
- Some double sided tape (VHB tape works great)
Wiring
Just solder the LDR between pins D1 and D2, so that it can go through a hole in the back of the Puck.js case.
See the video for more information.
Simple Pulse Counter
This is the code used in the video - it measures the number of pulses and reports that over Bluetooth LE Advertising.
Simply copy & paste this to the right-hand side of the Web IDE and click the 'Upload' button.
var counter = 0;
// Update BLE advertising
function update() {
var a = new ArrayBuffer(4);
var d = new DataView(a);
d.setUint32(0, counter, false/*big endian*/);
NRF.setAdvertising({},{
name: "Puck.js \xE2\x9A\xA1",
manufacturer:0x0590,
manufacturerData:a,
interval: 600 // default is 375 - save a bit of power
});
}
// Set up pin states
D1.write(0);
pinMode(D2,"input_pullup");
// Watch for pin changes
setWatch(function(e) {
counter++;
update();
digitalPulse(LED1,1,1); // show activity
}, D2, { repeat:true, edge:"falling" });
Pulse Counter with BTHome/Home Assistant
You can also make your Puck.js advertise in the BTHome advertising format which means it'll be picked up and automatically integrated into Home Assistant.
💡This code can be uploaded in a few clicks with the Espruino App loader
Just use the following code:
var counter = 0; // number of impulses
var power = 0; // power in watts (assuming 1000imp/kwh)
var lastPulse = getTime();
// Update BLE advertising
function update() {
NRF.setAdvertising(require("BTHome").getAdvertisement([
{
type : "battery",
v : E.getBattery()
}, {
type : "energy",
v : counter/1000 // kWh, and usualy 1000 imp/kWh
}, {
type : "power",
v : power // kWh, and usualy 1000 imp/kWh
}
]), {
name : "Electricity",
// not being connectable/scannable saves power (but you'll need to reboot to connect again with the IDE!)
//connectable : false, scannable : false,
});
}
// Set up pin states
D1.write(0);
pinMode(D2,"input_pullup");
// Watch for pin changes
setWatch(function(e) {
let timeDiff = e.time - lastPulse;
power = 3600 / timeDiff; // 1000imp/kwh -> 1000 pulses in 3600sec = 1kW
counter++;
update();
lastPulse = e.time;
digitalPulse(LED1,1,1); // show activity
}, D2, { repeat:true, edge:"falling" });
update();
Note: we calculate power here using the time between pulses, but if no pulse is received we don't 'zero' the power usage. We're just assuming that a power meter will always have some power draw and so will flash every so often.
Pulse Counter and usage history
However, you can easily go a bit further and can store historical energy usage data on the Puck.js itself.
This code uses exactly the same detection code as before,
but adds a class called Counter
that stores historical
energy usage data.
Once connected to a Web Bluetooth website, the data can then be read and displayed on a graph.
To use this:
- Go into the Web IDE settings,
Communications
and ensure thatSet Current Time
is ticked. - Copy & paste the code below to the right-hand side of the Web IDE
- Click the 'Upload' button
- Type
save()
on the left-hand side of the IDE. You're now ready to go.
/** Create a new counter. Contains the following:
{
totals : {
count // Overall count
year // each month of the year (0..11)
week // each day of the week (0..6)
month // each day of the month (0..31)
day : // each hour (0..23)
},
history // last 96 hours, newest last (0..95)
}
*/
function Counter() {
this.clear();
};
/// Clear the counters back to zero
Counter.prototype.clear = function() {
this.totals = {
count : 0, // Overall count
year : new Uint32Array(12), // each month of the year (0..11)
week : new Uint32Array(7), // each day of the week (0..6)
month : new Uint32Array(31), // each day of the month (0..31)
day : new Uint32Array(24) // each hour (0..23)
};
this.historyPeriod = 60*60*1000; // 1 hour in milliseconds
this.history = new Uint32Array(96); // last 96 hours
this.historyTime = 0;
this.lastUpdate = new Date.now();
};
///
Counter.prototype.inc = function(n) {
if (n===undefined) n=1;
var d = new Date();
var t = this.totals;
// Totals by time period
t.count+=n;
t.year[d.getMonth()]+=n;
t.week[d.getDay()]+=n;
t.month[d.getDate()-1]+=n;
t.day[d.getHours()]+=n;
// Rolling history
this.historyTime += d.getTime()-this.lastUpdate;
this.lastUpdate = d.getTime();
var steps = Math.floor(this.historyTime / this.historyPeriod);
if (steps>=0) {
this.historyTime -= steps*this.historyPeriod;
var h = this.history;
h.set(new Uint32Array(h.buffer, 4*steps), 0);
h.fill(0, h.length-steps);
h[h.length-1]+=n;
}
};
var c = new Counter();
// Update BLE advertising
function update() {
var a = new ArrayBuffer(4);
var d = new DataView(a);
d.setUint32(0, c.totals.count, false/*big endian*/);
NRF.setAdvertising({},{
name: "Puck.js \xE2\x9A\xA1",
manufacturer:0x0590,
manufacturerData:a,
interval: 600 // default is 375 - save a bit of power
});
}
function onInit() {
clearWatch();
D1.write(0);
pinMode(D2,"input_pullup");
setWatch(function(e) {
c.inc(1);
update();
digitalPulse(LED1,1,1); // show activity
}, D2, { repeat:true, edge:"falling" });
update();
}
Now you have the Puck.js set up, you can use the Web Bluetooth website below (or can modify it for your needs).
To try it, just click the Try Me!
button in the bottom right of the code sample below. To make your own version, follow the instructions for creating a GitHub page at the top of the Web Bluetooth tutorial
<html>
<head>
<title>Dashboard</title>
<meta name="viewport" content="width=620, initial-scale=1">
</head>
<body style="width:620px;height:800px">
<link href="https://espruino.github.io/TinyDash/tinydash.css" rel="stylesheet">
<script src="https://espruino.github.io/TinyDash/tinydash.js"></script>
<script src="https://www.puck-js.com/puck.js"></script>
<script>
function connectDevice() {
// connect, issue Ctrl-C to clear out any data that might have been left in REPL
Puck.write("\x03", function() {
setTimeout(function() {
// After a short delay ask for the battery percentage
Puck.eval("{bat:E.getBattery()}", function(d,err) {
if (!d) {
alert("Web Bluetooth connection failed!\n"+(err||""));
return;
}
// remove the 'connect' window
elements.modal.remove();
// update battery meter
elements.bat.setValue(d.bat);
// Get all our data - it can take a while
// so we do it in stages
Puck.eval("c.totals", function(d,err) {
elements.total.setValue(d.count);
elements.year.setData(d.year);
elements.week.setData(d.week);
elements.month.setData(d.month);
elements.day.setData(d.day);
// Get the 96 hour history last
Puck.eval("c.history", function(d) {
elements.history.setData(d);
});
});
}, 500);
});
});
}
// Set up the controls we see on the screen
var elements = {
heading : TD.label({x:10,y:10,width:190,height:50,label:"Electricity meter"}),
total : TD.value({x:10,y:70,width:190,height:60,label:"Total Usage",value:0}),
bat : TD.gauge({x:10,y:140,width:190,height:220,label:"Battery Level",value:0,min:0,max:100}),
history : TD.graph({x:210,y:10+160*0,width:400,height:150,label:"96 hr history"}),
day : TD.graph({x:210,y:10+160*1,width:400,height:150,label:"Day"}),
month : TD.graph({x:210,y:10+160*2,width:400,height:150,label:"Month"}),
week : TD.graph({x:210,y:10+160*3,width:400,height:150,label:"Week"}),
year : TD.graph({x:210,y:10+160*4,width:400,height:150,label:"Year"}),
modal: TD.modal({x:10,y:10,width:600,height:790,label:"Click to connect",onchange:connectDevice})
}
for (var i in elements)
document.body.appendChild(elements[i]);
</script>
</body>
</html>
Notes
- While this example doesn't do it, your current power consumption can be calculated by measuring the time between pulses - it'd be a good addition to the advertising data.
- Web Bluetooth websites can't read advertising data (yet) so cannot display any electricity data without connecting. However another Espruino device like Pixl.js could do that easily.
- The resistance of the LDR varies with the light on it. On a standard LDR like a GL5537 complete darkness has a resistance of around 2 M Ohms, so the power usage from the coin cell (against the 40k internal pullup) is very low. However the more ambient light that gets into the sensor the lower the resistance and so the lower the battery life - so it's worth trying to fit the sensor as snugly as possible.
Do I need to use an LDR?
The Puck does have the ability to measure light via Puck.light()
,
and it does this using the fact the red LED makes a small voltage when exposed to
some wavelengths of light.
If you're lucky and your electricity meter's flashing LED is of a wavelength that will trigger the red (or green) LEDs then you could try and use some code like this:
// D5 (LED1) is used for sensing
// D1 is used as an output which is also watched with setWatch to detect a state change
var ll = require("NRF52LL");
analogRead(D5);
// set up D1 as an output
digitalWrite(D1,0);
// create a 'toggle' task for pin D1
var tog = ll.gpiote(7, {type:"task",pin:D1,lo2hi:1,hi2lo:1,initialState:0});
// compare D5 against vref/16 (vref:8 would be vref/2)
var comp = ll.lpcomp({pin:D5,vref:1,hyst:true});
// use a PPI to trigger the toggle event
ll.ppiEnable(0, comp.eCross, tog.tOut);
// Detect a change on D1
setWatch(function() {
// called twice per 'flash' (for light on and off)
print("Light level changed");
}, D1, {repeat:true});
You would then need to mount the Puck the opposite way around with the LEDs directly over the top of the meter's light.
This is in some ways better than the LDR arrangement as there is no resistance draining the Puck's battery.
This page is auto-generated from GitHub. If you see any mistakes or have suggestions, please let us know.