sml_simple.tc¶
sml_simple.tc — SML meter readout, no charts, no emulator.
// @name: SML Simple
// @info: https://ottelo9.github.io/tasmota-sml-script/sml/
// sml_simple.tc — SML meter readout, no charts, no emulator.
// Smallest program of the family: pick the meter, read it, hand the values
// on via MQTT and HTTP. Charts: sml_chart.tc. Emulator: sml_ct002.tc.
// Docs: https://ottelo9.github.io/tasmota-sml-script/sml/
// Needs ESP32, TinyC in slot 0, USE_SML.
#include "ui_common.tc"
#include "sml_descriptor.tc"
#include "sml_modbus_common.tc"
char sml_vers[] = "28.09.2026";
// ─── Settings ──────────────────────────────────────────────────────────────
persist int sml_sndpwr; // 1 = publish power2 via MQTT
int sml_e_sndpwr; // edit buffer, applied by Save
// ─── Runtime ───────────────────────────────────────────────────────────────
int sml_pwr_last;
char sml_mtopic[64];
byte sml_pub[128];
int do_reset;
// ─── Callbacks ─────────────────────────────────────────────────────────────
void Every100ms() {
mbPoll();
}
void EverySecond() {
uiApplyBegin();
if (tc_opt_first == 1) {
sml_sndpwr = tcOptGet(10, sml_sndpwr);
tcOptSet(10, sml_sndpwr);
}
if (ui_reloading) sml_e_sndpwr = sml_sndpwr;
if (ui_applying) { sml_sndpwr = sml_e_sndpwr; tcOptSet(10, sml_sndpwr); }
sml_descriptor_apply();
mbTick();
if (do_reset) {
tasmCmd("Sensor53 r", sml_buf);
do_reset = 0;
}
// smlPublish: runtime topic, log level 3, reports a changed rc once.
if (tasm_uptime - sml_pwr_last >= 5) {
sml_pwr_last = tasm_uptime;
sml_power2 = (0.9 * sml_power2) + (0.1 * smlGet(1));
if (sml_sndpwr > 0) {
sprintf(sml_ftopic, "stat/%s/script/power2", sml_mtopic);
sprintf(sml_pub, "%.0f", sml_power2);
smlPublish(sml_ftopic, sml_pub);
}
}
}
// /pwr — JSON for a second battery polling over HTTP (opendtu-onbattery DPL)
void WebOn() {
if (webHandler() != 7) return; // /pwr
if (smlVars()) return; // /pwr?v=... single values
smlPwrJson(sml_power2);
strcat(sml_buf, "}");
smlSendJson(sml_buf);
}
void WebCall() {
if (!sml_activ) {
if (ui_de) webSend("{s}SML{m}aus (Rule1){e}");
else webSend("{s}SML{m}off (Rule1){e}");
return;
}
uiSep();
if (ui_de) {
sprintf(sml_buf, "{s}Leistung{m}%.0f W{e}", smlGet(1)); webSend(sml_buf);
sprintf(sml_buf, "{s}Bezug{m}%.3f kWh{e}", smlGet(2)); webSend(sml_buf);
sprintf(sml_buf, "{s}Einspeisung{m}%.3f kWh{e}", sml_export()); webSend(sml_buf);
} else {
sprintf(sml_buf, "{s}Power{m}%.0f W{e}", smlGet(1)); webSend(sml_buf);
sprintf(sml_buf, "{s}Import{m}%.3f kWh{e}", smlGet(2)); webSend(sml_buf);
sprintf(sml_buf, "{s}Export{m}%.3f kWh{e}", sml_export()); webSend(sml_buf);
}
sml_phase_rows();
mbRows();
}
void WebUI() {
if (webPage() == 5) { webSend("<script>location.replace('/tc')</script>"); return; }
if (webPage() != 0) return;
sml_render_settings_panel(); // opens the panel div
uiHead("📡", "Telemetry", "Telemetrie", "sml/#telemetry");
if (ui_de) webCheckbox(sml_e_sndpwr, "power2 senden (MQTT)");
else webCheckbox(sml_e_sndpwr, "Send power2 (MQTT)");
sprintf(sml_buf, "<div class='n'>power2: <b>%.0f W</b></div>", sml_power2);
webSend(sml_buf);
webSend("<b>HTTP API</b>");
webSend("<div class='n'>HTTP: <a href='/pwr' target='_blank'>/pwr</a> <a href='/pwr?v=all' target='_blank'>/pwr?v=all</a> <a href='/pwr?v=sml[1]' target='_blank'>/pwr?v=sml[1]</a></div>");
mbPanel();
webSend("<hr>");
uiSaveButton();
if (ui_de) webButton(do_reset, "SML Neustart|OK");
else webButton(do_reset, "Restart SML|OK");
uiUptime();
sprintf(sml_buf, "sml_simple.tc · Version %s<br>", sml_vers);
webSend(sml_buf);
webSend("by <a href='https://ottelo.jimdofree.com' target='_blank'>ottelo.jimdofree.com</a> & gemu2015 (TinyC <span id='tcv'>...</span>)</div></div>");
webSend("<script>if(window.__tcv){document.getElementById('tcv').textContent=window.__tcv;}else{fetch('/tc_api?cmd=status').then(function(r){return r.json();}).then(function(j){window.__tcv=j.release;var e=document.getElementById('tcv');if(e)e.textContent=j.release;}).catch(function(){});}</script>");
}
int main() {
// -1 keeps the %0?rxpin% placeholder in /sml_meter.def untouched until
// the user picks a pin; 0 is a real GPIO and cannot be the sentinel.
if (sml_rx_pin == 0 && sml_tx_pin == 0) {
sml_rx_pin = -1;
sml_tx_pin = -1;
}
if (sml_activ != tasm_rule) {
tasm_rule = sml_activ;
}
sml_e_sndpwr = sml_sndpwr;
tasmInfo(0, sml_mtopic);
sml_power2 = smlGet(1);
sml_pwr_last = 0;
mbInit();
webPageLabel(0, "⚙️ SML");
webPageLabel(5, "💻 TinyC"); // hands over to /tc - kept last so it stays
// last when a program adds a page
webOn(7, "/pwr");
smlScripterLoad("/sml_meter.def");
addLog("sml_simple: %s started rx=%d tx=%d meter=%d modbus=%d",
sml_vers, sml_rx_pin, sml_tx_pin, sml_meter_sel, mb_on);
return 0;
}