Browse Source

Moved web interface to PureCSS

fastled
Xose Pérez 8 years ago
parent
commit
10fb326667
37 changed files with 1218 additions and 427 deletions
  1. +8
    -8
      README.md
  2. +8
    -0
      code/build_data
  3. BIN
      code/data/checkboxes-min.css.gz
  4. BIN
      code/data/checkboxes-min.js.gz
  5. +1
    -1
      code/data/fsversion
  6. BIN
      code/data/grids-responsive-min.css.gz
  7. BIN
      code/data/images/off.png
  8. BIN
      code/data/images/on.png
  9. BIN
      code/data/images/slider.png
  10. BIN
      code/data/images/slider_center.png
  11. BIN
      code/data/images/slider_left.png
  12. BIN
      code/data/images/slider_right.png
  13. +0
    -392
      code/data/index.html
  14. BIN
      code/data/index.html.gz
  15. BIN
      code/data/jquery-1.12.3.min.js.gz
  16. BIN
      code/data/pure-min.css.gz
  17. BIN
      code/data/side-menu-min.css.gz
  18. BIN
      code/data/spectre.min.css.gz
  19. +1
    -0
      code/html/checkboxes-min.css
  20. +1
    -0
      code/html/checkboxes-min.js
  21. +73
    -0
      code/html/checkboxes.css
  22. +350
    -0
      code/html/checkboxes.js
  23. BIN
      code/html/favicon.ico
  24. +1
    -0
      code/html/grids-responsive-min.css
  25. BIN
      code/html/images/off.png
  26. BIN
      code/html/images/on.png
  27. BIN
      code/html/images/slider.png
  28. BIN
      code/html/images/slider_center.png
  29. BIN
      code/html/images/slider_left.png
  30. BIN
      code/html/images/slider_right.png
  31. +495
    -0
      code/html/index.html
  32. +5
    -0
      code/html/jquery-1.12.3.min.js
  33. +1
    -0
      code/html/pure-min.css
  34. +1
    -0
      code/html/side-menu-min.css
  35. +248
    -0
      code/html/side-menu.css
  36. +1
    -1
      code/src/defaults.h
  37. +24
    -25
      code/src/webserver.ino

+ 8
- 8
README.md View File

@ -28,7 +28,7 @@ You can read about this board and firmware in [my blog][2].
* Support for **automatic over-the-air updates** through the [NoFUSS Library][6] * Support for **automatic over-the-air updates** through the [NoFUSS Library][6]
* Support for **current monitoring** through then [EmonLiteESP Library][7] * Support for **current monitoring** through then [EmonLiteESP Library][7]
* Support for **DHT22** sensors * Support for **DHT22** sensors
* Command line configuration
## Flashing ## Flashing
@ -46,27 +46,27 @@ The project is ready to be build using [PlatformIO][3].
Please refer to their web page for instructions on how to install the builder. Once installed: Please refer to their web page for instructions on how to install the builder. Once installed:
```bash ```bash
> platformio run --target upload -e wire
> platformio run --target uploadfs -e wire
> platformio run --target upload -e node-debug
> platformio run --target uploadfs -e node-debug
``` ```
Once you have flashed it you can flash it again over-the-air using the ```ota``` environment: Once you have flashed it you can flash it again over-the-air using the ```ota``` environment:
```bash ```bash
> platformio run --target upload -e ota
> platformio run --target uploadfs -e ota
> platformio run --target upload -e node-debug-ota
> platformio run --target uploadfs -e node-debug-ota
``` ```
When using OTA environment it defaults to the IP address of the device in SoftAP mode. If you want to flash it when connected to your home network best way is to supply the IP of the device: When using OTA environment it defaults to the IP address of the device in SoftAP mode. If you want to flash it when connected to your home network best way is to supply the IP of the device:
```bash ```bash
> platformio run --target upload -e ota --upload-port 192.168.1.151
> platformio run --target uploadfs -e ota --upload-port 192.168.1.151
> platformio run --target upload -e node-debug-ota --upload-port 192.168.1.151
> platformio run --target uploadfs -e node-debug-ota --upload-port 192.168.1.151
``` ```
You can also use the automatic OTA update feature. Check the [NoFUSS library][6] for more info. You can also use the automatic OTA update feature. Check the [NoFUSS library][6] for more info.
Library dependencies are automatically managed via PlatformIO Library Manager.
Library dependencies are automatically managed via PlatformIO Library Manager or included via submodules and linked from the "lib" folder.
## Usage ## Usage


+ 8
- 0
code/build_data View File

@ -0,0 +1,8 @@
#!/bin/bash
rm -rf data/*.gz
cp html/*min.css data/
cp html/*min.js data/
cp html/index.html data/
gzip data/*.js
gzip data/*.css
gzip data/*.html

BIN
code/data/checkboxes-min.css.gz View File


BIN
code/data/checkboxes-min.js.gz View File


+ 1
- 1
code/data/fsversion View File

@ -1 +1 @@
0.9.5
0.9.7

BIN
code/data/grids-responsive-min.css.gz View File


BIN
code/data/images/off.png View File

Before After
Width: 289  |  Height: 27  |  Size: 2.5 KiB

BIN
code/data/images/on.png View File

Before After
Width: 289  |  Height: 27  |  Size: 2.4 KiB

BIN
code/data/images/slider.png View File

Before After
Width: 39  |  Height: 27  |  Size: 1.2 KiB

BIN
code/data/images/slider_center.png View File

Before After
Width: 4  |  Height: 27  |  Size: 260 B

BIN
code/data/images/slider_left.png View File

Before After
Width: 4  |  Height: 27  |  Size: 324 B

BIN
code/data/images/slider_right.png View File

Before After
Width: 4  |  Height: 27  |  Size: 321 B

+ 0
- 392
code/data/index.html View File

@ -1,392 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>ESPurna</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="spectre.min.css" />
<script src="jquery-1.12.3.min.js"></script>
<style>
html, body {
font-size: 12px;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
div.page {
display: none;
}
.bg-grey {
background-color: #efefef;
padding: 1rem;
border-radius: .3rem;
}
div.hint {
background-color: #efefef;
padding: 2rem;
margin: 20px 0;
border-left: 2px solid blue;
}
</style>
<script>
$(function() {
var timer = null;
$("form").submit(function(event) {
button = $(":submit", this);
button.addClass("loading");
$.ajax({
'method': 'POST',
'url': '/save',
'dataType': 'json',
'data': $(this).serializeArray()
}).done(function(data) {
button.removeClass("loading");
}).fail(function() {
button.removeClass("loading");
});
event.preventDefault();
setTimeout(update, 200);
});
function init() {
$.ajax({
'method': 'GET',
'url': '/init',
'dataType': 'json'
}).done(function(data) {
keys = Object.keys(data);
for (index in keys) {
key = "#" + keys[index];
value = data[keys[index]];
try {
if ($(key).prop('tagName') == 'INPUT') {
$(key).val(value);
} else {
$(key).html(value);
}
} catch(err) {
// nope
};
};
timer = setInterval(update, data.updateInterval);
document.title = data.hostname;
$("[name='pwMainsVoltage']").val(data.pwMainsVoltage);
$("[name='rfDevice']").val(data.rfDevice);
});
}
function update() {
$.ajax({
'method': 'GET',
'url': '/status',
'dataType': 'json'
}).done(function(data) {
$("#mqtt").val(data.mqtt ? "CONNECTED" : "NOT CONNECTED");
$("#power").val((data.power | 0) + "W");
$("#temperature").val((data.temperature | 0) + "ºC");
$("#humidity").val((data.humidity | 0) + "%");
if (data.relay) {
$("#relay").addClass('btn-primary').html("ON");
$("#status").val(1);
} else {
$("#relay").removeClass('btn-primary').html("OFF");
$("#status").val(0);
}
});
}
$("#btn-admin").click(function() {
$("#panel-status").hide();
$("#panel-admin").show();
$("#btn-admin").addClass('btn-primary');
$("#btn-status").removeClass('btn-primary');
});
$("#btn-status").click(function() {
$("#panel-admin").hide();
$("#panel-status").show();
$("#btn-admin").removeClass('btn-primary');
$("#btn-status").addClass('btn-primary');
});
$("#relay").click(function(event) {
var status = parseInt($("#status").val());
if (status == 1) {
$.ajax({'method': 'GET', 'url': '/relay/off'});
} else {
$.ajax({'method': 'GET', 'url': '/relay/on'});
}
setTimeout(update, 200);
event.preventDefault();
})
init();
update();
});
</script>
</head>
<body>
<div class="container">
<header class="navbar bg-grey">
<section class="navbar-section">
<a href="#" class="navbar-brand"><span id="appname"></span></a>
</section>
<section class="navbar-section">
<button class="btn" id="btn-admin">Administration</button>
<button class="btn btn-primary" id="btn-status">Status</button>
</section>
</header>
<div class="page" id="panel-status" style="display: block;">
<div class="columns">
<div class="column col-12">
<form class="form-horizontal">
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="manufacturer">Manufacturer</label>
</div>
<div class="col-xs-9">
<input type="text" class="form-input" id="manufacturer" value="" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="device">Device</label>
</div>
<div class="col-xs-9">
<input type="text" class="form-input" id="device" value="" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="hostname">Hostname</label>
</div>
<div class="col-xs-9">
<input type="text" class="form-input" id="hostname" value="" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="network">Network</label>
</div>
<div class="col-xs-9">
<input type="text" class="form-input" id="network" value="" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="ip">IP</label>
</div>
<div class="col-xs-9">
<input type="text" class="form-input" id="ip" value="" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="mqtt">MQTT Status</label>
</div>
<div class="col-xs-9">
<input type="text" class="form-input" id="mqtt" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="power">Power</label>
</div>
<div class="col-xs-6">
<input type="text" class="form-input" id="power" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="temperature">Temperature</label>
</div>
<div class="col-xs-6">
<input type="text" class="form-input" id="temperature" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="humidity">Humidity</label>
</div>
<div class="col-xs-6">
<input type="text" class="form-input" id="humidity" disabled>
</div>
</div>
<div class="form-group">
<div class="col-xs-3">
<label class="form-label" for="relay">Relay Status</label>
</div>
<div class="col-xs-6">
<input type="hidden" class="form-input" name="status" id="status" value="0" />
<a class="btn" data="0" id="relay">OFF</a>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="page" id="panel-admin">
<form action="/" method="post">
<div class="hint">
You can configure up to 3 different WiFi networks. The device will try to connect to any of them starting with the first one.
</div>
<div class="columns">
<div class="column col-4">
<h5>Network 1</h5>
<div class="form-group">
<label class="form-label" for="ssid0">Network SSID</label>
<input name="ssid0" id="ssid0" type="text" class="form-input" size="8" tabindex="1" placeholder="Network SSID">
</div>
<div>
<label class="form-label" for="pass0">Network Password</label>
<input name="pass0" id="pass0" type="text" class="form-input" maxlength="255" tabindex="2" placeholder="Network password">
</div>
</div>
<div class="column col-4">
<h5>Network 2</h5>
<div class="form-group">
<label class="form-label" for="ssid1">Network SSID</label>
<input name="ssid1" id="ssid1" type="text" class="form-input" size="8" tabindex="3" placeholder="Network SSID">
</div>
<div>
<label class="form-label" for="pass1">Network Password</label>
<input name="pass1" id="pass1" type="text" class="form-input" maxlength="255" tabindex="4" placeholder="Network password">
</div>
</div>
<div class="column col-4">
<h5>Network 3</h5>
<div class="form-group">
<label class="form-label" for="ssid2">Network SSID</label>
<input name="ssid2" id="ssid2" type="text" class="form-input" size="8" tabindex="5" placeholder="Network SSID">
</div>
<div>
<label class="form-label" for="pass2">Network Password</label>
<input name="pass2" id="pass2" type="text" class="form-input" maxlength="255" tabindex="6" placeholder="Network password">
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="hint">
Configure an <strong>MQTT broker</strong> in your network and you will be able to change the switch status via an MQTT message. Just send a 0 or a 1 as a payload to the provided topic below.
The switch will also report its current open/close status to the same topic and its IP address to the topic you define plus "<code>/ip</code>". Leave the server field empty to disable MQTT.
</div>
<div class="columns">
<div class="column col-4">
<div class="form-group">
<label class="form-label" for="mqttServer">MQTT Server</label>
<input name="mqttServer" id="mqttServer" type="text" class="form-input" size="8" tabindex="8" placeholder="MQTT Server">
</div>
</div>
<div class="column col-2">
<div class="form-group">
<label class="form-label" for="mqttPort">MQTT Port</label>
<input name="mqttPort" id="mqttPort" type="text" class="form-input" size="8" tabindex="9" placeholder="1883">
</div>
</div>
</div>
<div class="columns">
<div class="column col-4">
<div class="form-group">
<label class="form-label" for="mqttUser">MQTT User</label>
<input name="mqttUser" id="mqttUser" type="text" class="form-input" size="8" tabindex="10" placeholder="Leave blank if no user/pass">
</div>
</div>
<div class="column col-4">
<div class="form-group">
<label class="form-label" for="mqttPassword">MQTT Password</label>
<input name="mqttPassword" id="mqttPassword" type="text" class="form-input" size="8" tabindex="11" placeholder="Leave blank if no user/pass">
</div>
</div>
</div>
<div class="columns">
<div class="column col-4">
<div class="form-group">
<label class="form-label" for="mqttTopic">MQTT Topic</label>
<input name="mqttTopic" id="mqttTopic" type="text" class="form-input" size="8" tabindex="12" maxlength="35" placeholder="MQTT Topic">
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="hint">
If your device supports RF switching you can configure here the channel and device ID.
</div>
<div class="columns">
<div class="column col-2">
<div class="form-group">
<label class="form-label" for="rfChannel">RF Channel</label>
<input name="rfChannel" id="rfChannel" type="number" min="0" max="31" step="1" class="form-input" tabindex="13"/>
</div>
</div>
<div class="column col-2">
<div class="form-group">
<label class="form-label" for="rfDevice">RF Device</label>
<select name="rfDevice" class="form-input" tabindex="14">
<option value="0">A</a>
<option value="1">B</a>
<option value="2">C</a>
<option value="3">D</a>
<option value="4">E</a>
</select>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="hint">
If your device supports power measurement, here you can configure line potential and current ratio.
</div>
<div class="columns">
<div class="column col-2">
<div class="form-group">
<label class="form-label" for="pwMainsVoltage">AC RMS Voltage</label>
<select name="pwMainsVoltage" class="form-input" tabindex="15">
<option value="125">125</a>
<option value="220">220</a>
<option value="230">230</a>
<option value="240">240</a>
</select>
</div>
</div>
<div class="column col-2">
<div class="form-group">
<label class="form-label" for="pwCurrentRatio">Current ratio</label>
<input name="pwCurrentRatio" id="pwCurrentRatio" type="text" class="form-input" size="8" tabindex="16" placeholder="30">
</div>
</div>
</div>
<div>
<div>
<button class="btn btn-primary float-right">Update</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>

BIN
code/data/index.html.gz View File


BIN
code/data/jquery-1.12.3.min.js.gz View File


BIN
code/data/pure-min.css.gz View File


BIN
code/data/side-menu-min.css.gz View File


BIN
code/data/spectre.min.css.gz View File


+ 1
- 0
code/html/checkboxes-min.css View File

@ -0,0 +1 @@
.iPhoneCheckContainer{-webkit-transform:translate3d(0,0,0);position:relative;height:27px;cursor:pointer;overflow:hidden}.iPhoneCheckContainer input{position:absolute;top:5px;left:30px;filter:alpha(Opacity=0);opacity:0}.iPhoneCheckContainer label,.iPhoneCheckHandle{display:block;height:27px;cursor:pointer;position:absolute;top:0}.iPhoneCheckContainer label{white-space:nowrap;font-size:17px;line-height:17px;font-weight:700;font-family:"Helvetica Neue",Arial,Helvetica,sans-serif;width:auto;padding-top:5px;overflow:hidden}.iPhoneCheckContainer,.iPhoneCheckContainer label{user-select:none;-moz-user-select:none;-khtml-user-select:none}.iPhoneCheckDisabled{filter:alpha(Opacity=50);opacity:.5}label.iPhoneCheckLabelOn{color:#fff;background:url(images/on.png) no-repeat;text-shadow:0 0 2px rgba(0,0,0,.6);left:0;padding-top:5px}label.iPhoneCheckLabelOn span{padding-left:8px}label.iPhoneCheckLabelOff{color:#8b8b8b;background:url(images/off.png) right 0 no-repeat;text-shadow:0 0 2px rgba(255,255,255,.6);text-align:right;right:0}label.iPhoneCheckLabelOff span{padding-right:8px}.iPhoneCheckHandle{left:0;width:0;background:url(images/slider_left.png) no-repeat;padding-left:3px}.iPhoneCheckHandleRight{height:100%;width:100%;padding-right:3px;background:url(images/slider_right.png) right 0 no-repeat}.iPhoneCheckHandleCenter{height:100%;width:100%;background:url(images/slider_center.png)}

+ 1
- 0
code/html/checkboxes-min.js
File diff suppressed because it is too large
View File


+ 73
- 0
code/html/checkboxes.css View File

@ -0,0 +1,73 @@
.iPhoneCheckContainer {
-webkit-transform:translate3d(0,0,0);
position: relative;
height: 27px;
cursor: pointer;
overflow: hidden; }
.iPhoneCheckContainer input {
position: absolute;
top: 5px;
left: 30px;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0; }
.iPhoneCheckContainer label {
white-space: nowrap;
font-size: 17px;
line-height: 17px;
font-weight: bold;
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
cursor: pointer;
display: block;
height: 27px;
position: absolute;
width: auto;
top: 0;
padding-top: 5px;
overflow: hidden; }
.iPhoneCheckContainer, .iPhoneCheckContainer label {
user-select: none;
-moz-user-select: none;
-khtml-user-select: none; }
.iPhoneCheckDisabled {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
opacity: 0.5; }
label.iPhoneCheckLabelOn {
color: white;
background: url('images/on.png') no-repeat;
text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.6);
left: 0;
padding-top: 5px; }
label.iPhoneCheckLabelOn span {
padding-left: 8px; }
label.iPhoneCheckLabelOff {
color: #8b8b8b;
background: url('images/off.png') no-repeat right 0;
text-shadow: 0px 0px 2px rgba(255, 255, 255, 0.6);
text-align: right;
right: 0; }
label.iPhoneCheckLabelOff span {
padding-right: 8px; }
.iPhoneCheckHandle {
display: block;
height: 27px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
width: 0;
background: url('images/slider_left.png') no-repeat;
padding-left: 3px; }
.iPhoneCheckHandleRight {
height: 100%;
width: 100%;
padding-right: 3px;
background: url('images/slider_right.png') no-repeat right 0; }
.iPhoneCheckHandleCenter {
height: 100%;
width: 100%;
background: url('images/slider_center.png'); }

+ 350
- 0
code/html/checkboxes.js View File

@ -0,0 +1,350 @@
// Generated by CoffeeScript 1.6.2
(function() {
var iOSCheckbox, matched, userAgent,
__slice = [].slice;
if ($.browser == null) {
userAgent = navigator.userAgent || "";
jQuery.uaMatch = function(ua) {
var match;
ua = ua.toLowerCase();
match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+))?/.exec(ua) || [];
return {
browser: match[1] || "",
version: match[2] || "0"
};
};
matched = jQuery.uaMatch(userAgent);
jQuery.browser = {};
if (matched.browser) {
jQuery.browser[matched.browser] = true;
jQuery.browser.version = matched.version;
}
if (jQuery.browser.webkit) {
jQuery.browser.safari = true;
}
}
iOSCheckbox = (function() {
function iOSCheckbox(elem, options) {
var key, opts, value;
this.elem = $(elem);
opts = $.extend({}, iOSCheckbox.defaults, options);
for (key in opts) {
value = opts[key];
this[key] = value;
}
this.elem.data(this.dataName, this);
this.wrapCheckboxWithDivs();
this.attachEvents();
this.disableTextSelection();
this.calculateDimensions();
}
iOSCheckbox.prototype.calculateDimensions = function() {
if (this.resizeHandle) {
this.optionallyResize('handle');
}
if (this.resizeContainer) {
this.optionallyResize('container');
}
return this.initialPosition();
};
iOSCheckbox.prototype.isDisabled = function() {
return this.elem.is(':disabled');
};
iOSCheckbox.prototype.wrapCheckboxWithDivs = function() {
this.elem.wrap("<div class='" + this.containerClass + "' />");
this.container = this.elem.parent();
this.offLabel = $("<label class='" + this.labelOffClass + "'>\n <span>" + this.uncheckedLabel + "</span>\n</label>").appendTo(this.container);
this.offSpan = this.offLabel.children('span');
this.onLabel = $("<label class='" + this.labelOnClass + "'>\n <span>" + this.checkedLabel + "</span>\n</label>").appendTo(this.container);
this.onSpan = this.onLabel.children('span');
return this.handle = $("<div class='" + this.handleClass + "'>\n <div class='" + this.handleRightClass + "'>\n <div class='" + this.handleCenterClass + "' />\n </div>\n</div>").appendTo(this.container);
};
iOSCheckbox.prototype.disableTextSelection = function() {
if ($.browser.msie) {
return $([this.handle, this.offLabel, this.onLabel, this.container]).attr("unselectable", "on");
}
};
iOSCheckbox.prototype._getDimension = function(elem, dimension) {
if ($.fn.actual != null) {
return elem.actual(dimension);
} else {
return elem[dimension]();
}
};
iOSCheckbox.prototype.optionallyResize = function(mode) {
var newWidth, offLabelWidth, offSpan, onLabelWidth, onSpan;
onSpan = this.onLabel.find('span');
onLabelWidth = this._getDimension(onSpan, "width");
onLabelWidth += parseInt(onSpan.css('padding-left'), 10);
offSpan = this.offLabel.find('span');
offLabelWidth = this._getDimension(offSpan, "width");
offLabelWidth += parseInt(offSpan.css('padding-right'), 10);
if (mode === "container") {
newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
newWidth += this._getDimension(this.handle, "width") + this.handleMargin;
return this.container.css({
width: newWidth
});
} else {
newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
return this.handle.css({
width: newWidth
});
}
};
iOSCheckbox.prototype.onMouseDown = function(event) {
var x;
event.preventDefault();
if (this.isDisabled()) {
return;
}
x = event.pageX || event.originalEvent.changedTouches[0].pageX;
iOSCheckbox.currentlyClicking = this.handle;
iOSCheckbox.dragStartPosition = x;
return iOSCheckbox.handleLeftOffset = parseInt(this.handle.css('left'), 10) || 0;
};
iOSCheckbox.prototype.onDragMove = function(event, x) {
var newWidth, p;
if (iOSCheckbox.currentlyClicking !== this.handle) {
return;
}
p = (x + iOSCheckbox.handleLeftOffset - iOSCheckbox.dragStartPosition) / this.rightSide;
if (p < 0) {
p = 0;
}
if (p > 1) {
p = 1;
}
newWidth = p * this.rightSide;
this.handle.css({
left: newWidth
});
this.onLabel.css({
width: newWidth + this.handleRadius
});
this.offSpan.css({
marginRight: -newWidth
});
return this.onSpan.css({
marginLeft: -(1 - p) * this.rightSide
});
};
iOSCheckbox.prototype.onDragEnd = function(event, x) {
var p;
if (iOSCheckbox.currentlyClicking !== this.handle) {
return;
}
if (this.isDisabled()) {
return;
}
if (iOSCheckbox.dragging) {
p = (x - iOSCheckbox.dragStartPosition) / this.rightSide;
this.elem.prop('checked', p >= 0.5).change();
} else {
this.elem.prop('checked', !this.elem.prop('checked')).change();
}
iOSCheckbox.currentlyClicking = null;
iOSCheckbox.dragging = null;
if (typeof this.onChange === "function") {
this.onChange(this.elem, this.elem.prop('checked'));
}
return this.didChange();
};
iOSCheckbox.prototype.refresh = function() {
return this.didChange();
};
iOSCheckbox.prototype.didChange = function() {
var new_left;
if (this.isDisabled()) {
this.container.addClass(this.disabledClass);
return false;
} else {
this.container.removeClass(this.disabledClass);
}
new_left = this.elem.prop('checked') ? this.rightSide : 0;
this.handle.animate({
left: new_left
}, this.duration);
this.onLabel.animate({
width: new_left + this.handleRadius
}, this.duration);
this.offSpan.animate({
marginRight: -new_left
}, this.duration);
return this.onSpan.animate({
marginLeft: new_left - this.rightSide
}, this.duration);
};
iOSCheckbox.prototype.attachEvents = function() {
var localMouseMove, localMouseUp, self;
self = this;
localMouseMove = function(event) {
return self.onGlobalMove.apply(self, arguments);
};
localMouseUp = function(event) {
self.onGlobalUp.apply(self, arguments);
$(document).unbind('mousemove touchmove', localMouseMove);
return $(document).unbind('mouseup touchend', localMouseUp);
};
this.elem.change(function() {
return self.refresh();
});
return this.container.bind('mousedown touchstart', function(event) {
self.onMouseDown.apply(self, arguments);
$(document).bind('mousemove touchmove', localMouseMove);
return $(document).bind('mouseup touchend', localMouseUp);
});
};
iOSCheckbox.prototype.initialPosition = function() {
var containerWidth, offset;
containerWidth = this._getDimension(this.container, "width");
this.offLabel.css({
width: containerWidth - this.containerRadius
});
offset = this.containerRadius + 1;
if ($.browser.msie && $.browser.version < 7) {
offset -= 3;
}
this.rightSide = containerWidth - this._getDimension(this.handle, "width") - offset;
if (this.elem.is(':checked')) {
this.handle.css({
left: this.rightSide
});
this.onLabel.css({
width: this.rightSide + this.handleRadius
});
this.offSpan.css({
marginRight: -this.rightSide
});
} else {
this.onLabel.css({
width: 0
});
this.onSpan.css({
marginLeft: -this.rightSide
});
}
if (this.isDisabled()) {
return this.container.addClass(this.disabledClass);
}
};
iOSCheckbox.prototype.onGlobalMove = function(event) {
var x;
if (!(!this.isDisabled() && iOSCheckbox.currentlyClicking)) {
return;
}
event.preventDefault();
x = event.pageX || event.originalEvent.changedTouches[0].pageX;
if (!iOSCheckbox.dragging && (Math.abs(iOSCheckbox.dragStartPosition - x) > this.dragThreshold)) {
iOSCheckbox.dragging = true;
}
return this.onDragMove(event, x);
};
iOSCheckbox.prototype.onGlobalUp = function(event) {
var x;
if (!iOSCheckbox.currentlyClicking) {
return;
}
event.preventDefault();
x = event.pageX || event.originalEvent.changedTouches[0].pageX;
this.onDragEnd(event, x);
return false;
};
iOSCheckbox.defaults = {
duration: 200,
checkedLabel: 'ON',
uncheckedLabel: 'OFF',
resizeHandle: true,
resizeContainer: true,
disabledClass: 'iPhoneCheckDisabled',
containerClass: 'iPhoneCheckContainer',
labelOnClass: 'iPhoneCheckLabelOn',
labelOffClass: 'iPhoneCheckLabelOff',
handleClass: 'iPhoneCheckHandle',
handleCenterClass: 'iPhoneCheckHandleCenter',
handleRightClass: 'iPhoneCheckHandleRight',
dragThreshold: 5,
handleMargin: 15,
handleRadius: 4,
containerRadius: 5,
dataName: "iphoneStyle",
onChange: function() {}
};
return iOSCheckbox;
})();
$.iphoneStyle = this.iOSCheckbox = iOSCheckbox;
$.fn.iphoneStyle = function() {
var args, checkbox, dataName, existingControl, method, params, _i, _len, _ref, _ref1, _ref2, _ref3;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
dataName = (_ref = (_ref1 = args[0]) != null ? _ref1.dataName : void 0) != null ? _ref : iOSCheckbox.defaults.dataName;
_ref2 = this.filter(':checkbox');
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
checkbox = _ref2[_i];
existingControl = $(checkbox).data(dataName);
if (existingControl != null) {
method = args[0], params = 2 <= args.length ? __slice.call(args, 1) : [];
if ((_ref3 = existingControl[method]) != null) {
_ref3.apply(existingControl, params);
}
} else {
new iOSCheckbox(checkbox, args[0]);
}
}
return this;
};
$.fn.iOSCheckbox = function(options) {
var opts;
if (options == null) {
options = {};
}
opts = $.extend({}, options, {
resizeHandle: false,
disabledClass: 'iOSCheckDisabled',
containerClass: 'iOSCheckContainer',
labelOnClass: 'iOSCheckLabelOn',
labelOffClass: 'iOSCheckLabelOff',
handleClass: 'iOSCheckHandle',
handleCenterClass: 'iOSCheckHandleCenter',
handleRightClass: 'iOSCheckHandleRight',
dataName: 'iOSCheckbox'
});
return this.iphoneStyle(opts);
};
}).call(this);

BIN
code/html/favicon.ico View File

Before After

+ 1
- 0
code/html/grids-responsive-min.css
File diff suppressed because it is too large
View File


BIN
code/html/images/off.png View File

Before After
Width: 289  |  Height: 27  |  Size: 2.5 KiB

BIN
code/html/images/on.png View File

Before After
Width: 289  |  Height: 27  |  Size: 2.4 KiB

BIN
code/html/images/slider.png View File

Before After
Width: 39  |  Height: 27  |  Size: 1.2 KiB

BIN
code/html/images/slider_center.png View File

Before After
Width: 4  |  Height: 27  |  Size: 260 B

BIN
code/html/images/slider_left.png View File

Before After
Width: 4  |  Height: 27  |  Size: 324 B

BIN
code/html/images/slider_right.png View File

Before After
Width: 4  |  Height: 27  |  Size: 321 B

+ 495
- 0
code/html/index.html View File

@ -0,0 +1,495 @@
<!DOCTYPE html>
<html>
<head>
<title>ESPurna 0.0.0</title>
<meta charset="utf-8" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="pure-min.css" />
<link rel="stylesheet" href="side-menu-min.css" />
<link rel="stylesheet" href="grids-responsive-min.css" />
<link rel="stylesheet" href="checkboxes-min.css" />
<script src="jquery-1.12.3.min.js"></script>
<script src="checkboxes-min.js"></script>
<style>
#menu .pure-menu-heading {
font-size: 100%;
padding: .5em .5em;
}
.header h2 {
font-size: 1em;
}
.panel {
display: none;
}
.content {
margin: 0px;
}
.page {
margin-top: 40px;
}
.center {
text-align: center;
}
.pure-button {
color: white;
padding: 8px 16px;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.button-update {
width: 100px;
margin: 50px auto;
background: #1f8dd6;
}
.pure-g {
margin-bottom: 20px;
}
legend {
font-weight: bold;
}
.l-box {
padding-right: 1px;
}
#topics .pure-g {
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 1px dashed #e5e5e5;
}
.pure-form input[type=text][disabled] {
color: #777777;
}
div.hint {
font-size: 80%;
color: #ccc;
margin-top: -5px;
}
.iPhoneCheckContainer {
letter-spacing: 0em;
height: 36px;
}
.iPhoneCheckHandle {
top: 8px;
}
</style>
<script>
var update_timer = null;
var relaySlider;
function doUpdate() {
var self = $(this);
self.addClass("loading");
$.ajax({
'method': 'POST',
'url': '/post',
'dataType': 'json',
'data': $("#formSave").serializeArray()
}).done(function(data) {
self.removeClass("loading");
}).fail(function() {
self.removeClass("loading");
});
}
function showPanel() {
$(".panel").hide();
$("#" + $(this).attr("data")).show();
if ($("#layout").hasClass('active')) toggleMenu();
};
function toggleMenu() {
$("#layout").toggleClass('active');
$("#menu").toggleClass('active');
$("#menuLink").toggleClass('active');
}
function parseResponse(data) {
// pre-process
if ("network" in data) data.network = data.network.toUpperCase();
if ("mqttStatus" in data) data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
// relay
if ("relayStatus" in data) {
$("input[name='relayStatus']")
.prop("checked", data.relayStatus)
.iphoneStyle("refresh");
}
// title
if ("app" in data) {
document.title = data.app;
$(".pure-menu-heading").html(data.app);
}
// automatic assign
Object.keys(data).forEach(function(key) {
var id = "input[name=" + key + "]";
if ($(id).length) $(id).val(data[key]);
});
// WIFI
var groups = $("#panel-wifi .pure-g");
for (var i in data.wifi) {
var wifi = data.wifi[i];
Object.keys(wifi).forEach(function(key) {
var id = "input[name=" + key + "]";
if ($(id, groups[i]).length) $(id, groups[i]).val(wifi[key]);
});
};
if ("updateInterval" in data) {
if (update_timer) clearInterval(update_timer);
if (data.updateInterval > 0) {
update_timer = setInterval(update, data.updateInterval);
}
}
}
function update() {
$.ajax({
'method': 'GET',
'url': '/status',
'dataType': 'json'
}).done(parseResponse);
}
function init() {
$.ajax({
'method': 'GET',
'url': '/get',
'dataType': 'json'
}).done(parseResponse);
}
$(function() {
$("#menuLink").on('click', toggleMenu);
$(".button-update").on('click', doUpdate);
$(".pure-menu-link").on('click', showPanel);
relaySlider = $('#relayStatus').iphoneStyle({
checkedLabel: 'ON',
uncheckedLabel: 'OFF',
onChange: function(elem, value) {
$.ajax({
'method': 'GET',
'url': value ? '/relay/on' : '/relay/off',
'dataType': 'json'
});
setTimeout(update, 200);
}
});
init();
});
</script>
</head>
<body>
<div id="layout">
<a href="#menu" id="menuLink" class="menu-link">
<span></span>
</a>
<div id="menu">
<div class="pure-menu">
<span class="pure-menu-heading">ESPurna 0.0.0</span>
<ul class="pure-menu-list">
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-status">STATUS</a>
</li>
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-wifi">WIFI</a>
</li>
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a>
</li>
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-rf">RF</a>
</li>
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-power">POWER</a>
</li>
</ul>
<div class="center">
<button class="pure-button button-update">Update</button>
</div>
</div>
</div>
<div class="content">
<div class="panel" id="panel-status" style="display: block;">
<div class="header">
<h1>STATUS</h1>
<h2>Current configuration</h2>
</div>
<div class="page">
<form class="pure-form pure-form-aligned">
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="manufacturer">Manufacturer</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="manufacturer" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="device">Device</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="device" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="hostname">Hostname</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="hostname" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="network">Network</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="network" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="ip">IP</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="ip" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="mqtt">MQTT Status</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="mqttStatus" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="temperature">Temperature</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="temperature" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="humidity">Humidity</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="humidity" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="power">Power</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="power" disabled>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="relayStatus">Relay Status</label>
<input class="pure-u-1 pure-u-sm-3-4" type="checkbox" checked="checked" name="relayStatus" id="relayStatus"/>
</div>
</fieldset>
</form>
</div>
</div>
<form id="formSave" class="pure-form pure-form-stacked" action="/" method="post">
<div class="panel" id="panel-wifi">
<div class="header">
<h1>WIFI</h1>
<h2>You can configure up to 3 different WiFi networks. The device will try to connect to any of them starting with the first one.</h2>
</div>
<div class="page">
<fieldset>
<legend>First network</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<label for="ssid">SSID</label>
<input type="text" class="pure-u-23-24" tabindex="10" name="ssid">
</div>
<div class="pure-u-1 pure-u-md-1-2">
<label for="pass">Password</label>
<input type="text" class="pure-u-23-24" tabindex="11" name="pass">
</div>
</div>
<legend>Second network</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<label for="ssid">SSID</label>
<input type="text" class="pure-u-23-24" tabindex="12" name="ssid">
</div>
<div class="pure-u-1 pure-u-md-1-2">
<label for="pass">Password</label>
<input type="text" class="pure-u-23-24" tabindex="13" name="pass">
</div>
</div>
<legend>Third network</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<label for="ssid">SSID</label>
<input type="text" class="pure-u-23-24" tabindex="14" name="ssid">
</div>
<div class="pure-u-1 pure-u-md-1-2">
<label for="pass">Password</label>
<input type="text" class="pure-u-23-24" tabindex="15" name="pass">
</div>
</div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-mqtt">
<div class="header">
<h1>MQTT</h1>
<h2>Configure an <strong>MQTT broker</strong> in your network and you will be able to change the switch status via an MQTT message. Leave the server field empty to disable MQTT.</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<div class="pure-u-1">
<label class="form-label" for="mqttServer">MQTT Server</label>
<input name="mqttServer" type="text" class="pure-u-23-24" value="" size="20" tabindex="10" placeholder="MQTT Server">
</div>
<div class="pure-u-1">
<label class="form-label" for="mqttPort">MQTT Port</label>
<input name="mqttPort" type="text" class="pure-u-23-24" value="" size="21" tabindex="11" placeholder="1883">
</div>
<div class="pure-u-1">
<label class="form-label" for="mqttUser">MQTT User</label>
<input name="mqttUser" type="text" class="pure-u-23-24" value="" size="22" tabindex="12" placeholder="Leave blank if no user/pass">
</div>
<div class="pure-u-1">
<label class="form-label" for="mqttPassword">MQTT Password</label>
<input name="mqttPassword" type="text" class="pure-u-23-24" value="" size="23" tabindex="13" placeholder="Leave blank if no user/pass">
</div>
<div class="pure-u-1">
<label class="form-label" for="mqttTopic">MQTT Topic</label>
<div class="hint">Send a 0 or a 1 as a payload to the provided topic below to switch it on or off. You can also send a 2 to toggle its current state. The switch will also report its current open/close status to the same topic and its IP address, hertbeat, firmware version and file system version to the topic you define plus "/ip", "/heartbeat", "/version" and "/fsversion" respectively. </div>
<input name="mqttTopic" type="text" class="pure-u-23-24" value="" size="24" tabindex="13" placeholder="Leave blank if no user/pass">
</div>
</div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-rf">
<div class="header">
<h1>RADIO</h1>
<h2>
Configure your radio channel and device ID.
</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<div class="pure-u-1">
<label class="form-label" for="rfChannel">Channel</label>
<div class="hint">This is the 5 bits code (0-31) you define on your remote, usually from a DIP switch.</div>
<input name="rfChannel" type="number" class="pure-u-1-4" min="0" max="30" step="1" tabindex="13"/>
</div>
<div class="pure-u-1">
<label class="form-label" for="rfDevice">Device</label>
<div class="hint">This is the button in your remote that will trigger the switch</div>
<select name="rfDevice" class="pure-u-1-4" tabindex="31">
<option value="0">A</a>
<option value="1">B</a>
<option value="2">C</a>
<option value="3">D</a>
<option value="4">E</a>
</select>
</div>
</div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-power">
<div class="header">
<h1>POWER</h1>
<h2>
Configure your power monitor variables.
</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<div class="pure-u-1">
<label class="form-label" for="pwMainsVoltage">AC RMS Voltage</label>
<div class="hint">This is your house nominal voltage, you probably know this or you wont be playing with this device...</div>
<select name="pwMainsVoltage" class="pure-u-1-4" tabindex="40">
<option value="125">125</a>
<option value="220">220</a>
<option value="230">230</a>
<option value="240">240</a>
</select>
</div>
<div class="pure-u-1">
<label class="form-label" for="pwCurrentRatio">Current Ratio</label>
<div class="hint">This is the value in amps for a 1V output for your sensor. Some current sensors like the YHDC SCT-013-030 have it written in the enclosure: 30A 1V. If you are using a current sensor that outputs a current (no built in burden resistor) it will depend on the turns ratio between the primary and secondary coils in the sensor and the burden resistor you use. Check about this constant in the <a href="https://openenergymonitor.org/emon/buildingblocks/calibration" target="_blank">post about calibration</a> in the Open Energy Monitor site.</div>
<input name="pwCurrentRatio" type="text" class="pure-u-1-4" value="" size="8" tabindex="41" placeholder="0">
</div>
</div>
</fieldset>
</div>
</div>
</form>
</div> <!-- content -->
</div> <!-- layout -->
</body>
</html>

+ 5
- 0
code/html/jquery-1.12.3.min.js
File diff suppressed because it is too large
View File


+ 1
- 0
code/html/pure-min.css
File diff suppressed because it is too large
View File


+ 1
- 0
code/html/side-menu-min.css View File

@ -0,0 +1 @@
body{color:#777}.pure-img-responsive{max-width:100%;height:auto}#layout,#menu,.menu-link{-webkit-transition:all 0.2s ease-out;-moz-transition:all 0.2s ease-out;-ms-transition:all 0.2s ease-out;-o-transition:all 0.2s ease-out;transition:all 0.2s ease-out}#layout{position:relative;padding-left:0}#layout.active #menu{left:150px;width:150px}#layout.active .menu-link{left:150px}.content{margin:0 auto;padding:0 2em;max-width:800px;margin-bottom:50px;line-height:1.6em}.header{margin:0;color:#333;text-align:center;padding:2.5em 2em 0;border-bottom:1px solid #eee}.header h1{margin:0.2em 0;font-size:3em;font-weight:300}.header h2{font-weight:300;color:#ccc;padding:0;margin-top:0}.content-subhead{margin:50px 0 20px 0;font-weight:300;color:#888}#menu{margin-left:-150px;width:150px;position:fixed;top:0;left:0;bottom:0;z-index:1000;background:#191818;overflow-y:auto;-webkit-overflow-scrolling:touch}#menu a{color:#999;border:none;padding:0.6em 0 0.6em 0.6em}#menu .pure-menu,#menu .pure-menu ul{border:none;background:transparent}#menu .pure-menu ul,#menu .pure-menu .menu-item-divided{border-top:1px solid #333}#menu .pure-menu li a:hover,#menu .pure-menu li a:focus{background:#333}#menu .pure-menu-selected,#menu .pure-menu-heading{background:#1f8dd6}#menu .pure-menu-selected a{color:#fff}#menu .pure-menu-heading{font-size:110%;color:#fff;margin:0}.menu-link{position:fixed;display:block;top:0;left:0;background:#000;background:rgba(0,0,0,0.7);font-size:10px;z-index:10;width:2em;height:auto;padding:2.1em 1.6em}.menu-link:hover,.menu-link:focus{background:#000}.menu-link span{position:relative;display:block}.menu-link span,.menu-link span:before,.menu-link span:after{background-color:#fff;width:100%;height:0.2em}.menu-link span:before,.menu-link span:after{position:absolute;margin-top:-0.6em;content:" "}.menu-link span:after{margin-top:0.6em}@media (min-width: 48em){.header,.content{padding-left:2em;padding-right:2em}#layout{padding-left:150px;left:0}#menu{left:150px}.menu-link{position:fixed;left:150px;display:none}#layout.active .menu-link{left:150px}}@media (max-width: 48em){#layout.active{position:relative;left:150px}}

+ 248
- 0
code/html/side-menu.css View File

@ -0,0 +1,248 @@
body {
color: #777;
}
.pure-img-responsive {
max-width: 100%;
height: auto;
}
/*
Add transition to containers so they can push in and out.
*/
#layout,
#menu,
.menu-link {
-webkit-transition: all 0.2s ease-out;
-moz-transition: all 0.2s ease-out;
-ms-transition: all 0.2s ease-out;
-o-transition: all 0.2s ease-out;
transition: all 0.2s ease-out;
}
/*
This is the parent `<div>` that contains the menu and the content area.
*/
#layout {
position: relative;
padding-left: 0;
}
#layout.active #menu {
left: 150px;
width: 150px;
}
#layout.active .menu-link {
left: 150px;
}
/*
The content `<div>` is where all your content goes.
*/
.content {
margin: 0 auto;
padding: 0 2em;
max-width: 800px;
margin-bottom: 50px;
line-height: 1.6em;
}
.header {
margin: 0;
color: #333;
text-align: center;
padding: 2.5em 2em 0;
border-bottom: 1px solid #eee;
}
.header h1 {
margin: 0.2em 0;
font-size: 3em;
font-weight: 300;
}
.header h2 {
font-weight: 300;
color: #ccc;
padding: 0;
margin-top: 0;
}
.content-subhead {
margin: 50px 0 20px 0;
font-weight: 300;
color: #888;
}
/*
The `#menu` `<div>` is the parent `<div>` that contains the `.pure-menu` that
appears on the left side of the page.
*/
#menu {
margin-left: -150px; /* "#menu" width */
width: 150px;
position: fixed;
top: 0;
left: 0;
bottom: 0;
z-index: 1000; /* so the menu or its navicon stays above all content */
background: #191818;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/*
All anchors inside the menu should be styled like this.
*/
#menu a {
color: #999;
border: none;
padding: 0.6em 0 0.6em 0.6em;
}
/*
Remove all background/borders, since we are applying them to #menu.
*/
#menu .pure-menu,
#menu .pure-menu ul {
border: none;
background: transparent;
}
/*
Add that light border to separate items into groups.
*/
#menu .pure-menu ul,
#menu .pure-menu .menu-item-divided {
border-top: 1px solid #333;
}
/*
Change color of the anchor links on hover/focus.
*/
#menu .pure-menu li a:hover,
#menu .pure-menu li a:focus {
background: #333;
}
/*
This styles the selected menu item `<li>`.
*/
#menu .pure-menu-selected,
#menu .pure-menu-heading {
background: #1f8dd6;
}
/*
This styles a link within a selected menu item `<li>`.
*/
#menu .pure-menu-selected a {
color: #fff;
}
/*
This styles the menu heading.
*/
#menu .pure-menu-heading {
font-size: 110%;
color: #fff;
margin: 0;
}
/* -- Dynamic Button For Responsive Menu -------------------------------------*/
/*
The button to open/close the Menu is custom-made and not part of Pure. Here's
how it works:
*/
/*
`.menu-link` represents the responsive menu toggle that shows/hides on
small screens.
*/
.menu-link {
position: fixed;
display: block; /* show this only on small screens */
top: 0;
left: 0; /* "#menu width" */
background: #000;
background: rgba(0,0,0,0.7);
font-size: 10px; /* change this value to increase/decrease button size */
z-index: 10;
width: 2em;
height: auto;
padding: 2.1em 1.6em;
}
.menu-link:hover,
.menu-link:focus {
background: #000;
}
.menu-link span {
position: relative;
display: block;
}
.menu-link span,
.menu-link span:before,
.menu-link span:after {
background-color: #fff;
width: 100%;
height: 0.2em;
}
.menu-link span:before,
.menu-link span:after {
position: absolute;
margin-top: -0.6em;
content: " ";
}
.menu-link span:after {
margin-top: 0.6em;
}
/* -- Responsive Styles (Media Queries) ------------------------------------- */
/*
Hides the menu at `48em`, but modify this based on your app's needs.
*/
@media (min-width: 48em) {
.header,
.content {
padding-left: 2em;
padding-right: 2em;
}
#layout {
padding-left: 150px; /* left col width "#menu" */
left: 0;
}
#menu {
left: 150px;
}
.menu-link {
position: fixed;
left: 150px;
display: none;
}
#layout.active .menu-link {
left: 150px;
}
}
@media (max-width: 48em) {
/* Only apply this when the window is small. Otherwise, the following
case results in extra padding on the left:
* Make the window small.
* Tap the menu to trigger the active state.
* Make the window large again.
*/
#layout.active {
position: relative;
left: 150px;
}
}

+ 1
- 1
code/src/defaults.h View File

@ -50,7 +50,7 @@
#define OTA_PORT 8266 #define OTA_PORT 8266
#define BUFFER_SIZE 1024 #define BUFFER_SIZE 1024
#define STATUS_UPDATE_INTERVAL 30000
#define STATUS_UPDATE_INTERVAL 10000
#define HEARTBEAT_INTERVAL 60000 #define HEARTBEAT_INTERVAL 60000
#define FS_VERSION_FILE "/fsversion" #define FS_VERSION_FILE "/fsversion"


+ 24
- 25
code/src/webserver.ino View File

@ -74,39 +74,38 @@ bool handleFileRead(String path) {
} }
void handleInit() {
void handleGet() {
#ifdef DEBUG #ifdef DEBUG
Serial.println("[WEBSERVER] Request: /init");
Serial.println("[WEBSERVER] Request: /get");
#endif #endif
char buffer[64]; char buffer[64];
char built[16];
getCompileTime(built);
sprintf(buffer, "%s %s built %s", APP_NAME, APP_VERSION, built);
sprintf(buffer, "%s %s", APP_NAME, APP_VERSION);
StaticJsonBuffer<1024> jsonBuffer; StaticJsonBuffer<1024> jsonBuffer;
JsonObject& root = jsonBuffer.createObject(); JsonObject& root = jsonBuffer.createObject();
root["appname"] = String(buffer);
root["app"] = buffer;
root["manufacturer"] = String(MANUFACTURER); root["manufacturer"] = String(MANUFACTURER);
root["device"] = String(DEVICE); root["device"] = String(DEVICE);
root["hostname"] = getSetting("hostname"); root["hostname"] = getSetting("hostname");
root["network"] = getNetwork(); root["network"] = getNetwork();
root["ip"] = getIP(); root["ip"] = getIP();
root["updateInterval"] = STATUS_UPDATE_INTERVAL; root["updateInterval"] = STATUS_UPDATE_INTERVAL;
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
root["ssid" + String(i)] = getSetting("ssid" + String(i));
root["pass" + String(i)] = getSetting("pass" + String(i));
}
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER); root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", String(MQTT_PORT)); root["mqttPort"] = getSetting("mqttPort", String(MQTT_PORT));
root["mqttUser"] = getSetting("mqttUser"); root["mqttUser"] = getSetting("mqttUser");
root["mqttPassword"] = getSetting("mqttPassword"); root["mqttPassword"] = getSetting("mqttPassword");
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC); root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<3; i++) {
JsonObject& network = wifi.createNestedObject();
network["ssid"] = getSetting("ssid" + String(i));
network["pass"] = getSetting("pass" + String(i));
}
#if ENABLE_RF #if ENABLE_RF
root["rfChannel"] = getSetting("rfChannel", String(RF_CHANNEL)); root["rfChannel"] = getSetting("rfChannel", String(RF_CHANNEL));
root["rfDevice"] = getSetting("rfDevice", String(RF_DEVICE)); root["rfDevice"] = getSetting("rfDevice", String(RF_DEVICE));
@ -134,8 +133,8 @@ void handleStatus() {
StaticJsonBuffer<256> jsonBuffer; StaticJsonBuffer<256> jsonBuffer;
JsonObject& root = jsonBuffer.createObject(); JsonObject& root = jsonBuffer.createObject();
root["relay"] = digitalRead(RELAY_PIN) ? 1: 0;
root["mqtt"] = mqttConnected();
root["relayStatus"] = (digitalRead(RELAY_PIN) == HIGH);
root["mqttStatus"] = mqttConnected();
#if ENABLE_EMON #if ENABLE_EMON
root["power"] = getCurrent() * getSetting("pwMainsVoltage", String(EMON_MAINS_VOLTAGE)).toFloat(); root["power"] = getCurrent() * getSetting("pwMainsVoltage", String(EMON_MAINS_VOLTAGE)).toFloat();
#endif #endif
@ -150,27 +149,27 @@ void handleStatus() {
} }
void handleSave() {
void handlePost() {
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("[WEBSERVER] Request: /save"));
Serial.println(F("[WEBSERVER] Request: /post"));
#endif #endif
bool dirty = false; bool dirty = false;
bool dirtyMQTT = false; bool dirtyMQTT = false;
unsigned int network = 0;
for (unsigned int i=0; i<server.args(); i++) { for (unsigned int i=0; i<server.args(); i++) {
String key = server.argName(i); String key = server.argName(i);
String value = server.arg(i); String value = server.arg(i);
if (key == "status") {
if (value == "1") {
switchRelayOn();
} else {
switchRelayOff();
}
continue;
if (key == "ssid") {
key = key + String(network);
}
if (key == "pass") {
key = key + String(network);
++network;
} }
if (value != getSetting(key)) { if (value != getSetting(key)) {
@ -212,9 +211,9 @@ void webServerSetup() {
server.on("/relay/off", HTTP_GET, handleRelayOff); server.on("/relay/off", HTTP_GET, handleRelayOff);
// Configuration page // Configuration page
server.on("/init", HTTP_GET, handleInit);
server.on("/get", HTTP_GET, handleGet);
server.on("/post", HTTP_POST, handlePost);
server.on("/status", HTTP_GET, handleStatus); server.on("/status", HTTP_GET, handleStatus);
server.on("/save", HTTP_POST, handleSave);
// Anything else // Anything else
server.onNotFound([]() { server.onNotFound([]() {


Loading…
Cancel
Save