This is an old revision of the document!
Self hosted server that allows you to control many different types of IOT devices.
Open source thermostat.
$ curl -s http://192.168.30.132/cm?cmnd=power|jq { "POWER": "ON" }
$ curl -s http://192.168.30.132/cm?cmnd=status|jq { "Status": { "Module": 41, "DeviceName": "s31-power-2", "FriendlyName": [ "s31-power-2" ], "Topic": "tasmota_07E0AD", "ButtonTopic": "0", "Power": 1, "PowerOnState": 3, "LedState": 1, "LedMask": "FFFF", "SaveData": 1, "SaveState": 1, "SwitchTopic": "0", "SwitchMode": [ 0, 0, 0, 0, 0, 0, 0, 0 ], "ButtonRetain": 0, "SwitchRetain": 0, "SensorRetain": 0, "PowerRetain": 0 } }
$ curl -s http://192.168.30.132/cm?cmnd=Power%20Off {"POWER":"OFF"}
$ curl -s http://192.168.30.132/cm?cmnd=Power%20On {"POWER":"ON"}
$ curl -s http://192.168.30.132/cm?cmnd=FriendlyName%20testing {"FriendlyName1":"testing"}
$ curl -s http://192.168.30.132/cm?cmnd=Status%200|jq { "Status": { "Module": 41, "DeviceName": "s31-power-2", "FriendlyName": [ "testing" ], "Topic": "tasmota_07E0AD", "ButtonTopic": "0", "Power": 1, "PowerOnState": 3, "LedState": 1, "LedMask": "FFFF", "SaveData": 1, "SaveState": 1, "SwitchTopic": "0", "SwitchMode": [ 0, 0, 0, 0, 0, 0, 0, 0 ], "ButtonRetain": 0, "SwitchRetain": 0, "SensorRetain": 0, "PowerRetain": 0 }, "StatusPRM": { "Baudrate": 4800, "SerialConfig": "8E1", "GroupTopic": "tasmotas", "OtaUrl": "http://ota.tasmota.com/tasmota/release/tasmota.bin.gz", "RestartReason": "Power On", "Uptime": "0T00:19:57", "StartupUTC": "2021-01-25T21:33:18", "Sleep": 50, "CfgHolder": 4617, "BootCount": 14, "BCResetTime": "2021-01-15T02:55:01", "SaveCount": 39, "SaveAddress": "F8000" }, "StatusFWR": { "Version": "9.2.0(tasmota)", "BuildDateTime": "2020-12-21T15:03:40", "Boot": 31, "Core": "2_7_4_9", "SDK": "2.2.2-dev(38a443e)", "CpuFrequency": 80, "Hardware": "ESP8266EX", "CR": "400/699" }, "StatusLOG": { "SerialLog": 0, "WebLog": 2, "MqttLog": 2, "SysLog": 0, "LogHost": "", "LogPort": 514, "SSId": [ "PKLAN-IOT", "" ], "TelePeriod": 10, "Resolution": "558180C0", "SetOption": [ "00008009", "2805C8000100068000005A00000000000000", "00000000", "00006000", "00000000" ] }, "StatusMEM": { "ProgramSize": 586, "Free": 416, "Heap": 26, "ProgramFlashSize": 1024, "FlashSize": 4096, "FlashChipId": "1640EF", "FlashFrequency": 40, "FlashMode": 3, "Features": [ "00000809", "8FDAC787", "04368001", "000000CF", "010013C0", "C000F981", "00004004", "00001000" ], "Drivers": "1,2,3,4,5,6,7,8,9,10,12,16,18,19,20,21,22,24,26,27,29,30,35,37,45", "Sensors": "1,2,3,4,5,6" }, "StatusNET": { "Hostname": "tasmota_07E0AD-0173", "IPAddress": "192.168.30.132", "Gateway": "192.168.30.1", "Subnetmask": "255.255.255.0", "DNSServer": "192.168.1.1", "Mac": "C8:2B:96:07:E0:AD", "Webserver": 2, "WifiConfig": 4, "WifiPower": 17 }, "StatusMQT": { "MqttHost": "192.168.20.207", "MqttPort": 1883, "MqttClientMask": "DVES_%06X", "MqttClient": "DVES_07E0AD", "MqttUser": "phil", "MqttCount": 0, "MAX_PACKET_SIZE": 1200, "KEEPALIVE": 30 }, "StatusTIM": { "UTC": "2021-01-25T21:53:15", "Local": "2021-01-25T22:53:15", "StartDST": "2021-03-28T02:00:00", "EndDST": "2021-10-31T03:00:00", "Timezone": "+01:00", "Sunrise": "08:28", "Sunset": "17:36" }, "StatusPTH": { "PowerDelta": [ 0, 0, 0 ], "PowerLow": 0, "PowerHigh": 0, "VoltageLow": 0, "VoltageHigh": 0, "CurrentLow": 0, "CurrentHigh": 0 }, "StatusSNS": { "Time": "2021-01-25T22:53:15", "ENERGY": { "TotalStartTime": "2021-01-15T02:55:01", "Total": 0, "Yesterday": 0, "Today": 0, "Power": 0, "ApparentPower": 0, "ReactivePower": 0, "Factor": 0, "Voltage": 118, "Current": 0 } }, "StatusSTS": { "Time": "2021-01-25T22:53:15", "Uptime": "0T00:19:57", "UptimeSec": 1197, "Heap": 26, "SleepMode": "Dynamic", "Sleep": 50, "LoadAvg": 19, "MqttCount": 0, "POWER": "ON", "Wifi": { "AP": 1, "SSId": "PKLAN-IOT", "BSSId": "02:9F:C2:77:2D:37", "Channel": 11, "RSSI": 100, "Signal": -43, "LinkCount": 1, "Downtime": "0T00:00:03" } } }
$ curl -s http://192.168.30.136/cm?cmnd=Status%208|jq { "StatusSNS": { "Time": "2021-01-25T22:56:32", "ENERGY": { "TotalStartTime": "2021-01-15T00:21:03", "Total": 48.545, "Yesterday": 4.492, "Today": 5.049, "Power": 380, "ApparentPower": 384, "ReactivePower": 56, "Factor": 0.99, "Voltage": 120, "Current": 3.2 } } }
Just in case I'm saving this.
import requests import json class s31powerplug: def __init__(self, target): self.target = target def cmd(self, cmd): r = requests.get('http://%s/cm?cmnd=%s' % (self.target, cmd)) if r.status_code == 200: return r.json() else: return False def energy(self): r = self.cmd('Status%208') return r['StatusSNS']['ENERGY'] def network(self): r = self.cmd('Status%205') return r['StatusNET'] def power(self, toggle=None): if toggle is None: return self.cmd('power') elif toggle: return self.cmd('power%20On') else: return self.cmd('power%20Off')
if you enable websocket interface on mosquitto, you can also talk to it from a browser using javascript, which is kinda handy. Here's the lib I've used for that → https://github.com/mqttjs/MQTT.js (basically it just encapsulates the normal MQTT protocol inside a websocket connection so that it can be used directly in a browser – it's handy for things like dashboards that update in real-time)
I've just used curl with the API so far (from shell scripts) well, plus the MQTT interface via. a python library this one → https://pypi.org/project/paho-mqtt/
that's an example of a simple MQTT frontend – I kinda cheated by doing some server-side stuff in PHP, so I provided both the PHP source as well as the final HTML it generates I wrote that back in 2016, but I think it should still work with modern servers/libraries lol some of this code is kinda gross, but at least it gives you the general idea
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>MCP Relay Power Control</title> <link rel="shortcut icon" href="../favicon-power.png" type="image/png" /> <meta name="viewport" content="initial-scale = 0.5, maximum-scale = 1.0, minimum-scale = 0.5" /> <style type="text/css">@import "style.css";</style> </head> <body> <script src="../mqttws31.js" type="text/javascript"></script> <script type="text/javascript"> mqtt_topic = "power/relay"; function mqtt_connect() { console.log("MQTT connecting..."); client = new Paho.MQTT.Client('mqtt.mcp.lcl', 8000, "Web interface for computer power control from 192.168.48.217 started at Mon, 25 Jan 2021 18:45:22 -0600"); client.onConnectionLost = on_mqtt_connection_lost; client.onMessageArrived = on_mqtt_message; client.connect({onSuccess:on_mqtt_connect}); } mqtt_connect(); function on_mqtt_connect() { console.log("MQTT connected."); client.subscribe(mqtt_topic + "/#"); }; function on_mqtt_connection_lost(response) { if (response.errorCode !== 0) { console.log("MQTT connection lost: " + response.errorMessage); } update_online(false); console.log("MQTT attempting reconnect..."); mqtt_connect(); }; function on_mqtt_message(msg) { console.log("MQTT received: " + msg.destinationName + " => " + msg.payloadString); if (msg.destinationName.indexOf(mqtt_topic) == 0) { var short_topic = msg.destinationName.substr(mqtt_topic.length + 1); if (short_topic == "status") { switch (msg.payloadString) { case "online": update_online(true); break; case "offline": update_online(false); break; default: console.log("WARNING: Invalid status received: " + msg.payloadString); } } else { var element = document.getElementById(short_topic); if (element) { update_element(short_topic, msg.payloadString, element); } else { console.log("INFO: Ignored MQTT message for unlisted computer: " + short_topic + " => " + msg.payloadString); } } } else { console.log("WARNING: Unrecognized MQTT message received: " + short_topic + " => " + msg.payloadString); } //client.disconnect(); }; function send_mqtt(device, state) { if (document.getElementById("relays").className == "online") { if (state == "off" && ! confirm("Are you sure you want to power off " + device + "?")) { return; } if (state == "on" && ! confirm("Are you sure you want to power on " + device + "?")) { return; } console.log("Sending MQTT: " + device + " => " + state); var msg = new Paho.MQTT.Message(String(state)); msg.destinationName = mqtt_topic + "/" + String(device); msg.qos = 1; msg.retained = true; client.send(msg); } else { console.log("Ignoring command while relay power controller is offline"); } }; function update_online(is_online) { if (is_online) { console.log("Marking display online"); document.getElementById("relays").className = "online"; } else { console.log("Marking display offline"); document.getElementById("relays").className = "offline"; } }; function update_element(device, status, element) { console.log("Marking status of device " + device + " as " + status); switch (status) { case "0": element.className = "off"; break; case "1": element.className = "on"; break; case "off": case "on": element.className = "pending"; break; default: element.className = ""; } }; function toggle_device(device) { state = document.getElementById(device).className; switch (state) { case "off": return send_mqtt(device, "on"); case "on": return send_mqtt(device, "off"); default: console.log("Not known how to toggle state " + state + " on device " + device); } } </script> <h1>MCP Relay Power Control</h1> <div id="relays" class="offline"> <div class="buttonrow"><div class="button"><a id="soffit_n" href="javascript:toggle_device('soffit_n');">soffit_n</a></div><div class="button"><a id="soffit_s" href="javascript:toggle_device('soffit_s');">soffit_s</a></div><div class="button"><a id="vest_r" href="javascript:toggle_device('vest_r');">vest_r</a></div><div class="button"><a id="vest_w" href="javascript:toggle_device('vest_w');">vest_w</a></div><div class="button"><a id="console" href="javascript:toggle_device('console');">console</a></div><div class="button"><a id="cat_main" href="javascript:toggle_device('cat_main');">cat_main</a></div><div class="button"><a id="cat_rope" href="javascript:toggle_device('cat_rope');">cat_rope</a></div><div class="button"><a id="inuse" href="javascript:toggle_device('inuse');">inuse</a></div></div><div class="buttonrow"><div class="button"><a id="ds-proj-a" href="javascript:toggle_device('ds-proj-a');">ds-proj-a</a></div><div class="button"><a id="ds-proj-b" href="javascript:toggle_device('ds-proj-b');">ds-proj-b</a></div><div class="button"><a id="ds-proj-c" href="javascript:toggle_device('ds-proj-c');">ds-proj-c</a></div><div class="button"><a id="ds-proj-d" href="javascript:toggle_device('ds-proj-d');">ds-proj-d</a></div><div class="button"><a id="ds-proj-e" href="javascript:toggle_device('ds-proj-e');">ds-proj-e</a></div><div class="button"><a id="ds-proj-f" href="javascript:toggle_device('ds-proj-f');">ds-proj-f</a></div><div class="button"><a id="giftshop" href="javascript:toggle_device('giftshop');">giftshop</a></div><div class="button"><a id="waiting-sound" href="javascript:toggle_device('waiting-sound');">waiting-sound</a></div></div><div class="buttonrow"><div class="button"><a id="waiting" href="javascript:toggle_device('waiting');">waiting</a></div><div class="button"><a id="office-sound" href="javascript:toggle_device('office-sound');">office-sound</a></div><div class="button"><a id="epson-proj" href="javascript:toggle_device('epson-proj');">epson-proj</a></div><div class="button"><a id="waiting-proj" href="javascript:toggle_device('waiting-proj');">waiting-proj</a></div></div></div> </body> </html>
<?php require 'power.inc.php'; _read_devices_csv(); ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>MCP Relay Power Control</title> <link rel="shortcut icon" href="../favicon-power.png" type="image/png" /> <meta name="viewport" content="initial-scale = 0.5, maximum-scale = 1.0, minimum-scale = 0.5" /> <style type="text/css">@import "style.css";</style> </head> <body> <script src="../mqttws31.js" type="text/javascript"></script> <script type="text/javascript"> mqtt_topic = "<?php echo MQTT_TOPIC; ?>"; function mqtt_connect() { console.log("MQTT connecting..."); client = new Paho.MQTT.Client('mqtt.mcp.lcl', 8000, "Web interface for computer power control from <?php echo $_SERVER['REMOTE_ADDR'] ?> started at <?php echo date('r'); ?>"); client.onConnectionLost = on_mqtt_connection_lost; client.onMessageArrived = on_mqtt_message; client.connect({onSuccess:on_mqtt_connect}); } mqtt_connect(); function on_mqtt_connect() { console.log("MQTT connected."); client.subscribe(mqtt_topic + "/#"); }; function on_mqtt_connection_lost(response) { if (response.errorCode !== 0) { console.log("MQTT connection lost: " + response.errorMessage); } update_online(false); console.log("MQTT attempting reconnect..."); mqtt_connect(); }; function on_mqtt_message(msg) { console.log("MQTT received: " + msg.destinationName + " => " + msg.payloadString); if (msg.destinationName.indexOf(mqtt_topic) == 0) { var short_topic = msg.destinationName.substr(mqtt_topic.length + 1); if (short_topic == "status") { switch (msg.payloadString) { case "online": update_online(true); break; case "offline": update_online(false); break; default: console.log("WARNING: Invalid status received: " + msg.payloadString); } } else { var element = document.getElementById(short_topic); if (element) { update_element(short_topic, msg.payloadString, element); } else { console.log("INFO: Ignored MQTT message for unlisted computer: " + short_topic + " => " + msg.payloadString); } } } else { console.log("WARNING: Unrecognized MQTT message received: " + short_topic + " => " + msg.payloadString); } //client.disconnect(); }; function send_mqtt(device, state) { if (document.getElementById("relays").className == "online") { if (state == "off" && ! confirm("Are you sure you want to power off " + device + "?")) { return; } if (state == "on" && ! confirm("Are you sure you want to power on " + device + "?")) { return; } console.log("Sending MQTT: " + device + " => " + state); var msg = new Paho.MQTT.Message(String(state)); msg.destinationName = mqtt_topic + "/" + String(device); msg.qos = 1; msg.retained = true; client.send(msg); } else { console.log("Ignoring command while relay power controller is offline"); } }; function update_online(is_online) { if (is_online) { console.log("Marking display online"); document.getElementById("relays").className = "online"; } else { console.log("Marking display offline"); document.getElementById("relays").className = "offline"; } }; function update_element(device, status, element) { console.log("Marking status of device " + device + " as " + status); switch (status) { case "0": element.className = "off"; break; case "1": element.className = "on"; break; case "off": case "on": element.className = "pending"; break; default: element.className = ""; } }; function toggle_device(device) { state = document.getElementById(device).className; switch (state) { case "off": return send_mqtt(device, "on"); case "on": return send_mqtt(device, "off"); default: console.log("Not known how to toggle state " + state + " on device " + device); } } </script> <h1>MCP Relay Power Control</h1> <div id="relays" class="offline"> <?php function print_device($device, $params) { print '<div class="button"><a id="'. $device .'" href="javascript:toggle_device(\''. $device .'\');">'. $device .'</a></div>'; } print '<div class="buttonrow">'; $i = 0; foreach ($devices as $device => $params) { print_device($device, $params); if (++$i == 8) { print '</div><div class="buttonrow">'; $i = 0; } } print '</div>'; ?></div> </body> </html>