Skip to content

Commit

Permalink
Plugin TasmotaSP: add power/energy monitoring
Browse files Browse the repository at this point in the history
  • Loading branch information
dexterbg committed Sep 12, 2023
1 parent 1de4b4f commit 8671d03
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 25 deletions.
37 changes: 31 additions & 6 deletions plugins/tasmotasp/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tasmota Smart Plug Control

**Smart Plug control for hardware running the Tasmota open source firmware**

Version 1.0 by Michael Balzer <dexter@dexters-web.de>
Version 2.0 by Michael Balzer <dexter@dexters-web.de>

- `Tasmota open source firmware <https://tasmota.github.io/>`_
- `List of Tasmota based smart plugs <https://templates.blakadder.com/plug.html>`_
Expand All @@ -17,6 +17,10 @@ The smart plug can be bound to a defined location. Automatic periodic recharging
be configured via main battery SOC level and/or 12V battery voltage level. The plugin can also
switch off power at the charge stop event of the main and/or 12V battery.

Since version 2.0, the plugin can also monitor the plug power & energy sensors, if
available. Energy counters can be reset from the config page.


------------
Installation
------------
Expand Down Expand Up @@ -63,19 +67,28 @@ Use the web frontend for simple configuration.
usr tasmotasp.aux_volt_on optional: switch on if 12V level at/below
usr tasmotasp.aux_volt_off optional: switch off if 12V level at/above
usr tasmotasp.aux_stop_off optional: yes = switch off on vehicle.charge.12v.stop
usr tasmotasp.power_mon optional: yes = monitor plug power & energy sensors
-----
Usage
-----

.. code-block:: none
script eval tasmotasp.get()
script eval tasmotasp.set("on" | "off" | 0 | 1 | true | false)
script eval tasmotasp.info()
script eval tasmotasp.get([true]) -- read power state, true = also read sensor data
script eval tasmotasp.set("on" | "off" | 1 | 0 | true | false)
script eval tasmotasp.info() -- output config, state & data
script eval tasmotasp.status() -- only output state & data
script eval tasmotasp.sendcmd(command, [true]) -- send any Tasmota command, true = followed by sensor read
Notes: ``get()``, ``set()`` & ``sendcmd()`` are asynchronous operations, the results are logged.
Use ``info()`` or ``status()`` to show/fetch the current state.
Sensor monitoring (if enabled) is done on request and while the power is switched on,
with a single final update after switching the power off.
See Tasmota documentation on command syntax.

Note: ``get()`` & ``set()`` do an async update (if the location matches), the result is logged.
Use ``info()`` to show the current state.
Hint: to use a Tasmota plug for charging on the road, bind it to the module's Wifi
access point.

------
Events
Expand All @@ -86,4 +99,16 @@ Events
usr.tasmotasp.on
usr.tasmotasp.off
usr.tasmotasp.error
usr.tasmotasp.getdata -- trigger a sensor read
-------------
Notifications
-------------

.. code-block:: none
usr.tasmotasp.data -- stream notification, payload = sensor data object (JSON)
See web plugins on how to use the sensor data stream. Note: regular updates are
done on the ticker event, i.e. by default once per minute.

54 changes: 51 additions & 3 deletions plugins/tasmotasp/tasmotasp-status.htm
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
<!--
Web UI plugin for page /status hook body.post:
Add Tasmota Smart Plug power control button to Vehicle panel
Tasmota Smart Plug plugin Version 1.0 Michael Balzer <dexter@dexters-web.de>
Tasmota Smart Plug plugin Version 2.0 Michael Balzer <dexter@dexters-web.de>
-->

<style>
.btn-group.data-tasmotasp .metric {
display: block;
float: none;
font-size: 80%;
line-height: 110%;
}
</style>

<script>
(function(){

var tasmotasp = { cfg: {}, state: { power: "", error: "" } };
var tasmotasp = { cfg: {}, state: { power: "", error: "" }, data: {} };
var $receiver = $('#livestatus');
var $actionres = $('#vehicle-cmdres');
var $actionset;
var $datadisplay;

// State & UI update:
function update(data) {
Expand All @@ -23,12 +33,28 @@
.prop('checked', true)
.parent().addClass('active');
}
// update energy readings:
if (tasmotasp.cfg["power_mon"] == "yes") {
updateEnergy();
$datadisplay.show();
} else {
$datadisplay.hide();
}
// show error:
if (tasmotasp.state.error) {
$actionres.text("TasmotaSP: " + tasmotasp.state.error);
}
}

// Display power/energy sensor data:
function updateEnergy() {
if (tasmotasp.data["StatusSNS"] && tasmotasp.data["StatusSNS"]["ENERGY"]) {
let td = tasmotasp.data["StatusSNS"]["ENERGY"];
$('#data-tasmotasp-power').text(td["Power"]);
$('#data-tasmotasp-total').text(td["Total"]);
}
}

// Listen to tasmotasp events:
$receiver.on('msg:event', function(e, event) {
if (event == "usr.tasmotasp.on")
Expand All @@ -39,6 +65,21 @@
getInfo();
});

// Listen to tasmotasp stream update:
$receiver.on('msg:notify', function(e, msg) {
if (msg.subtype == "usr/tasmotasp/data") {
try {
var data = JSON.parse(msg.value);
if (typeof data == "object") {
tasmotasp.data = Object.assign(tasmotasp.data, data);
updateEnergy();
}
} catch (e) {
// ignore
}
}
});

// Get state & config:
function getInfo() {
loadjs('tasmotasp.info()').then(function(output) {
Expand All @@ -51,13 +92,18 @@
// add buttons to panel:
$('#vehicle-cmdres').parent().before(
'<li>'+
'<label class="control-label">Power:</label>'+
'<label class="control-label">Plug:</label>'+
'<div class="btn-group action-tasmotasp-set" data-toggle="buttons">'+
'<label class="btn btn-default btn-sm action-tasmotasp-off"><input type="radio" name="power" value="off">OFF</label>'+
'<label class="btn btn-default btn-sm action-tasmotasp-on"><input type="radio" name="power" value="on">ON</label>'+
'</div>'+
'<div class="btn-group data-tasmotasp" style="display:none">'+
'<div class="metric number"><span class="value" id="data-tasmotasp-power">?</span><span class="unit">W</span></div>'+
'<div class="metric number"><span class="value" id="data-tasmotasp-total">?</span><span class="unit">kWh</span></div>'+
'</div>'+
'</li>');
$actionset = $('.action-tasmotasp-set > label');
$datadisplay = $('.data-tasmotasp');
// add button handler:
$('.action-tasmotasp-set input').on('change', function(e) {
$actionres.empty();
Expand All @@ -66,6 +112,8 @@
});
// get status:
getInfo();
// subscribe to stream updates:
$receiver.subscribe("notify/stream/usr/tasmotasp/#");
});

})();
Expand Down
91 changes: 87 additions & 4 deletions plugins/tasmotasp/tasmotasp.htm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!--
Web UI page plugin:
Control & configuration frontend for Tasmota Smart Plug module plugin (tasmotasp.js)
Tasmota Smart Plug Plugin Version 1.0 Michael Balzer <dexter@dexters-web.de>
Tasmota Smart Plug Plugin Version 2.0 Michael Balzer <dexter@dexters-web.de>
Recommended installation:
- Page: /usr/tasmotasp
Expand All @@ -10,11 +10,21 @@
- Auth: Cookie
-->

<style>
#form-data table {
width: auto;
}
#form-data th, #form-data td {
text-align: left;
padding-right: 2em;
}
</style>

<div class="panel panel-primary panel-single">
<div class="panel-heading">Tasmota Smart Plug</div>
<div class="panel-body">
<div class="alert alert-dismissible" style="display:none" />
<div class="receiver" id="tasmotasp">
<div class="receiver" id="tasmotasp" data-subscriptions="notify/stream/usr/tasmotasp/#">

<form class="form-horizontal" id="form-state">
<fieldset><legend>State</legend>
Expand All @@ -28,6 +38,24 @@
<samp class="samp-inline" id="action-tasmotasp-output"></samp>
</div>
</div>
<div class="form-group" id="form-data" style="display:none">
<label class="control-label col-sm-3">Energy:</label>
<div class="col-sm-9">
<table class="table table-striped table-condensed">
<tbody>
</tbody>
</table>
<button type="button" class="btn btn-default" data-target="#action-tasmotasp-output2"
data-js="tasmotasp.sendcmd('EnergyToday 0', true)">
Reset Today
</button>
<button type="button" class="btn btn-default" data-target="#action-tasmotasp-output2"
data-js="tasmotasp.sendcmd('EnergyTotal 0', true)">
Reset Total
</button>
<samp class="samp-inline" id="action-tasmotasp-output2"></samp>
</div>
</div>
</fieldset>
</form>

Expand Down Expand Up @@ -66,6 +94,13 @@
</span>
</div>
</div>
<div class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<div class="checkbox">
<label><input type="checkbox" name="power_mon" id="input-power_mon" value="yes">Enable energy monitoring</label>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3" for="input-location">Location:</label>
<div class="col-sm-9">
Expand Down Expand Up @@ -134,10 +169,22 @@
<script>
(function(){

var tasmotasp = { cfg: {}, state: { power: "", error: "" } };
var tasmotasp = { cfg: {}, state: { power: "", error: "" }, data: {} };
var $actionset = $('#tasmotasp .action-tasmotasp-set > label');
var $actionres = $('#action-tasmotasp-output');
var $alert = $('.alert');
var $datadisplay = $('#form-data'), $datarows = $('#form-data tbody');

var units = {
"Voltage": "V",
"Current": "A",
"Power": "W",
"ApparentPower": "VA",
"ReactivePower": "VAr",
"Total": "kWh",
"Today": "kWh",
"Yesterday": "kWh",
};

// Alert utility:
function showAlert(type, text) {
Expand All @@ -161,11 +208,19 @@
.prop('checked', true)
.parent().addClass('active');
}
// update energy data:
if (tasmotasp.cfg.power_mon == "yes") {
updateEnergy();
$datadisplay.show();
} else {
$datadisplay.hide();
}
// update config form:
$('#input-ip').val(tasmotasp.cfg.ip);
$('#input-user').val(tasmotasp.cfg.user);
$('#input-pass').val(tasmotasp.cfg.pass);
$('#input-socket').val(tasmotasp.cfg.socket);
$('#input-power_mon').prop("checked", tasmotasp.cfg.power_mon == "yes");
$('#input-location').val(tasmotasp.cfg.location);
$('#soc_on').slider({ checked: (tasmotasp.cfg.soc_on != ""), value: Number(tasmotasp.cfg.soc_on) });
$('#soc_off').slider({ checked: (tasmotasp.cfg.soc_off != ""), value: Number(tasmotasp.cfg.soc_off) });
Expand All @@ -178,6 +233,18 @@
showAlert("danger", tasmotasp.state.error);
}
}

// Display power/energy sensor data:
function updateEnergy() {
if (tasmotasp.data["StatusSNS"] && tasmotasp.data["StatusSNS"]["ENERGY"]) {
$datarows.empty();
for (let [key, value] of Object.entries(tasmotasp.data["StatusSNS"]["ENERGY"])) {
if (typeof value == "number") {
$datarows.append(`<tr><th>${key}</th><td>${value}</td><td>${units[key]||""}</td></tr>`);
}
}
}
}

// Listen to tasmotasp events:
$('#tasmotasp').on('msg:event', function(e, event) {
Expand All @@ -189,6 +256,21 @@
getInfo();
});

// Listen to tasmotasp stream update:
$('#tasmotasp').on('msg:notify', function(e, msg) {
if (msg.subtype == "usr/tasmotasp/data") {
try {
var data = JSON.parse(msg.value);
if (typeof data == "object") {
tasmotasp.data = Object.assign(tasmotasp.data, data);
updateEnergy();
}
} catch (e) {
// ignore
}
}
});

// Get state & config:
function getInfo() {
loadcmd('script eval tasmotasp.info()').then(function(output) {
Expand All @@ -209,7 +291,8 @@
$actionres.empty();
var upd = {
soc_on: "", soc_off: "", chg_stop_off: "",
aux_volt_on: "", aux_volt_off: "", aux_stop_off: ""
aux_volt_on: "", aux_volt_off: "", aux_stop_off: "",
power_mon: ""
};
var inp = $('#form-cfg').serializeArray();
inp.map(el => upd[el["name"]] = el["value"]);
Expand Down

0 comments on commit 8671d03

Please sign in to comment.