").appendTo(this.container);
68 | this.onSpan = this.onLabel.children('span');
69 | this.handle = $("
").appendTo(this.container);
70 | this.handleCenter = $("
").appendTo(this.handle);
71 | this.handleRight = $("
").appendTo(this.handle);
72 | return true;
73 | };
74 |
75 | iOSCheckbox.prototype.disableTextSelection = function() {
76 | if ($.browser.msie) {
77 | return $([this.handle, this.offLabel, this.onLabel, this.container]).attr("unselectable", "on");
78 | }
79 | };
80 |
81 | iOSCheckbox.prototype._getDimension = function(elem, dimension) {
82 | if ($.fn.actual != null) {
83 | return elem.actual(dimension);
84 | } else {
85 | return elem[dimension]();
86 | }
87 | };
88 |
89 | iOSCheckbox.prototype.optionallyResize = function(mode) {
90 | var newWidth, offLabelWidth, offSpan, onLabelWidth, onSpan;
91 |
92 | onSpan = this.onLabel.find('span');
93 | onLabelWidth = this._getDimension(onSpan, "width");
94 | onLabelWidth += parseInt(onSpan.css('padding-left'), 10);
95 | offSpan = this.offLabel.find('span');
96 | offLabelWidth = this._getDimension(offSpan, "width");
97 | offLabelWidth += parseInt(offSpan.css('padding-right'), 10);
98 | if (mode === "container") {
99 | newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
100 | newWidth += this._getDimension(this.handle, "width") + this.handleMargin;
101 | return this.container.css({
102 | width: newWidth
103 | });
104 | } else {
105 | newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
106 | this.handleCenter.css({
107 | width: newWidth + 4
108 | });
109 | return this.handle.css({
110 | width: newWidth + 7
111 | });
112 | }
113 | };
114 |
115 | iOSCheckbox.prototype.onMouseDown = function(event) {
116 | var x;
117 |
118 | event.preventDefault();
119 | if (this.isDisabled()) {
120 | return;
121 | }
122 | x = event.pageX || event.originalEvent.changedTouches[0].pageX;
123 | iOSCheckbox.currentlyClicking = this.handle;
124 | iOSCheckbox.dragStartPosition = x;
125 | return iOSCheckbox.handleLeftOffset = parseInt(this.handle.css('left'), 10) || 0;
126 | };
127 |
128 | iOSCheckbox.prototype.onDragMove = function(event, x) {
129 | var newWidth, p;
130 |
131 | if (iOSCheckbox.currentlyClicking !== this.handle) {
132 | return;
133 | }
134 | p = (x + iOSCheckbox.handleLeftOffset - iOSCheckbox.dragStartPosition) / this.rightSide;
135 | if (p < 0) {
136 | p = 0;
137 | }
138 | if (p > 1) {
139 | p = 1;
140 | }
141 | newWidth = p * this.rightSide;
142 | this.handle.css({
143 | left: newWidth
144 | });
145 | this.onLabel.css({
146 | width: newWidth + this.handleRadius
147 | });
148 | this.offSpan.css({
149 | marginRight: -newWidth
150 | });
151 | return this.onSpan.css({
152 | marginLeft: -(1 - p) * this.rightSide
153 | });
154 | };
155 |
156 | iOSCheckbox.prototype.onDragEnd = function(event, x) {
157 | var p;
158 |
159 | if (iOSCheckbox.currentlyClicking !== this.handle) {
160 | return;
161 | }
162 | if (this.isDisabled()) {
163 | return;
164 | }
165 | if (iOSCheckbox.dragging) {
166 | p = (x - iOSCheckbox.dragStartPosition) / this.rightSide;
167 | this.elem.prop('checked', p >= 0.5).change();
168 | } else {
169 | this.elem.prop('checked', !this.elem.prop('checked')).change();
170 | }
171 | iOSCheckbox.currentlyClicking = null;
172 | iOSCheckbox.dragging = null;
173 | if (typeof this.onChange === "function") {
174 | this.onChange(this.elem, this.elem.prop('checked'));
175 | }
176 | return this.didChange();
177 | };
178 |
179 | iOSCheckbox.prototype.refresh = function() {
180 | return this.didChange();
181 | };
182 |
183 | iOSCheckbox.prototype.didChange = function() {
184 | var new_left;
185 |
186 | if (this.isDisabled()) {
187 | this.container.addClass(this.disabledClass);
188 | return false;
189 | } else {
190 | this.container.removeClass(this.disabledClass);
191 | }
192 | new_left = this.elem.prop('checked') ? this.rightSide + 2 : 0;
193 | this.handle.animate({
194 | left: new_left
195 | }, this.duration);
196 | this.onLabel.animate({
197 | width: new_left + this.handleRadius
198 | }, this.duration);
199 | this.offSpan.animate({
200 | marginRight: -new_left
201 | }, this.duration);
202 | return this.onSpan.animate({
203 | marginLeft: new_left - this.rightSide
204 | }, this.duration);
205 | };
206 |
207 | iOSCheckbox.prototype.attachEvents = function() {
208 | var localMouseMove, localMouseUp, self;
209 |
210 | self = this;
211 | localMouseMove = function(event) {
212 | return self.onGlobalMove.apply(self, arguments);
213 | };
214 | localMouseUp = function(event) {
215 | self.onGlobalUp.apply(self, arguments);
216 | $(document).unbind('mousemove touchmove', localMouseMove);
217 | return $(document).unbind('mouseup touchend', localMouseUp);
218 | };
219 | this.elem.change(function() {
220 | return self.refresh();
221 | });
222 | return this.container.bind('mousedown touchstart', function(event) {
223 | self.onMouseDown.apply(self, arguments);
224 | $(document).bind('mousemove touchmove', localMouseMove);
225 | return $(document).bind('mouseup touchend', localMouseUp);
226 | });
227 | };
228 |
229 | iOSCheckbox.prototype.initialPosition = function() {
230 | var containerWidth, offset;
231 |
232 | containerWidth = this._getDimension(this.container, "width");
233 | this.offLabel.css({
234 | width: containerWidth - this.containerRadius - 4
235 | });
236 | this.offBorder.css({
237 | left: containerWidth - 4
238 | });
239 | offset = this.containerRadius + 1;
240 | if ($.browser.msie && $.browser.version < 7) {
241 | offset -= 3;
242 | }
243 | this.rightSide = containerWidth - this._getDimension(this.handle, "width") - offset;
244 | if (this.elem.is(':checked')) {
245 | this.handle.css({
246 | left: this.rightSide
247 | });
248 | this.onLabel.css({
249 | width: this.rightSide + this.handleRadius
250 | });
251 | this.offSpan.css({
252 | marginRight: -this.rightSide,
253 | });
254 | } else {
255 | this.onLabel.css({
256 | width: 0
257 | });
258 | this.onSpan.css({
259 | marginLeft: -this.rightSide
260 | });
261 | }
262 | if (this.isDisabled()) {
263 | return this.container.addClass(this.disabledClass);
264 | }
265 | };
266 |
267 | iOSCheckbox.prototype.onGlobalMove = function(event) {
268 | var x;
269 |
270 | if (!(!this.isDisabled() && iOSCheckbox.currentlyClicking)) {
271 | return;
272 | }
273 | event.preventDefault();
274 | x = event.pageX || event.originalEvent.changedTouches[0].pageX;
275 | if (!iOSCheckbox.dragging && (Math.abs(iOSCheckbox.dragStartPosition - x) > this.dragThreshold)) {
276 | iOSCheckbox.dragging = true;
277 | }
278 | return this.onDragMove(event, x);
279 | };
280 |
281 | iOSCheckbox.prototype.onGlobalUp = function(event) {
282 | var x;
283 |
284 | if (!iOSCheckbox.currentlyClicking) {
285 | return;
286 | }
287 | event.preventDefault();
288 | x = event.pageX || event.originalEvent.changedTouches[0].pageX;
289 | this.onDragEnd(event, x);
290 | return false;
291 | };
292 |
293 | iOSCheckbox.defaults = {
294 | duration: 200,
295 | checkedLabel: 'ON',
296 | uncheckedLabel: 'OFF',
297 | resizeHandle: true,
298 | resizeContainer: true,
299 | disabledClass: 'iPhoneCheckDisabled',
300 | containerClass: 'iPhoneCheckContainer',
301 | labelOnClass: 'iPhoneCheckLabelOn',
302 | labelOffClass: 'iPhoneCheckLabelOff',
303 | handleClass: 'iPhoneCheckHandle',
304 | handleCenterClass: 'iPhoneCheckHandleCenter',
305 | handleRightClass: 'iPhoneCheckHandleRight',
306 | dragThreshold: 5,
307 | handleMargin: 15,
308 | handleRadius: 4,
309 | containerRadius: 5,
310 | dataName: "iphoneStyle",
311 | onChange: function() {}
312 | };
313 |
314 | return iOSCheckbox;
315 |
316 | })();
317 |
318 | $.iphoneStyle = this.iOSCheckbox = iOSCheckbox;
319 |
320 | $.fn.iphoneStyle = function() {
321 | var args, checkbox, dataName, existingControl, method, params, _i, _len, _ref, _ref1, _ref2, _ref3;
322 |
323 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
324 | dataName = (_ref = (_ref1 = args[0]) != null ? _ref1.dataName : void 0) != null ? _ref : iOSCheckbox.defaults.dataName;
325 | _ref2 = this.filter(':checkbox');
326 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
327 | checkbox = _ref2[_i];
328 | existingControl = $(checkbox).data(dataName);
329 | if (existingControl != null) {
330 | method = args[0], params = 2 <= args.length ? __slice.call(args, 1) : [];
331 | if ((_ref3 = existingControl[method]) != null) {
332 | _ref3.apply(existingControl, params);
333 | }
334 | } else {
335 | new iOSCheckbox(checkbox, args[0]);
336 | }
337 | }
338 | return this;
339 | };
340 |
341 | $.fn.iOSCheckbox = function(options) {
342 | var opts;
343 |
344 | if (options == null) {
345 | options = {};
346 | }
347 | opts = $.extend({}, options, {
348 | resizeHandle: false,
349 | disabledClass: 'iOSCheckDisabled',
350 | containerClass: 'iOSCheckContainer',
351 | labelOnClass: 'iOSCheckLabelOn',
352 | labelOffClass: 'iOSCheckLabelOff',
353 | handleClass: 'iOSCheckHandle',
354 | handleCenterClass: 'iOSCheckHandleCenter',
355 | handleRightClass: 'iOSCheckHandleRight',
356 | dataName: 'iOSCheckbox'
357 | });
358 | return this.iphoneStyle(opts);
359 | };
360 |
361 | }).call(this);
362 |
--------------------------------------------------------------------------------
/esp8266/html/custom.css:
--------------------------------------------------------------------------------
1 | #menu .pure-menu-heading {
2 | font-size: 100%;
3 | padding: .5em .5em;
4 | }
5 | .header h2 {
6 | font-size: 1em;
7 | }
8 | .panel {
9 | display: none;
10 | }
11 | .footer {
12 | position: absolute;
13 | bottom: 0;
14 | left: 0;
15 | right: 0;
16 | padding: 10px;
17 | font-size: 80%;
18 | color: #999;
19 | }
20 | #menu .footer a {
21 | text-decoration: none;
22 | padding: 0px;
23 | }
24 | .content {
25 | margin: 0px;
26 | }
27 | .page {
28 | margin-top: 40px;
29 | }
30 | .pure-button {
31 | color: white;
32 | padding: 8px 12px;
33 | border-radius: 4px;
34 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
35 | }
36 | .main-buttons {
37 | margin: 50px auto;
38 | text-align: center;
39 | }
40 | .main-buttons button {
41 | width: 100px;
42 | margin: 5px auto;
43 | }
44 | .button-update-password,
45 | .button-update {
46 | background: #1f8dd6;
47 | }
48 | .button-reset,
49 | .button-reconnect {
50 | background: rgb(202, 60, 60);
51 | }
52 | .button-upgrade {
53 | background: rgb(202, 60, 60);
54 | margin-left: 5px;
55 | }
56 | .button-upgrade-browse,
57 | .button-apikey {
58 | background: rgb(0, 202, 0);
59 | margin-left: 5px;
60 | }
61 | .button-add-network {
62 | background: rgb(28, 184, 65);
63 | }
64 | .button-del-network {
65 | background: rgb(202, 60, 60);
66 | }
67 | .button-more-network {
68 | background: rgb(223, 117, 20);
69 | }
70 | .button-settings-backup,
71 | .button-settings-restore {
72 | background: rgb(0, 202, 0);
73 | }
74 | .pure-g {
75 | margin-bottom: 20px;
76 | }
77 | legend {
78 | font-weight: bold;
79 | }
80 | .l-box {
81 | padding-right: 1px;
82 | }
83 | .pure-form input[type=text][disabled] {
84 | color: #777777;
85 | }
86 | div.hint {
87 | font-size: 80%;
88 | color: #ccc;
89 | }
90 | .break {
91 | margin-top: 5px;
92 | }
93 | #networks .pure-g {
94 | padding-bottom: 10px;
95 | margin-bottom: 5px;
96 | border-bottom: 2px dashed #e5e5e5;
97 | }
98 | #networks div.more {
99 | display: none;
100 | }
101 | .module {
102 | display: none;
103 | }
104 | .template {
105 | display: none;
106 | }
107 | input[name=upgrade] {
108 | display: none;
109 | }
110 | #upgrade-progress {
111 | display: none;
112 | width: 100%;
113 | height: 20px;
114 | margin-top: 10px;
115 | }
116 | .pure-form .center {
117 | margin: .5em 0 .2em;
118 | }
119 | .webmode {
120 | display: none;
121 | }
122 | #credentials {
123 | font-size: 200%;
124 | text-align: center;
125 | height: 100px;
126 | width: 400px;
127 | position: fixed;
128 | top: 50%;
129 | left: 50%;
130 | margin-top: -50px;
131 | margin-left: -200px;
132 | }
133 |
134 | div.state {
135 | border-top: 1px solid #eee;
136 | margin-top: 20px;
137 | padding-top: 30px;
138 | }
139 |
140 | .state div {
141 | font-size: 80%;
142 | }
143 |
144 | .state span {
145 | font-size: 80%;
146 | font-weight: bold;
147 | }
148 |
149 | .right {
150 | text-align: right;
151 | }
152 |
--------------------------------------------------------------------------------
/esp8266/html/custom.js:
--------------------------------------------------------------------------------
1 | var websock;
2 | var password = false;
3 | var maxNetworks;
4 | var messages = [];
5 | var webhost;
6 |
7 | // http://www.the-art-of-web.com/javascript/validate-password/
8 | function checkPassword(str) {
9 | // at least one number, one lowercase and one uppercase letter
10 | // at least eight characters that are letters, numbers or the underscore
11 | var re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{8,}$/;
12 | return re.test(str);
13 | }
14 |
15 | function validateForm(form) {
16 |
17 | // password
18 | var adminPass1 = $("input[name='adminPass1']", form).val();
19 | if (adminPass1.length > 0 && !checkPassword(adminPass1)) {
20 | alert("The password you have entered is not valid, it must have at least 8 characters, 1 lower and 1 uppercase and 1 number!");
21 | return false;
22 | }
23 |
24 | var adminPass2 = $("input[name='adminPass2']", form).val();
25 | if (adminPass1 != adminPass2) {
26 | alert("Passwords are different!");
27 | return false;
28 | }
29 |
30 | return true;
31 |
32 | }
33 |
34 | function valueSet(data, name, value) {
35 | for (var i in data) {
36 | if (data[i]['name'] == name) {
37 | data[i]['value'] = value;
38 | return;
39 | }
40 | }
41 | data.push({'name': name, 'value': value});
42 | }
43 |
44 | function zeroPad(number, positions) {
45 | return ("0".repeat(positions) + number).slice(-positions);
46 | }
47 |
48 | function doUpdate() {
49 |
50 | var form = $("#formSave");
51 |
52 | if (validateForm(form)) {
53 |
54 | // Get data
55 | var data = form.serializeArray();
56 |
57 | // Post-process
58 | delete(data['filename']);
59 | $("input[type='checkbox']").each(function() {
60 | var name = $(this).attr("name");
61 | if (name) {
62 | valueSet(data, name, $(this).is(':checked') ? 1 : 0);
63 | }
64 | });
65 |
66 | websock.send(JSON.stringify({'config': data}));
67 | }
68 |
69 | return false;
70 |
71 | }
72 |
73 | function doUpgrade() {
74 |
75 | var contents = $("input[name='upgrade']")[0].files[0];
76 | if (typeof contents == 'undefined') {
77 | alert("First you have to select a file from your computer.");
78 | return false;
79 | }
80 | var filename = $("input[name='upgrade']").val().split('\\').pop();
81 |
82 | var data = new FormData();
83 | data.append('upgrade', contents, filename);
84 |
85 | $.ajax({
86 |
87 | // Your server script to process the upload
88 | url: webhost + 'upgrade',
89 | type: 'POST',
90 |
91 | // Form data
92 | data: data,
93 |
94 | // Tell jQuery not to process data or worry about content-type
95 | // You *must* include these options!
96 | cache: false,
97 | contentType: false,
98 | processData: false,
99 |
100 | success: function(data, text) {
101 | $("#upgrade-progress").hide();
102 | if (data == 'OK') {
103 | alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.");
104 | setTimeout(function() {
105 | window.location.reload();
106 | }, 5000);
107 | } else {
108 | alert("There was an error trying to upload the new image, please try again (" + data + ").");
109 | }
110 | },
111 |
112 | // Custom XMLHttpRequest
113 | xhr: function() {
114 | $("#upgrade-progress").show();
115 | var myXhr = $.ajaxSettings.xhr();
116 | if (myXhr.upload) {
117 | // For handling the progress of the upload
118 | myXhr.upload.addEventListener('progress', function(e) {
119 | if (e.lengthComputable) {
120 | $('progress').attr({ value: e.loaded, max: e.total });
121 | }
122 | } , false);
123 | }
124 | return myXhr;
125 | },
126 |
127 | });
128 |
129 | return false;
130 |
131 | }
132 |
133 | function doUpdatePassword() {
134 | var form = $("#formPassword");
135 | if (validateForm(form)) {
136 | var data = form.serializeArray();
137 | websock.send(JSON.stringify({'config': data}));
138 | }
139 | return false;
140 | }
141 |
142 | function doReset() {
143 | var response = window.confirm("Are you sure you want to reset the device?");
144 | if (response == false) return false;
145 | websock.send(JSON.stringify({'action': 'reset'}));
146 | return false;
147 | }
148 |
149 | function doReconnect() {
150 | var response = window.confirm("Are you sure you want to disconnect from the current WIFI network?");
151 | if (response == false) return false;
152 | websock.send(JSON.stringify({'action': 'reconnect'}));
153 | return false;
154 | }
155 |
156 | function backupSettings() {
157 | document.getElementById('downloader').src = webhost + 'config';
158 | return false;
159 | }
160 |
161 | function onFileUpload(event) {
162 |
163 | var inputFiles = this.files;
164 | if (inputFiles == undefined || inputFiles.length == 0) return false;
165 | var inputFile = inputFiles[0];
166 | this.value = "";
167 |
168 | var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
169 | if (response == false) return false;
170 |
171 | var reader = new FileReader();
172 | reader.onload = function(e) {
173 | var data = getJson(e.target.result);
174 | if (data) {
175 | websock.send(JSON.stringify({'action': 'restore', 'data': data}));
176 | } else {
177 | alert(messages[4]);
178 | }
179 | };
180 | reader.readAsText(inputFile);
181 |
182 | return false;
183 |
184 | }
185 |
186 | function restoreSettings() {
187 | if (typeof window.FileReader !== 'function') {
188 | alert("The file API isn't supported on this browser yet.");
189 | } else {
190 | $("#uploader").click();
191 | }
192 | return false;
193 | }
194 |
195 | function randomString(length, chars) {
196 | var mask = '';
197 | if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz';
198 | if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
199 | if (chars.indexOf('#') > -1) mask += '0123456789';
200 | if (chars.indexOf('@') > -1) mask += 'ABCDEF';
201 | if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\';
202 | var result = '';
203 | for (var i = length; i > 0; --i) result += mask[Math.round(Math.random() * (mask.length - 1))];
204 | return result;
205 | }
206 |
207 | function doGenerateAPIKey() {
208 | var apikey = randomString(16, '@#');
209 | $("input[name=\"apiKey\"]").val(apikey);
210 | return false;
211 | }
212 |
213 | function showPanel() {
214 | $(".panel").hide();
215 | $("#" + $(this).attr("data")).show();
216 | if ($("#layout").hasClass('active')) toggleMenu();
217 | $("input[type='checkbox']").iphoneStyle("calculateDimensions").iphoneStyle("refresh");
218 | };
219 |
220 | function toggleMenu() {
221 | $("#layout").toggleClass('active');
222 | $("#menu").toggleClass('active');
223 | $("#menuLink").toggleClass('active');
224 | }
225 |
226 | function delNetwork() {
227 | var parent = $(this).parents(".pure-g");
228 | $(parent).remove();
229 | }
230 |
231 | function moreNetwork() {
232 | var parent = $(this).parents(".pure-g");
233 | $("div.more", parent).toggle();
234 | }
235 |
236 | function addNetwork() {
237 |
238 | var numNetworks = $("#networks > div").length;
239 | if (numNetworks >= maxNetworks) {
240 | alert("Max number of networks reached");
241 | return;
242 | }
243 |
244 | var tabindex = 200 + numNetworks * 10;
245 | var template = $("#networkTemplate").children();
246 | var line = $(template).clone();
247 | $(line).find("input").each(function() {
248 | $(this).attr("tabindex", tabindex++);
249 | });
250 | $(line).find(".button-del-network").on('click', delNetwork);
251 | $(line).find(".button-more-network").on('click', moreNetwork);
252 | line.appendTo("#networks");
253 |
254 | return line;
255 |
256 | }
257 |
258 | function forgetCredentials() {
259 | $.ajax({
260 | 'method': 'GET',
261 | 'url': '/',
262 | 'async': false,
263 | 'username': "logmeout",
264 | 'password': "123456",
265 | 'headers': { "Authorization": "Basic xxx" }
266 | }).done(function(data) {
267 | return false;
268 | // If we don't get an error, we actually got an error as we expect an 401!
269 | }).fail(function(){
270 | // We expect to get an 401 Unauthorized error! In this case we are successfully
271 | // logged out and we redirect the user.
272 | return true;
273 | });
274 | }
275 |
276 | function processData(data) {
277 |
278 | // title
279 | if ("app" in data) {
280 | var title = data.app;
281 | if ("version" in data) {
282 | title = title + " " + data.version;
283 | }
284 | $(".pure-menu-heading").html(title);
285 | if ("hostname" in data) {
286 | title = data.hostname + " - " + title;
287 | }
288 | document.title = title;
289 | }
290 |
291 | Object.keys(data).forEach(function(key) {
292 |
293 | // Web Modes
294 | if (key == "webMode") {
295 | password = data.webMode == 1;
296 | $("#layout").toggle(data.webMode == 0);
297 | $("#password").toggle(data.webMode == 1);
298 | $("#credentials").hide();
299 | }
300 |
301 | // Actions
302 | if (key == "action") {
303 |
304 | if (data.action == "reload") {
305 | if (password) forgetCredentials();
306 | setTimeout(function() {
307 | window.location.reload();
308 | }, 1000);
309 | }
310 |
311 | return;
312 |
313 | }
314 |
315 | if (key == "uptime") {
316 | var uptime = parseInt(data[key]);
317 | var seconds = uptime % 60; uptime = parseInt(uptime / 60);
318 | var minutes = uptime % 60; uptime = parseInt(uptime / 60);
319 | var hours = uptime % 24; uptime = parseInt(uptime / 24);
320 | var days = uptime;
321 | data[key] = days + 'd ' + zeroPad(hours, 2) + 'h ' + zeroPad(minutes, 2) + 'm ' + zeroPad(seconds, 2) + 's';
322 | }
323 |
324 | if (key == "maxNetworks") {
325 | maxNetworks = parseInt(data.maxNetworks);
326 | return;
327 | }
328 |
329 | // Wifi
330 | if (key == "wifi") {
331 |
332 | var networks = data.wifi;
333 |
334 | for (var i in networks) {
335 |
336 | // add a new row
337 | var line = addNetwork();
338 |
339 | // fill in the blanks
340 | var wifi = data.wifi[i];
341 | Object.keys(wifi).forEach(function(key) {
342 | var element = $("input[name=" + key + "]", line);
343 | if (element.length) element.val(wifi[key]);
344 | });
345 |
346 | }
347 |
348 | return;
349 |
350 | }
351 |
352 | // Messages
353 | if (key == "message") {
354 | window.alert(messages[data.message]);
355 | return;
356 | }
357 |
358 | // Enable options
359 | if (key.endsWith("Visible")) {
360 | var module = key.slice(0,-7);
361 | $(".module-" + module).show();
362 | return;
363 | }
364 |
365 | // Pre-process
366 | if (key == "network") {
367 | data.network = data.network.toUpperCase();
368 | }
369 | if (key == "mqttStatus") {
370 | data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
371 | }
372 | if (key == "ntpStatus") {
373 | data.ntpStatus = data.ntpStatus ? "SYNC'D" : "NOT SYNC'D";
374 | }
375 |
376 | // Look for INPUTs
377 | var element = $("input[name=" + key + "]");
378 | if (element.length > 0) {
379 | if (element.attr('type') == 'checkbox') {
380 | element
381 | .prop("checked", data[key])
382 | .iphoneStyle("refresh");
383 | } else if (element.attr('type') == 'radio') {
384 | element.val([data[key]]);
385 | } else {
386 | var pre = element.attr("pre") || "";
387 | var post = element.attr("post") || "";
388 | element.val(pre + data[key] + post);
389 | }
390 | return;
391 | }
392 |
393 | // Look for SPANs
394 | var element = $("span[name=" + key + "]");
395 | if (element.length > 0) {
396 | var pre = element.attr("pre") || "";
397 | var post = element.attr("post") || "";
398 | element.html(pre + data[key] + post);
399 | return;
400 | }
401 |
402 | // Look for SELECTs
403 | var element = $("select[name=" + key + "]");
404 | if (element.length > 0) {
405 | element.val(data[key]);
406 | return;
407 | }
408 |
409 | });
410 |
411 | // Auto generate an APIKey if none defined yet
412 | if ($("input[name='apiKey']").val() == "") {
413 | doGenerateAPIKey();
414 | }
415 |
416 | }
417 |
418 | function getJson(str) {
419 | try {
420 | return JSON.parse(str);
421 | } catch (e) {
422 | return false;
423 | }
424 | }
425 |
426 | function connect(host) {
427 |
428 | if (typeof host === 'undefined') {
429 | host = window.location.href.replace('#', '');
430 | } else {
431 | if (!host.startsWith("http")) {
432 | host = 'http://' + host + '/';
433 | }
434 | }
435 | webhost = host;
436 | wshost = host.replace('http', 'ws') + 'ws';
437 |
438 | if (websock) websock.close();
439 | websock = new WebSocket(wshost);
440 | websock.onopen = function(evt) {
441 | console.log("Connected");
442 | };
443 | websock.onclose = function(evt) {
444 | console.log("Disconnected");
445 | };
446 | websock.onerror = function(evt) {
447 | console.log("Error: ", evt);
448 | };
449 | websock.onmessage = function(evt) {
450 | var data = getJson(evt.data);
451 | if (data) processData(data);
452 | };
453 | }
454 |
455 | function initMessages() {
456 | messages[01] = "Remote update started";
457 | messages[02] = "OTA update started";
458 | messages[03] = "Error parsing data!";
459 | messages[04] = "The file does not look like a valid configuration backup or is corrupted";
460 | messages[05] = "Changes saved. You should reboot your board now";
461 | messages[06] = "Home Assistant auto-discovery message sent";
462 | messages[07] = "Passwords do not match!";
463 | messages[08] = "Changes saved";
464 | messages[09] = "No changes detected";
465 | messages[10] = "Session expired, please reload page...";
466 | }
467 |
468 | function init() {
469 |
470 | initMessages();
471 |
472 | $("#menuLink").on('click', toggleMenu);
473 | $(".button-update").on('click', doUpdate);
474 | $(".button-update-password").on('click', doUpdatePassword);
475 | $(".button-reset").on('click', doReset);
476 | $(".button-reconnect").on('click', doReconnect);
477 | $(".button-settings-backup").on('click', backupSettings);
478 | $(".button-settings-restore").on('click', restoreSettings);
479 | $('#uploader').on('change', onFileUpload);
480 | $(".button-apikey").on('click', doGenerateAPIKey);
481 | $(".button-upgrade").on('click', doUpgrade);
482 | $(".button-upgrade-browse").on('click', function() {
483 | $("input[name='upgrade']")[0].click();
484 | return false;
485 | });
486 | $("input[name='upgrade']").change(function (){
487 | var fileName = $(this).val();
488 | $("input[name='filename']").val(fileName.replace(/^.*[\\\/]/, ''));
489 | });
490 | $('progress').attr({ value: 0, max: 100 });
491 | $(".pure-menu-link").on('click', showPanel);
492 | $(".button-add-network").on('click', function() {
493 | $("div.more", addNetwork()).toggle();
494 | });
495 |
496 | var host = window.location.hostname;
497 | var port = location.port;
498 |
499 | $.ajax({
500 | 'method': 'GET',
501 | 'url': window.location.href + 'auth'
502 | }).done(function(data) {
503 | connect();
504 | }).fail(function(){
505 | $("#credentials").show();
506 | });
507 |
508 | }
509 |
510 | $(init);
511 |
--------------------------------------------------------------------------------
/esp8266/html/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/favicon.ico
--------------------------------------------------------------------------------
/esp8266/html/grids-responsive-min.css:
--------------------------------------------------------------------------------
1 | @media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}}
2 |
--------------------------------------------------------------------------------
/esp8266/html/images/border-off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/images/border-off.png
--------------------------------------------------------------------------------
/esp8266/html/images/border-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/images/border-on.png
--------------------------------------------------------------------------------
/esp8266/html/images/handle-center.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/images/handle-center.png
--------------------------------------------------------------------------------
/esp8266/html/images/handle-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/images/handle-left.png
--------------------------------------------------------------------------------
/esp8266/html/images/handle-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/images/handle-right.png
--------------------------------------------------------------------------------
/esp8266/html/images/label-off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/images/label-off.png
--------------------------------------------------------------------------------
/esp8266/html/images/label-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/html/images/label-on.png
--------------------------------------------------------------------------------
/esp8266/html/pure-min.css:
--------------------------------------------------------------------------------
1 | html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0}
2 |
--------------------------------------------------------------------------------
/esp8266/html/side-menu.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: #777;
3 | }
4 |
5 | .pure-img-responsive {
6 | max-width: 100%;
7 | height: auto;
8 | }
9 |
10 | /*
11 | Add transition to containers so they can push in and out.
12 | */
13 | #layout,
14 | #menu,
15 | .menu-link {
16 | -webkit-transition: all 0.2s ease-out;
17 | -moz-transition: all 0.2s ease-out;
18 | -ms-transition: all 0.2s ease-out;
19 | -o-transition: all 0.2s ease-out;
20 | transition: all 0.2s ease-out;
21 | }
22 |
23 | /*
24 | This is the parent `
` that contains the menu and the content area.
25 | */
26 | #layout {
27 | position: relative;
28 | padding-left: 0;
29 | }
30 | #layout.active #menu {
31 | left: 150px;
32 | width: 150px;
33 | }
34 |
35 | #layout.active .menu-link {
36 | left: 150px;
37 | }
38 | /*
39 | The content `
` is where all your content goes.
40 | */
41 | .content {
42 | margin: 0 auto;
43 | padding: 0 2em;
44 | max-width: 800px;
45 | margin-bottom: 50px;
46 | line-height: 1.6em;
47 | }
48 |
49 | .header {
50 | margin: 0;
51 | color: #333;
52 | text-align: center;
53 | padding: 2.5em 2em 0;
54 | border-bottom: 1px solid #eee;
55 | }
56 | .header h1 {
57 | margin: 0.2em 0;
58 | font-size: 3em;
59 | font-weight: 300;
60 | }
61 | .header h2 {
62 | font-weight: 300;
63 | color: #ccc;
64 | padding: 0;
65 | margin-top: 0;
66 | }
67 |
68 | .content-subhead {
69 | margin: 50px 0 20px 0;
70 | font-weight: 300;
71 | color: #888;
72 | }
73 |
74 |
75 |
76 | /*
77 | The `#menu` `
` is the parent `
` that contains the `.pure-menu` that
78 | appears on the left side of the page.
79 | */
80 |
81 | #menu {
82 | margin-left: -150px; /* "#menu" width */
83 | width: 150px;
84 | position: fixed;
85 | top: 0;
86 | left: 0;
87 | bottom: 0;
88 | z-index: 1000; /* so the menu or its navicon stays above all content */
89 | background: #191818;
90 | overflow-y: auto;
91 | -webkit-overflow-scrolling: touch;
92 | }
93 | /*
94 | All anchors inside the menu should be styled like this.
95 | */
96 | #menu a {
97 | color: #999;
98 | border: none;
99 | padding: 0.6em 0 0.6em 0.6em;
100 | }
101 |
102 | /*
103 | Remove all background/borders, since we are applying them to #menu.
104 | */
105 | #menu .pure-menu,
106 | #menu .pure-menu ul {
107 | border: none;
108 | background: transparent;
109 | }
110 |
111 | /*
112 | Add that light border to separate items into groups.
113 | */
114 | #menu .pure-menu ul,
115 | #menu .pure-menu .menu-item-divided {
116 | border-top: 1px solid #333;
117 | }
118 | /*
119 | Change color of the anchor links on hover/focus.
120 | */
121 | #menu .pure-menu li a:hover,
122 | #menu .pure-menu li a:focus {
123 | background: #333;
124 | }
125 |
126 | /*
127 | This styles the selected menu item `
`.
128 | */
129 | #menu .pure-menu-selected,
130 | #menu .pure-menu-heading {
131 | background: #1f8dd6;
132 | }
133 | /*
134 | This styles a link within a selected menu item ``.
135 | */
136 | #menu .pure-menu-selected a {
137 | color: #fff;
138 | }
139 |
140 | /*
141 | This styles the menu heading.
142 | */
143 | #menu .pure-menu-heading {
144 | font-size: 110%;
145 | color: #fff;
146 | margin: 0;
147 | }
148 |
149 | /* -- Dynamic Button For Responsive Menu -------------------------------------*/
150 |
151 | /*
152 | The button to open/close the Menu is custom-made and not part of Pure. Here's
153 | how it works:
154 | */
155 |
156 | /*
157 | `.menu-link` represents the responsive menu toggle that shows/hides on
158 | small screens.
159 | */
160 | .menu-link {
161 | position: fixed;
162 | display: block; /* show this only on small screens */
163 | top: 0;
164 | left: 0; /* "#menu width" */
165 | background: #000;
166 | background: rgba(0,0,0,0.7);
167 | font-size: 10px; /* change this value to increase/decrease button size */
168 | z-index: 10;
169 | width: 2em;
170 | height: auto;
171 | padding: 2.1em 1.6em;
172 | }
173 |
174 | .menu-link:hover,
175 | .menu-link:focus {
176 | background: #000;
177 | }
178 |
179 | .menu-link span {
180 | position: relative;
181 | display: block;
182 | }
183 |
184 | .menu-link span,
185 | .menu-link span:before,
186 | .menu-link span:after {
187 | background-color: #fff;
188 | width: 100%;
189 | height: 0.2em;
190 | }
191 |
192 | .menu-link span:before,
193 | .menu-link span:after {
194 | position: absolute;
195 | margin-top: -0.6em;
196 | content: " ";
197 | }
198 |
199 | .menu-link span:after {
200 | margin-top: 0.6em;
201 | }
202 |
203 |
204 | /* -- Responsive Styles (Media Queries) ------------------------------------- */
205 |
206 | /*
207 | Hides the menu at `48em`, but modify this based on your app's needs.
208 | */
209 | @media (min-width: 48em) {
210 |
211 | .header,
212 | .content {
213 | padding-left: 2em;
214 | padding-right: 2em;
215 | }
216 |
217 | #layout {
218 | padding-left: 150px; /* left col width "#menu" */
219 | left: 0;
220 | }
221 | #menu {
222 | left: 150px;
223 | }
224 |
225 | .menu-link {
226 | position: fixed;
227 | left: 150px;
228 | display: none;
229 | }
230 |
231 | #layout.active .menu-link {
232 | left: 150px;
233 | }
234 | }
235 |
236 | @media (max-width: 48em) {
237 | /* Only apply this when the window is small. Otherwise, the following
238 | case results in extra padding on the left:
239 | * Make the window small.
240 | * Tap the menu to trigger the active state.
241 | * Make the window large again.
242 | */
243 | #layout.active {
244 | position: relative;
245 | left: 150px;
246 | }
247 | }
248 |
249 |
--------------------------------------------------------------------------------
/esp8266/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "esp8266-filesystem-builder",
3 | "version": "0.1.0",
4 | "description": "Gulp based build system for ESP8266 file system files",
5 | "main": "gulpfile.js",
6 | "author": "Xose Pérez ",
7 | "license": "GPL-3.0",
8 | "devDependencies": {
9 | "del": "^2.2.1",
10 | "gulp": "^3.9.1",
11 | "gulp-base64-favicon": "^1.0.2",
12 | "gulp-clean-css": "^3.4.2",
13 | "gulp-css-base64": "^1.3.4",
14 | "gulp-gzip": "^1.4.0",
15 | "gulp-htmlmin": "^2.0.0",
16 | "gulp-inline": "^0.1.1",
17 | "gulp-uglify": "^1.5.3"
18 | },
19 | "dependencies": {}
20 | }
21 |
--------------------------------------------------------------------------------
/esp8266/platformio.ini:
--------------------------------------------------------------------------------
1 | ; PlatformIO Project Configuration File
2 | ;
3 | ; Build options: build flags, source filter, extra scripting
4 | ; Upload options: custom port, speed and extra flags
5 | ; Library options: dependencies, extra library storages
6 | ;
7 | ; Please visit documentation for the other options and examples
8 | ; http://docs.platformio.org/en/stable/projectconf.html
9 |
10 | [platformio]
11 | env_default = sonoffsc
12 | src_dir = sonoffsc
13 | data_dir = sonoffsc/data
14 |
15 | [common]
16 | lib_deps =
17 | ArduinoJson
18 | https://github.com/xoseperez/Time
19 | https://github.com/me-no-dev/ESPAsyncTCP#991f855
20 | https://github.com/me-no-dev/ESPAsyncWebServer#a94265d
21 | https://github.com/marvinroger/async-mqtt-client#v0.8.1
22 | PubSubClient
23 | Embedis
24 | NtpClientLib
25 | https://github.com/xoseperez/seriallink#0.1.0
26 | https://bitbucket.org/xoseperez/justwifi.git#1.1.4
27 | https://bitbucket.org/xoseperez/fauxmoesp.git#2.2.0
28 | https://bitbucket.org/xoseperez/nofuss.git#0.2.5
29 | https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
30 | build_flags = -g -Wl,-Tesp8266.flash.1m64.ld
31 |
32 | [env:sonoffsc]
33 | platform = https://github.com/platformio/platform-espressif8266.git#v1.5.0
34 | framework = arduino
35 | board = esp01_1m
36 | flash_mode = dio
37 | lib_deps = ${common.lib_deps}
38 | build_flags = ${common.build_flags}
39 |
40 | [env:ota]
41 | platform = https://github.com/platformio/platform-espressif8266.git#v1.5.0
42 | framework = arduino
43 | board = esp01_1m
44 | flash_mode = dio
45 | lib_deps = ${common.lib_deps}
46 | build_flags = ${common.build_flags}
47 | upload_speed = 115200
48 | upload_port = "sonoffsc.local"
49 | upload_flags = --auth=Algernon1 --port 8266
50 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/alexa.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | ALEXA MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | #if ALEXA_SUPPORT
10 |
11 | #include
12 |
13 | fauxmoESP alexa;
14 |
15 | // -----------------------------------------------------------------------------
16 | // ALEXA
17 | // -----------------------------------------------------------------------------
18 |
19 | void alexaConfigure() {
20 | alexa.enable(getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1);
21 | }
22 |
23 | void alexaSetup() {
24 |
25 | alexa.addDevice("clap");
26 |
27 | alexa.onMessage([](unsigned char device_id, const char * name, bool state) {
28 |
29 | DEBUG_MSG("[FAUXMO] %s state: %s\n", name, state ? "ON" : "OFF");
30 |
31 | if (strcmp(name, "clap") == 0) {
32 | setSetting("clapEnabled", state);
33 | if (state) {
34 | wsSend((char *) "{\"clapEnabled\": true}");
35 | } else {
36 | wsSend((char *) "{\"clapEnabled\": false}");
37 | }
38 | commsConfigure();
39 | }
40 |
41 | });
42 |
43 | }
44 |
45 | void alexaLoop() {
46 | alexa.handle();
47 | }
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/button.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | BUTTON MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | // -----------------------------------------------------------------------------
10 | // BUTTON
11 | // -----------------------------------------------------------------------------
12 |
13 | #include
14 |
15 | DebounceEvent _button = DebounceEvent(BUTTON_PIN, BUTTON_PUSHBUTTON, BUTTON_DEBOUNCE_DELAY, BUTTON_DBLCLICK_DELAY);
16 |
17 | void buttonSetup() {
18 | }
19 |
20 | void buttonLoop() {
21 |
22 | if (uint8_t event = _button.loop()) {
23 |
24 | if (event == EVENT_RELEASED) {
25 |
26 | DEBUG_MSG("[BUTTON] Button pressed. Event: %d Length:%d\n", _button.getEventCount(), _button.getEventLength());
27 |
28 | if (_button.getEventCount() == 1) {
29 |
30 | if(_button.getEventLength() >= BUTTON_LNGLNGCLICK_DELAY) {
31 | customReset(CUSTOM_RESET_HARDWARE);
32 | ESP.restart();
33 | }
34 |
35 | if(_button.getEventLength() >= BUTTON_LNGCLICK_DELAY) {
36 | DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
37 | settingsFactoryReset();
38 | customReset(CUSTOM_RESET_FACTORY);
39 | ESP.restart();
40 | }
41 |
42 | }
43 |
44 | if (_button.getEventCount() >= 2) {
45 | createAP();
46 | }
47 |
48 | }
49 |
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/comms.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | COMMUNICATIONS MODULE
4 |
5 | Copyright (C) 2016 by Xose Pérez
6 |
7 | */
8 |
9 | #include "SerialLink.h"
10 |
11 | SerialLink link(Serial, false);
12 |
13 | const PROGMEM char at_hello[] = "AT+HELLO";
14 | const PROGMEM char at_push[] = "AT+PUSH";
15 | const PROGMEM char at_every[] = "AT+EVERY";
16 | const PROGMEM char at_temp[] = "AT+TEMP";
17 | const PROGMEM char at_hum[] = "AT+HUM";
18 | const PROGMEM char at_dust[] = "AT+DUST";
19 | const PROGMEM char at_noise[] = "AT+NOISE";
20 | const PROGMEM char at_light[] = "AT+LIGHT";
21 | const PROGMEM char at_clap[] = "AT+CLAP";
22 | const PROGMEM char at_code[] = "AT+CODE";
23 | const PROGMEM char at_thld[] = "AT+THLD";
24 | const PROGMEM char at_fan[] = "AT+FAN";
25 | const PROGMEM char at_fanoff[] = "AT+FANOFF";
26 | const PROGMEM char at_timeout[] = "AT+TIMEOUT";
27 | const PROGMEM char at_effect[] = "AT+EFFECT";
28 | const PROGMEM char at_color[] = "AT+COLOR";
29 | const PROGMEM char at_bright[] = "AT+BRIGHT";
30 | const PROGMEM char at_speed[] = "AT+SPEED";
31 | const PROGMEM char at_move[] = "AT+MOVE";
32 |
33 | // -----------------------------------------------------------------------------
34 | // VALUES
35 | // -----------------------------------------------------------------------------
36 |
37 | float temperature;
38 | int humidity;
39 | int light;
40 | float dust;
41 | int noise;
42 | bool movement;
43 |
44 | bool gotResponse = false;
45 | long response;
46 |
47 | float getTemperature() { return temperature; }
48 | float getHumidity() { return humidity; }
49 | float getLight() { return light; }
50 | float getDust() { return dust; }
51 | float getNoise() { return noise; }
52 | float getMovement() { return movement; }
53 |
54 | // -----------------------------------------------------------------------------
55 | // COMMUNICATIONS
56 | // -----------------------------------------------------------------------------
57 |
58 | bool commsGet(char * key) {
59 | return false;
60 | }
61 |
62 | bool commsSet(char * key, long value) {
63 |
64 | char buffer[50];
65 |
66 | if (strcmp_P(key, at_code) == 0) {
67 | mqttSend(getSetting("mqttTopicClap", MQTT_TOPIC_CLAP).c_str(), String(value).c_str());
68 | return true;
69 | }
70 |
71 | if (strcmp_P(key, at_temp) == 0) {
72 | temperature = (float) value / 10;
73 | if (temperature < SENSOR_TEMPERATURE_MIN || SENSOR_TEMPERATURE_MAX < temperature) return false;
74 | mqttSend(getSetting("mqttTopicTemp", MQTT_TOPIC_TEMPERATURE).c_str(), String(temperature).c_str());
75 | domoticzSend("dczIdxTemp", temperature);
76 | sprintf(buffer, "{\"sensorTemp\": %s}", String(temperature).c_str());
77 | wsSend(buffer);
78 | return true;
79 | }
80 |
81 | if (strcmp_P(key, at_hum) == 0) {
82 | humidity = value;
83 | if (humidity < SENSOR_HUMIDITY_MIN || SENSOR_HUMIDITY_MAX < humidity) return false;
84 | mqttSend(getSetting("mqttTopicHum", MQTT_TOPIC_HUMIDITY).c_str(), String(humidity).c_str());
85 | domoticzSend("dczIdxHum", humidity);
86 | sprintf(buffer, "{\"sensorHum\": %d}", humidity);
87 | wsSend(buffer);
88 | return true;
89 | }
90 |
91 | if (strcmp_P(key, at_light) == 0) {
92 | light = value;
93 | if (light < 0 || 100 < light) return false;
94 | mqttSend(getSetting("mqttTopicLight", MQTT_TOPIC_LIGHT).c_str(), String(light).c_str());
95 | domoticzSend("dczIdxLight", light);
96 | sprintf(buffer, "{\"sensorLight\": %d}", light);
97 | wsSend(buffer);
98 | return true;
99 | }
100 |
101 | if (strcmp_P(key, at_dust) == 0) {
102 | dust = (float) value / 100;
103 | if (dust < SENSOR_DUST_MIN || SENSOR_DUST_MAX < dust) return false;
104 | mqttSend(getSetting("mqttTopicDust", MQTT_TOPIC_DUST).c_str(), String(dust).c_str());
105 | domoticzSend("dczIdxDust", dust);
106 | sprintf(buffer, "{\"sensorDust\": %s}", String(dust).c_str());
107 | wsSend(buffer);
108 | return true;
109 | }
110 |
111 | if (strcmp_P(key, at_noise) == 0) {
112 | noise = value;
113 | if (noise < 0 || 100 < noise) return false;
114 | mqttSend(getSetting("mqttTopicNoise", MQTT_TOPIC_NOISE).c_str(), String(noise).c_str());
115 | domoticzSend("dczIdxNoise", noise);
116 | sprintf(buffer, "{\"sensorNoise\": %d}", noise);
117 | wsSend(buffer);
118 | return true;
119 | }
120 |
121 | if (strcmp_P(key, at_move) == 0) {
122 | movement = value;
123 | mqttSend(getSetting("mqttTopicMovement", MQTT_TOPIC_MOVE).c_str(), movement ? "1" : "0");
124 | domoticzSend("dczIdxMovement", movement);
125 | sprintf(buffer, "{\"sensorMove\": %d}", movement ? 1 : 0);
126 | wsSend(buffer);
127 | #if LOCAL_NOTIFICATION
128 | sendNotification(movement);
129 | #endif
130 | return true;
131 | }
132 |
133 | gotResponse = true;
134 | response = value;
135 |
136 | return true;
137 |
138 | }
139 |
140 | bool send_P_repeat(const char * command, long payload, unsigned char tries = COMMS_DEFAULT_TRIES) {
141 | link.clear();
142 | while (tries--) {
143 | delay(50);
144 | link.send_P(command, payload);
145 | }
146 | }
147 |
148 | void commsConfigure() {
149 | link.clear();
150 | delay(200);
151 | send_P_repeat(at_every, getSetting("sensorEvery", SENSOR_EVERY).toInt());
152 | send_P_repeat(at_clap, getSetting("clapEnabled", SENSOR_CLAP_ENABLED).toInt() == 1 ? 1 : 0);
153 | send_P_repeat(at_push,1);
154 | }
155 |
156 | void commsSetup() {
157 |
158 | link.onGet(commsGet);
159 | link.onSet(commsSet);
160 | link.clear();
161 | delay(200);
162 |
163 | // Set FAN mode depending on delay
164 | send_P_repeat(at_fanoff, FAN_DELAY);
165 | send_P_repeat(at_fan, FAN_DELAY == 0);
166 |
167 | }
168 |
169 | void commsLoop() {
170 | link.handle();
171 | }
172 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/config/all.h:
--------------------------------------------------------------------------------
1 | #include "version.h"
2 | #include "arduino.h"
3 | #include "general.h"
4 | #include "prototypes.h"
5 |
6 | /*
7 | If you want to modify the stock configuration but you don't want to touch
8 | the repo files you can either define USE_CUSTOM_H or remove the
9 | "#ifdef USE_CUSTOM_H" & "#endif" lines and add a "custom.h"
10 | file to this same folder.
11 | Check https://bitbucket.org/xoseperez/espurna/issues/104/general_customh
12 | for an example on how to use this file.
13 | (Define USE_CUSTOM_H on commandline for platformio:
14 | export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" )
15 | */
16 | #ifdef USE_CUSTOM_H
17 | #include "custom.h"
18 | #endif
19 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/config/arduino.h:
--------------------------------------------------------------------------------
1 | //--------------------------------------------------------------------------------
2 | // These settings are normally provided by PlatformIO
3 | // Uncomment the appropiate line(s) to build from the Arduino IDE
4 | //--------------------------------------------------------------------------------
5 |
6 | //--------------------------------------------------------------------------------
7 | // Features (values below are non-default values)
8 | //--------------------------------------------------------------------------------
9 |
10 | //#define ALEXA_SUPPORT 0
11 | //#define DEBUG_SERIAL_SUPPORT 0
12 | //#define DEBUG_TELNET_SUPPORT 0
13 | //#define DEBUG_UDP_SUPPORT 1
14 | //#define DOMOTICZ_SUPPORT 0
15 | //#define MDNS_SUPPORT 0
16 | //#define NOFUSS_SUPPORT 1
17 | //#define NTP_SUPPORT 0
18 | //#define SPIFFS_SUPPORT 1
19 | //#define TELNET_SUPPORT 0
20 | //#define TERMINAL_SUPPORT 0
21 | //#define WEB_SUPPORT 0
22 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/config/general.h:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | // GENERAL
3 | //------------------------------------------------------------------------------
4 |
5 | #define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI)
6 | #define SERIAL_BAUDRATE 9600
7 | #define DEVICE_NAME APP_NAME
8 | #define MANUFACTURER "ITEAD STUDIO"
9 |
10 | //------------------------------------------------------------------------------
11 | // TELNET
12 | //------------------------------------------------------------------------------
13 |
14 | #ifndef TELNET_SUPPORT
15 | #define TELNET_SUPPORT 1 // Enable telnet support by default
16 | #endif
17 |
18 | #ifndef TELNET_ONLY_AP
19 | #define TELNET_ONLY_AP 0 // By default, allow only connections via AP interface
20 | #endif
21 |
22 | #define TELNET_PORT 23 // Port to listen to telnet clients
23 | #define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients
24 |
25 | //------------------------------------------------------------------------------
26 | // DEBUG
27 | //------------------------------------------------------------------------------
28 |
29 | // Serial debug log
30 |
31 | #ifndef DEBUG_SERIAL_SUPPORT
32 | #define DEBUG_SERIAL_SUPPORT 0 // Do not enable serial debug log
33 | #endif
34 | #ifndef DEBUG_PORT
35 | #define DEBUG_PORT Serial // Default debugging port
36 | #endif
37 |
38 | //------------------------------------------------------------------------------
39 |
40 | // UDP debug log
41 | // To receive the message son the destination computer use nc:
42 | // nc -ul 8113
43 |
44 | #ifndef DEBUG_UDP_SUPPORT
45 | #define DEBUG_UDP_SUPPORT 0 // Enable UDP debug log
46 | #endif
47 | #define DEBUG_UDP_IP IPAddress(192, 168, 1, 100)
48 | #define DEBUG_UDP_PORT 8113
49 |
50 | //------------------------------------------------------------------------------
51 |
52 | #ifndef DEBUG_TELNET_SUPPORT
53 | #define DEBUG_TELNET_SUPPORT TELNET_SUPPORT // Enable telnet debug log if telnet is enabled too
54 | #endif
55 |
56 | //------------------------------------------------------------------------------
57 |
58 | // General debug options and macros
59 | #define DEBUG_MESSAGE_MAX_LENGTH 80
60 | #define DEBUG_SUPPORT DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT || DEBUG_TELNET_SUPPORT
61 |
62 |
63 | #if DEBUG_SUPPORT
64 | #define DEBUG_MSG(...) debugSend(__VA_ARGS__)
65 | #define DEBUG_MSG_P(...) debugSend_P(__VA_ARGS__)
66 | #endif
67 |
68 | #ifndef DEBUG_MSG
69 | #define DEBUG_MSG(...)
70 | #define DEBUG_MSG_P(...)
71 | #endif
72 |
73 | //------------------------------------------------------------------------------
74 | // TERMINAL
75 | //------------------------------------------------------------------------------
76 |
77 | #ifndef TERMINAL_SUPPORT
78 | #define TERMINAL_SUPPORT 0 // Terminal support conflicts comms
79 | #endif
80 |
81 | //------------------------------------------------------------------------------
82 | // CRASH
83 | //------------------------------------------------------------------------------
84 |
85 | #define CRASH_SAFE_TIME 60000 // The system is considered stable after these many millis
86 | #define CRASH_COUNT_MAX 5 // After this many crashes on boot
87 | // the system is flagged as unstable
88 |
89 | //------------------------------------------------------------------------------
90 | // EEPROM
91 | //------------------------------------------------------------------------------
92 |
93 | #define EEPROM_SIZE 4096 // EEPROM size in bytes
94 | #define EEPROM_CUSTOM_RESET 0 // Address for the reset reason (1 byte)
95 | #define EEPROM_CRASH_COUNTER 1 // Address for the crash counter (1 byte)
96 | #define EEPROM_DATA_END 2 // End of custom EEPROM data block
97 |
98 | //------------------------------------------------------------------------------
99 | // HEARTBEAT
100 | //------------------------------------------------------------------------------
101 |
102 | #define HEARTBEAT_INTERVAL 300000 // Interval between heartbeat messages (in ms)
103 | #define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
104 |
105 | //------------------------------------------------------------------------------
106 | // RESET
107 | //------------------------------------------------------------------------------
108 |
109 | #define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button
110 | #define CUSTOM_RESET_WEB 2 // Reset from web interface
111 | #define CUSTOM_RESET_TERMINAL 3 // Reset from terminal
112 | #define CUSTOM_RESET_MQTT 4 // Reset via MQTT
113 | #define CUSTOM_RESET_RPC 5 // Reset via RPC (HTTP)
114 | #define CUSTOM_RESET_OTA 6 // Reset after successful OTA update
115 | #define CUSTOM_RESET_NOFUSS 8 // Reset after successful NOFUSS update
116 | #define CUSTOM_RESET_UPGRADE 9 // Reset after update from web interface
117 | #define CUSTOM_RESET_FACTORY 10 // Factory reset from terminal
118 |
119 | #define CUSTOM_RESET_MAX 10
120 |
121 | #include
122 |
123 | PROGMEM const char custom_reset_hardware[] = "Hardware button";
124 | PROGMEM const char custom_reset_web[] = "Reset from web interface";
125 | PROGMEM const char custom_reset_terminal[] = "Reset from terminal";
126 | PROGMEM const char custom_reset_mqtt[] = "Reset from MQTT";
127 | PROGMEM const char custom_reset_rpc[] = "Reset from RPC";
128 | PROGMEM const char custom_reset_ota[] = "Reset after successful OTA update";
129 | PROGMEM const char custom_reset_nofuss[] = "Reset after successful NoFUSS update";
130 | PROGMEM const char custom_reset_upgrade[] = "Reset after successful web update";
131 | PROGMEM const char custom_reset_factory[] = "Factory reset";
132 | PROGMEM const char* const custom_reset_string[] = {
133 | custom_reset_hardware, custom_reset_web, custom_reset_terminal,
134 | custom_reset_mqtt, custom_reset_rpc, custom_reset_ota,
135 | custom_reset_nofuss, custom_reset_upgrade, custom_reset_factory
136 | };
137 |
138 | //------------------------------------------------------------------------------
139 | // BUTTON
140 | //------------------------------------------------------------------------------
141 |
142 | #define BUTTON_DEBOUNCE_DELAY 50 // Debounce delay (ms)
143 | #define BUTTON_DBLCLICK_DELAY 500 // Time in ms to wait for a second (or third...) click
144 | #define BUTTON_LNGCLICK_DELAY 1000 // Time in ms holding the button down to get a long click
145 | #define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
146 |
147 | #define BUTTON_PIN 0
148 |
149 | // -----------------------------------------------------------------------------
150 | // WIFI
151 | // -----------------------------------------------------------------------------
152 |
153 | #define WIFI_CONNECT_TIMEOUT 30000 // Connecting timeout for WIFI in ms
154 | #define WIFI_RECONNECT_INTERVAL 300000
155 | #define WIFI_MAX_NETWORKS 5
156 | #define WIFI_AP_MODE AP_MODE_ALONE
157 |
158 | // Optional hardcoded configuration (up to 2 different networks)
159 | //#define WIFI1_SSID "..."
160 | //#define WIFI1_PASS "..."
161 | //#define WIFI1_IP "192.168.1.201"
162 | //#define WIFI1_GW "192.168.1.1"
163 | //#define WIFI1_MASK "255.255.255.0"
164 | //#define WIFI1_DNS "8.8.8.8"
165 | //#define WIFI2_SSID "..."
166 | //#define WIFI2_PASS "..."
167 |
168 | // -----------------------------------------------------------------------------
169 | // WEB
170 | // -----------------------------------------------------------------------------
171 |
172 | #ifndef WEB_SUPPORT
173 | #define WEB_SUPPORT 1 // Enable web support (http, api)
174 | #endif
175 |
176 | #ifndef WEB_EMBEDDED
177 | #define WEB_EMBEDDED 1 // Build the firmware with the web interface embedded in
178 | #endif
179 |
180 | // This is not working at the moment!!
181 | // Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core staging version.
182 | #define WEB_SSL_ENABLED 0 // Use HTTPS web interface
183 |
184 | #define WEB_MODE_NORMAL 0
185 | #define WEB_MODE_PASSWORD 1
186 |
187 | #define WEB_USERNAME "admin" // HTTP username
188 | #define WEB_FORCE_PASS_CHANGE 1 // Force the user to change the password if default one
189 | #define WEB_PORT 80 // HTTP port
190 |
191 | // -----------------------------------------------------------------------------
192 | // WEBSOCKETS
193 | // -----------------------------------------------------------------------------
194 |
195 | // This will only be enabled if WEB_SUPPORT is 1 (this is the default value)
196 |
197 | #define WS_BUFFER_SIZE 5 // Max number of secured websocket connections
198 | #define WS_TIMEOUT 1800000 // Timeout for secured websocket
199 |
200 | // -----------------------------------------------------------------------------
201 | // API
202 | // -----------------------------------------------------------------------------
203 |
204 | // This will only be enabled if WEB_SUPPORT is 1 (this is the default value)
205 |
206 | #define API_ENABLED 0 // Do not enable API by default
207 | #define API_BUFFER_SIZE 10 // Size of the buffer for HTTP GET API responses
208 |
209 | // -----------------------------------------------------------------------------
210 | // MDNS
211 | // -----------------------------------------------------------------------------
212 |
213 | #ifndef MDNS_SUPPORT
214 | #define MDNS_SUPPORT 1 // Publish services using mDNS by default
215 | #endif
216 |
217 | // -----------------------------------------------------------------------------
218 | // SPIFFS
219 | // -----------------------------------------------------------------------------
220 |
221 | #ifndef SPIFFS_SUPPORT
222 | #define SPIFFS_SUPPORT 0 // Do not add support for SPIFFS by default
223 | #endif
224 |
225 | // -----------------------------------------------------------------------------
226 | // OTA
227 | // -----------------------------------------------------------------------------
228 |
229 | #define OTA_PORT 8266 // OTA port
230 |
231 | // -----------------------------------------------------------------------------
232 | // NOFUSS
233 | // -----------------------------------------------------------------------------
234 |
235 | #ifndef NOFUSS_SUPPORT
236 | #define NOFUSS_SUPPORT 0 // Do not enable support for NoFuss by default
237 | #endif
238 |
239 | #define NOFUSS_ENABLED 0 // Do not perform NoFUSS updates by default
240 | #define NOFUSS_SERVER "" // Default NoFuss Server
241 | #define NOFUSS_INTERVAL 3600000 // Check for updates every hour
242 |
243 | // -----------------------------------------------------------------------------
244 | // MQTT
245 | // -----------------------------------------------------------------------------
246 |
247 | #ifndef MQTT_USE_ASYNC
248 | #define MQTT_USE_ASYNC 1 // Use AysncMQTTClient (1) or PubSubClient (0)
249 | #endif
250 |
251 | // MQTT OVER SSL
252 | // Using MQTT over SSL works pretty well but generates problems with the web interface.
253 | // It could be a good idea to use it in conjuntion with WEB_SUPPORT=0.
254 | // Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core staging version.
255 | //
256 | // You can use it with MQTT_USE_ASYNC=1 (AsyncMqttClient library)
257 | // but you might experience hiccups on the web interface, so my recommendation is:
258 | // WEB_SUPPORT=0
259 | //
260 | // If you use it with MQTT_USE_ASYNC=0 (PubSubClient library)
261 | // you will have to disable all the modules that use ESPAsyncTCP, that is:
262 | // ALEXA_SUPPORT=0, INFLUXDB_SUPPORT=0, TELNET_SUPPORT=0 and WEB_SUPPORT=0
263 | //
264 | // You will need the fingerprint for your MQTT server, example for CloudMQTT:
265 | // $ echo -n | openssl s_client -connect m11.cloudmqtt.com:24055 > cloudmqtt.pem
266 | // $ openssl x509 -noout -in cloudmqtt.pem -fingerprint -sha1
267 |
268 | #define MQTT_SSL_ENABLED 0 // By default MQTT over SSL will not be enabled
269 | #define MQTT_SSL_FINGERPRINT "" // SSL fingerprint of the server
270 |
271 | #define MQTT_ENABLED 0 // Do not enable MQTT connection by default
272 | #define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SUPPORT=1 will perform an autodiscover and
273 |
274 | #define MQTT_SERVER "" // Default MQTT broker address
275 | #define MQTT_USER "" // Default MQTT broker usename
276 | #define MQTT_PASS "" // Default MQTT broker password
277 | #define MQTT_PORT 1883 // MQTT broker port
278 | #define MQTT_TOPIC "/test/sonoffsc" // Default MQTT base topic
279 | #define MQTT_RETAIN true // MQTT retain flag
280 | #define MQTT_QOS 0 // MQTT QoS value for all messages
281 | #define MQTT_KEEPALIVE 30 // MQTT keepalive value
282 |
283 | #define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection
284 | #define MQTT_RECONNECT_DELAY_STEP 5000 // Increase the reconnect delay in 5 seconds after each failed attempt
285 | #define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
286 |
287 | #define MQTT_SKIP_RETAINED 1 // Skip retained messages on connection
288 | #define MQTT_SKIP_TIME 1000 // Skip messages for 1 second anter connection
289 |
290 | #define MQTT_USE_JSON 0 // Group messages in a JSON body
291 | #define MQTT_USE_JSON_DELAY 100 // Wait this many ms before grouping messages
292 |
293 | // Internal MQTT events (do not change)
294 | #define MQTT_CONNECT_EVENT 0
295 | #define MQTT_DISCONNECT_EVENT 1
296 | #define MQTT_MESSAGE_EVENT 2
297 |
298 | // Topics
299 | #define MQTT_TOPIC_STATUS "status"
300 | #define MQTT_TOPIC_IP "ip"
301 | #define MQTT_TOPIC_VERSION "version"
302 | #define MQTT_TOPIC_HEARTBEAT "heartbeat"
303 | #define MQTT_TOPIC_MODE "mode"
304 | #define MQTT_TOPIC_INTERVAL "interval"
305 | #define MQTT_TOPIC_TEMPERATURE "temperature"
306 | #define MQTT_TOPIC_HUMIDITY "humidity"
307 | #define MQTT_TOPIC_NOISE "noise"
308 | #define MQTT_TOPIC_CLAP "clap"
309 | #define MQTT_TOPIC_DUST "dust"
310 | #define MQTT_TOPIC_LIGHT "light"
311 | #define MQTT_TOPIC_RGB "color"
312 | #define MQTT_TOPIC_BRIGHTNESS "brightness"
313 | #define MQTT_TOPIC_SPEED "speed"
314 | #define MQTT_TOPIC_EFFECT "effect"
315 | #define MQTT_TOPIC_MOVE "movement"
316 |
317 | #define MQTT_TOPIC_TIME "time"
318 | #define MQTT_TOPIC_HOSTNAME "host"
319 |
320 | #define MQTT_TOPIC_JSON "data"
321 | #define MQTT_TOPIC_ACTION "action"
322 | #define MQTT_ACTION_RESET "reset"
323 |
324 | // Custom get and set postfixes
325 | // Use something like "/status" or "/set", with leading slash
326 | // Since 1.9.0 the default value is "" for getter and "/set" for setter
327 | #define MQTT_USE_GETTER ""
328 | #define MQTT_USE_SETTER "/set"
329 |
330 | // -----------------------------------------------------------------------------
331 | // SETTINGS
332 | // -----------------------------------------------------------------------------
333 |
334 | #ifndef SETTINGS_AUTOSAVE
335 | #define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit
336 | #endif
337 |
338 | // -----------------------------------------------------------------------------
339 | // DOMOTICZ
340 | // -----------------------------------------------------------------------------
341 |
342 | #ifndef DOMOTICZ_SUPPORT
343 | #define DOMOTICZ_SUPPORT 1 // Build with domoticz support
344 | #endif
345 |
346 | #define DOMOTICZ_ENABLED 0 // Disable domoticz by default
347 | #define DOMOTICZ_IN_TOPIC "domoticz/in" // Default subscription topic
348 | #define DOMOTICZ_OUT_TOPIC "domoticz/out" // Default publication topic
349 |
350 | // -----------------------------------------------------------------------------
351 | // NTP
352 | // -----------------------------------------------------------------------------
353 |
354 | #ifndef NTP_SUPPORT
355 | #define NTP_SUPPORT 1 // Build with NTP support by default
356 | #endif
357 |
358 | #define NTP_SERVER "pool.ntp.org" // Default NTP server
359 | #define NTP_TIME_OFFSET 1 // Default timezone offset (GMT+1)
360 | #define NTP_DAY_LIGHT true // Enable daylight time saving by default
361 | #define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes
362 |
363 | // -----------------------------------------------------------------------------
364 | // COMMS
365 | // -----------------------------------------------------------------------------
366 |
367 | #define COMMS_DEFAULT_TRIES 1
368 |
369 | // -----------------------------------------------------------------------------
370 | // NOTIFICATIONS
371 | // -----------------------------------------------------------------------------
372 |
373 | #define LOCAL_NOTIFICATION 0
374 | #define NOTIFICATION_EFFECT 12
375 | #define NOTIFICATION_SPEED 128
376 | #define NOTIFICATION_COLOR 255UL // Blue
377 | #define NOTIFICATION_BRIGHTNESS 255
378 | #define NOTIFICATION_TIME 30 // Seconds
379 |
380 | // -----------------------------------------------------------------------------
381 | // SENSORS
382 | // -----------------------------------------------------------------------------
383 |
384 | #define SENSOR_EVERY 60
385 | #define SENSOR_CLAP_ENABLED 1
386 |
387 | #define SENSOR_TEMPERATURE_MIN -10
388 | #define SENSOR_TEMPERATURE_MAX 50
389 | #define SENSOR_PRESSURE_MIN 870
390 | #define SENSOR_PRESSURE_MAX 1100
391 | #define SENSOR_DUST_MIN 0
392 | #define SENSOR_DUST_MAX 1
393 | #define SENSOR_HUMIDITY_MIN 10
394 | #define SENSOR_HUMIDITY_MAX 100
395 |
396 | #define FAN_DELAY 0
397 |
398 | // -----------------------------------------------------------------------------
399 | // LED
400 | // -----------------------------------------------------------------------------
401 |
402 | #define LED_PIN 13
403 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/config/prototypes.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | typedef std::function apiGetCallbackFunction;
8 | typedef std::function apiPutCallbackFunction;
9 | void apiRegister(const char * url, const char * key, apiGetCallbackFunction getFn, apiPutCallbackFunction putFn = NULL);
10 |
11 | void mqttRegister(void (*callback)(unsigned int, const char *, const char *));
12 | String mqttSubtopic(char * topic);
13 |
14 | template bool setSetting(const String& key, T value);
15 | template bool setSetting(const String& key, unsigned int index, T value);
16 | template String getSetting(const String& key, T defaultValue);
17 | template String getSetting(const String& key, unsigned int index, T defaultValue);
18 | template void domoticzSend(const char * key, T value);
19 | template void domoticzSend(const char * key, T nvalue, const char * svalue);
20 |
21 |
22 | char * ltrim(char * s);
23 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/config/version.h:
--------------------------------------------------------------------------------
1 | #define APP_NAME "SONOFFSC"
2 | #define APP_VERSION "1.1.1"
3 | #define APP_AUTHOR "xose.perez@gmail.com"
4 | #define APP_WEBSITE "http://tinkerman.cat"
5 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/data/index.html.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xoseperez/sonoffsc/b8c438245e61029ce07e9a8f18b2618cd97500b5/esp8266/sonoffsc/data/index.html.gz
--------------------------------------------------------------------------------
/esp8266/sonoffsc/debug.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | DEBUG MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | #if DEBUG_SUPPORT
10 |
11 | #include
12 | #include
13 | #include
14 |
15 | extern "C" {
16 | #include "user_interface.h"
17 | }
18 |
19 | #if DEBUG_UDP_SUPPORT
20 | #include
21 | WiFiUDP udpDebug;
22 | #endif
23 |
24 | void debugSend(const char * format, ...) {
25 |
26 | char buffer[DEBUG_MESSAGE_MAX_LENGTH+1];
27 |
28 | va_list args;
29 | va_start(args, format);
30 | int len = ets_vsnprintf(buffer, DEBUG_MESSAGE_MAX_LENGTH, format, args);
31 | va_end(args);
32 |
33 | #if DEBUG_SERIAL_SUPPORT
34 | DEBUG_PORT.printf(buffer);
35 | if (len > DEBUG_MESSAGE_MAX_LENGTH) {
36 | DEBUG_PORT.printf(" (...)\n");
37 | }
38 | #endif
39 |
40 | #if DEBUG_UDP_SUPPORT
41 | if (systemCheck()) {
42 | udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
43 | udpDebug.write(buffer);
44 | if (len > DEBUG_MESSAGE_MAX_LENGTH) {
45 | udpDebug.write(" (...)\n");
46 | }
47 | udpDebug.endPacket();
48 | delay(1);
49 | }
50 | #endif
51 |
52 | #if DEBUG_TELNET_SUPPORT
53 | _telnetWrite(buffer, strlen(buffer));
54 | #endif
55 |
56 | }
57 |
58 | void debugSend_P(PGM_P format, ...) {
59 |
60 | char f[DEBUG_MESSAGE_MAX_LENGTH+1];
61 | memcpy_P(f, format, DEBUG_MESSAGE_MAX_LENGTH);
62 |
63 | char buffer[DEBUG_MESSAGE_MAX_LENGTH+1];
64 |
65 | va_list args;
66 | va_start(args, format);
67 | int len = ets_vsnprintf(buffer, DEBUG_MESSAGE_MAX_LENGTH, f, args);
68 | va_end(args);
69 |
70 | #if DEBUG_SERIAL_SUPPORT
71 | DEBUG_PORT.printf(buffer);
72 | if (len > DEBUG_MESSAGE_MAX_LENGTH) {
73 | DEBUG_PORT.printf(" (...)\n");
74 | }
75 | #endif
76 |
77 | #if DEBUG_UDP_SUPPORT
78 | if (systemCheck()) {
79 | udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
80 | udpDebug.write(buffer);
81 | if (len > DEBUG_MESSAGE_MAX_LENGTH) {
82 | udpDebug.write(" (...)\n");
83 | }
84 | udpDebug.endPacket();
85 | delay(1);
86 | }
87 | #endif
88 |
89 | #if DEBUG_TELNET_SUPPORT
90 | _telnetWrite(buffer, strlen(buffer));
91 | #endif
92 |
93 | }
94 |
95 | // -----------------------------------------------------------------------------
96 | // Save crash info
97 | // Taken from krzychb EspSaveCrash
98 | // https://github.com/krzychb/EspSaveCrash
99 | // -----------------------------------------------------------------------------
100 |
101 | #define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
102 |
103 | /**
104 | * Structure of the single crash data set
105 | *
106 | * 1. Crash time
107 | * 2. Restart reason
108 | * 3. Exception cause
109 | * 4. epc1
110 | * 5. epc2
111 | * 6. epc3
112 | * 7. excvaddr
113 | * 8. depc
114 | * 9. adress of stack start
115 | * 10. adress of stack end
116 | * 11. stack trace bytes
117 | * ...
118 | */
119 | #define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
120 | #define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
121 | #define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
122 | #define SAVE_CRASH_EPC1 0x06 // 4 bytes
123 | #define SAVE_CRASH_EPC2 0x0A // 4 bytes
124 | #define SAVE_CRASH_EPC3 0x0E // 4 bytes
125 | #define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
126 | #define SAVE_CRASH_DEPC 0x16 // 4 bytes
127 | #define SAVE_CRASH_STACK_START 0x1A // 4 bytes
128 | #define SAVE_CRASH_STACK_END 0x1E // 4 bytes
129 | #define SAVE_CRASH_STACK_TRACE 0x22 // variable
130 |
131 | /**
132 | * Save crash information in EEPROM
133 | * This function is called automatically if ESP8266 suffers an exception
134 | * It should be kept quick / consise to be able to execute before hardware wdt may kick in
135 | */
136 | extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) {
137 |
138 | // This method assumes EEPROM has already been initialized
139 | // which is the first thing ESPurna does
140 |
141 | // write crash time to EEPROM
142 | uint32_t crash_time = millis();
143 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
144 |
145 | // write reset info to EEPROM
146 | EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason);
147 | EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause);
148 |
149 | // write epc1, epc2, epc3, excvaddr and depc to EEPROM
150 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1);
151 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2);
152 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3);
153 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr);
154 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc);
155 |
156 | // write stack start and end address to EEPROM
157 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
158 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
159 |
160 | // write stack trace to EEPROM
161 | int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
162 | for (uint32_t i = stack_start; i < stack_end; i++) {
163 | byte* byteValue = (byte*) i;
164 | EEPROM.write(current_address++, *byteValue);
165 | }
166 |
167 | EEPROM.commit();
168 |
169 | }
170 |
171 | /**
172 | * Clears crash info
173 | */
174 | void debugClearCrashInfo() {
175 | uint32_t crash_time = 0xFFFFFFFF;
176 | EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
177 | EEPROM.commit();
178 | }
179 |
180 | /**
181 | * Print out crash information that has been previusly saved in EEPROM
182 | */
183 | void debugDumpCrashInfo() {
184 |
185 | uint32_t crash_time;
186 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
187 | if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
188 | DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n"));
189 | return;
190 | }
191 |
192 | DEBUG_MSG_P(PSTR("[DEBUG] Crash at %ld ms\n"), crash_time);
193 | DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %d\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
194 | DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %d\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
195 |
196 | uint32_t epc1, epc2, epc3, excvaddr, depc;
197 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
198 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2);
199 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3);
200 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr);
201 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc);
202 | DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3);
203 | DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc);
204 |
205 | uint32_t stack_start, stack_end;
206 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
207 | EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
208 | DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] "));
209 | int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
210 | int16_t stack_len = stack_end - stack_start;
211 | uint32_t stack_trace;
212 | for (int16_t i = 0; i < stack_len; i += 0x10) {
213 | DEBUG_MSG_P(PSTR("%08x: "), stack_start + i);
214 | for (byte j = 0; j < 4; j++) {
215 | EEPROM.get(current_address, stack_trace);
216 | DEBUG_MSG_P(PSTR("%08x "), stack_trace);
217 | current_address += 4;
218 | }
219 | DEBUG_MSG_P(PSTR("\n[DEBUG] "));
220 | }
221 | DEBUG_MSG_P(PSTR("<<
6 |
7 | */
8 |
9 | #if DOMOTICZ_SUPPORT
10 |
11 | #include
12 |
13 | bool _dcz_enabled = false;
14 |
15 | //------------------------------------------------------------------------------
16 | // Private methods
17 | //------------------------------------------------------------------------------
18 |
19 | //------------------------------------------------------------------------------
20 | // Public API
21 | //------------------------------------------------------------------------------
22 |
23 | template void domoticzSend(const char * key, T nvalue, const char * svalue) {
24 | if (!_dcz_enabled) return;
25 | unsigned int idx = getSetting(key).toInt();
26 | if (idx > 0) {
27 | char payload[128];
28 | snprintf(payload, sizeof(payload), "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue);
29 | mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload);
30 | }
31 | }
32 |
33 | template void domoticzSend(const char * key, T nvalue) {
34 | domoticzSend(key, nvalue, "");
35 | }
36 |
37 | void domoticzConfigure() {
38 | _dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
39 | }
40 |
41 | void domoticzSetup() {
42 | domoticzConfigure();
43 | }
44 |
45 | bool domoticzEnabled() {
46 | return _dcz_enabled;
47 | }
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/lights.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | LIGHTS MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | #include
10 |
11 | Ticker _lights_defer;
12 | unsigned char _channels[3] = {0};
13 |
14 | // -----------------------------------------------------------------------------
15 | // LIGHTS
16 | // -----------------------------------------------------------------------------
17 |
18 | void _fromRGB(const char * rgb) {
19 |
20 | char * p = (char *) rgb;
21 | if (strlen(p) == 0) return;
22 |
23 | // if color begins with a # then assume HEX RGB
24 | if (p[0] == '#') {
25 |
26 | ++p;
27 | unsigned long value = strtoul(p, NULL, 16);
28 | _channels[0] = (value >> 16) & 0xFF;
29 | _channels[1] = (value >> 8) & 0xFF;
30 | _channels[2] = (value) & 0xFF;
31 |
32 | // it's a temperature in mireds
33 | } else if (p[0] == 'M') {
34 |
35 | unsigned long mireds = atol(p + 1);
36 | _fromMireds(mireds);
37 |
38 | // it's a temperature in kelvin
39 | } else if (p[0] == 'K') {
40 |
41 | unsigned long kelvin = atol(p + 1);
42 | _fromKelvin(kelvin);
43 |
44 | // otherwise assume decimal values separated by commas
45 | } else {
46 |
47 | char * tok;
48 | unsigned char count = 0;
49 |
50 | tok = strtok(p, ",");
51 | while (tok != NULL) {
52 | _channels[count] = atoi(tok);
53 | if (++count == 3) break;
54 | tok = strtok(NULL, ",");
55 | }
56 |
57 | // RGB but less than 3 values received
58 | if (count < 3) {
59 | _channels[1] = _channels[0];
60 | _channels[2] = _channels[0];
61 | }
62 |
63 | }
64 |
65 | }
66 |
67 | // Thanks to Sacha Telgenhof for sharing this code in his AiLight library
68 | // https://github.com/stelgenhof/AiLight
69 | void _fromKelvin(unsigned long kelvin) {
70 |
71 | // Calculate colors
72 | unsigned int red = (kelvin <= 66)
73 | ? 255
74 | : 329.698727446 * pow((kelvin - 60), -0.1332047592);
75 | unsigned int green = (kelvin <= 66)
76 | ? 99.4708025861 * log(kelvin) - 161.1195681661
77 | : 288.1221695283 * pow(kelvin, -0.0755148492);
78 | unsigned int blue = (kelvin >= 66)
79 | ? 255
80 | : ((kelvin <= 19)
81 | ? 0
82 | : 138.5177312231 * log(kelvin - 10) - 305.0447927307);
83 |
84 | // Save values
85 | _channels[0] = constrain(red, 0, 255);
86 | _channels[1] = constrain(green, 0, 255);
87 | _channels[2] = constrain(blue, 0, 255);
88 |
89 | }
90 |
91 | // Color temperature is measured in mireds (kelvin = 1e6/mired)
92 | void _fromMireds(unsigned long mireds) {
93 | if (mireds == 0) mireds = 1;
94 | unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100;
95 | _fromKelvin(kelvin);
96 | }
97 |
98 | void _lightsMqttCallback(unsigned int type, const char * topic, const char * payload) {
99 |
100 | // When connected, subscribe to the topic
101 | if (type == MQTT_CONNECT_EVENT) {
102 | mqttSubscribe(MQTT_TOPIC_RGB);
103 | mqttSubscribe(MQTT_TOPIC_EFFECT);
104 | mqttSubscribe(MQTT_TOPIC_BRIGHTNESS);
105 | mqttSubscribe(MQTT_TOPIC_SPEED);
106 | }
107 |
108 | // Messages
109 | if (type == MQTT_MESSAGE_EVENT) {
110 |
111 | String t = mqttSubtopic((char*) topic);
112 |
113 | if (t.equals(MQTT_TOPIC_RGB)) {
114 | sendColor((char*) payload);
115 | }
116 |
117 | if (t.equals(MQTT_TOPIC_BRIGHTNESS)) {
118 | unsigned char brightness = constrain(atoi(payload), 0, 255);
119 | sendBrightness(brightness);
120 | }
121 |
122 | if (t.equals(MQTT_TOPIC_SPEED)) {
123 | unsigned char speed = constrain(atoi(payload), 0, 255);
124 | sendSpeed(speed);
125 | }
126 |
127 | if (t.equals(MQTT_TOPIC_EFFECT)) {
128 | unsigned char effect = constrain(atoi(payload), 0, 53);
129 | sendEffect(effect);
130 | }
131 |
132 | }
133 |
134 | }
135 |
136 | //------------------------------------------------------------------------------
137 |
138 | void sendEffect(long effect) {
139 | DEBUG_MSG_P(PSTR("[LIGHTS] Effect to %d\n"), effect);
140 | send_P_repeat(at_effect, effect);
141 | }
142 |
143 | void sendColor(unsigned long color) {
144 | DEBUG_MSG_P(PSTR("[LIGHTS] Color to %lu\n"), color);
145 | send_P_repeat(at_color, color);
146 | }
147 |
148 | void sendBrightness(unsigned char brightness) {
149 | DEBUG_MSG_P(PSTR("[LIGHTS] Brightness to %d\n"), brightness);
150 | send_P_repeat(at_bright, brightness);
151 | }
152 |
153 | void sendColor(const char * rgb) {
154 | _fromRGB(rgb);
155 | unsigned long color = _channels[0] << 16 | _channels[1] << 8 | _channels[2];
156 | sendColor(color);
157 | }
158 |
159 | void sendSpeed(unsigned char speed) {
160 | DEBUG_MSG_P(PSTR("[LIGHTS] Speed to %d\n"), speed);
161 | send_P_repeat(at_speed, speed);
162 | }
163 |
164 | void sendNotification(bool state, unsigned long time) {
165 |
166 | if (state) {
167 | sendBrightness(NOTIFICATION_BRIGHTNESS);
168 | sendEffect(NOTIFICATION_EFFECT);
169 | sendSpeed(NOTIFICATION_SPEED);
170 | sendColor(NOTIFICATION_COLOR);
171 | } else {
172 | sendColor(0UL);
173 | }
174 |
175 | if (time > 0) _lights_defer.once(time, sendNotification, !state);
176 |
177 | }
178 |
179 | void sendNotification(bool state) {
180 | sendNotification(state, 0);
181 | }
182 |
183 | void lightsSetup() {
184 | mqttRegister(_lightsMqttCallback);
185 | send_P_repeat(at_timeout, 0);
186 | }
187 |
188 | void lightsLoop() {
189 | }
190 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/mqtt.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | MQTT MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #if MQTT_USE_ASYNC // Using AsyncMqttClient
16 |
17 | #include
18 | AsyncMqttClient _mqtt;
19 |
20 | #else // Using PubSubClient
21 |
22 | #include
23 | PubSubClient _mqtt;
24 | bool _mqtt_connected = false;
25 |
26 | WiFiClient _mqtt_client;
27 | #if ASYNC_TCP_SSL_ENABLED
28 | WiFiClientSecure _mqtt_client_secure;
29 | #endif // ASYNC_TCP_SSL_ENABLED
30 |
31 | #endif // MQTT_USE_ASYNC
32 |
33 | bool _mqtt_enabled = MQTT_ENABLED;
34 | bool _mqtt_use_json = false;
35 | unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
36 | String _mqtt_topic;
37 | String _mqtt_setter;
38 | String _mqtt_getter;
39 | bool _mqtt_forward;
40 | char *_mqtt_user = 0;
41 | char *_mqtt_pass = 0;
42 | char *_mqtt_will;
43 | #if MQTT_SKIP_RETAINED
44 | unsigned long _mqtt_connected_at = 0;
45 | #endif
46 |
47 | std::vector _mqtt_callbacks;
48 |
49 | typedef struct {
50 | char * topic;
51 | char * message;
52 | } mqtt_message_t;
53 | std::vector _mqtt_queue;
54 | Ticker _mqtt_flush_ticker;
55 |
56 | // -----------------------------------------------------------------------------
57 | // Public API
58 | // -----------------------------------------------------------------------------
59 |
60 | bool mqttConnected() {
61 | return _mqtt.connected();
62 | }
63 |
64 | void mqttDisconnect() {
65 | if (_mqtt.connected()) {
66 | DEBUG_MSG_P("[MQTT] Disconnecting\n");
67 | _mqtt.disconnect();
68 | }
69 | }
70 |
71 | bool mqttForward() {
72 | return _mqtt_forward;
73 | }
74 |
75 | String mqttSubtopic(char * topic) {
76 | String response;
77 | String t = String(topic);
78 | if (t.startsWith(_mqtt_topic) && t.endsWith(_mqtt_setter)) {
79 | response = t.substring(_mqtt_topic.length(), t.length() - _mqtt_setter.length());
80 | }
81 | return response;
82 | }
83 |
84 | void mqttSendRaw(const char * topic, const char * message) {
85 | if (_mqtt.connected()) {
86 | #if MQTT_USE_ASYNC
87 | unsigned int packetId = _mqtt.publish(topic, MQTT_QOS, MQTT_RETAIN, message);
88 | DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId);
89 | #else
90 | _mqtt.publish(topic, message, MQTT_RETAIN);
91 | DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
92 | #endif
93 | }
94 | }
95 |
96 | String getTopic(const char * topic, bool set) {
97 | String output = _mqtt_topic + String(topic);
98 | if (set) output += _mqtt_setter;
99 | return output;
100 | }
101 |
102 | String getTopic(const char * topic, unsigned int index, bool set) {
103 | char buffer[strlen(topic)+5];
104 | snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
105 | return getTopic(buffer, set);
106 | }
107 |
108 | void _mqttFlush() {
109 |
110 | if (_mqtt_queue.size() == 0) return;
111 |
112 | DynamicJsonBuffer jsonBuffer;
113 | JsonObject& root = jsonBuffer.createObject();
114 | for (unsigned char i=0; i<_mqtt_queue.size(); i++) {
115 | mqtt_message_t element = _mqtt_queue[i];
116 | root[element.topic] = element.message;
117 | }
118 | #if NTP_SUPPORT
119 | if (ntpConnected()) root[MQTT_TOPIC_TIME] = ntpDateTime();
120 | #endif
121 | root[MQTT_TOPIC_HOSTNAME] = getSetting("hostname");
122 | root[MQTT_TOPIC_IP] = getIP();
123 |
124 | String output;
125 | root.printTo(output);
126 | String path = _mqtt_topic + String(MQTT_TOPIC_JSON);
127 | mqttSendRaw(path.c_str(), output.c_str());
128 |
129 | for (unsigned char i = 0; i < _mqtt_queue.size(); i++) {
130 | mqtt_message_t element = _mqtt_queue[i];
131 | free(element.topic);
132 | free(element.message);
133 | }
134 | _mqtt_queue.clear();
135 |
136 | }
137 |
138 | void mqttSend(const char * topic, const char * message, bool force) {
139 | bool useJson = force ? false : _mqtt_use_json;
140 | if (useJson) {
141 | mqtt_message_t element;
142 | element.topic = strdup(topic);
143 | element.message = strdup(message);
144 | _mqtt_queue.push_back(element);
145 | _mqtt_flush_ticker.once_ms(MQTT_USE_JSON_DELAY, _mqttFlush);
146 | } else {
147 | String path = _mqtt_topic + String(topic) + _mqtt_getter;
148 | mqttSendRaw(path.c_str(), message);
149 | }
150 | }
151 |
152 | void mqttSend(const char * topic, const char * message) {
153 | mqttSend(topic, message, false);
154 | }
155 |
156 | void mqttSend(const char * topic, unsigned int index, const char * message, bool force) {
157 | char buffer[strlen(topic)+5];
158 | snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index);
159 | mqttSend(buffer, message, force);
160 | }
161 |
162 | void mqttSend(const char * topic, unsigned int index, const char * message) {
163 | mqttSend(topic, index, message, false);
164 | }
165 |
166 | void mqttSubscribeRaw(const char * topic) {
167 | if (_mqtt.connected() && (strlen(topic) > 0)) {
168 | #if MQTT_USE_ASYNC
169 | unsigned int packetId = _mqtt.subscribe(topic, MQTT_QOS);
170 | DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId);
171 | #else
172 | _mqtt.subscribe(topic, MQTT_QOS);
173 | DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s\n"), topic);
174 | #endif
175 | }
176 | }
177 |
178 | void mqttSubscribe(const char * topic) {
179 | String path = _mqtt_topic + String(topic) + _mqtt_setter;
180 | mqttSubscribeRaw(path.c_str());
181 | }
182 |
183 | void mqttRegister(void (*callback)(unsigned int, const char *, const char *)) {
184 | _mqtt_callbacks.push_back(callback);
185 | }
186 |
187 | // -----------------------------------------------------------------------------
188 | // Callbacks
189 | // -----------------------------------------------------------------------------
190 |
191 | void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
192 |
193 |
194 | if (type == MQTT_CONNECT_EVENT) {
195 |
196 | mqttSubscribe(MQTT_TOPIC_ACTION);
197 |
198 | }
199 |
200 | if (type == MQTT_MESSAGE_EVENT) {
201 |
202 | // Match topic
203 | String t = mqttSubtopic((char *) topic);
204 |
205 | // Actions
206 | if (t.equals(MQTT_TOPIC_ACTION)) {
207 | if (strcmp(payload, MQTT_ACTION_RESET) == 0) {
208 | customReset(CUSTOM_RESET_MQTT);
209 | ESP.restart();
210 | }
211 | }
212 |
213 | }
214 |
215 | }
216 |
217 | void _mqttOnConnect() {
218 |
219 | DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
220 | _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
221 |
222 | #if MQTT_SKIP_RETAINED
223 | _mqtt_connected_at = millis();
224 | #endif
225 |
226 | // Send first Heartbeat
227 | heartbeat();
228 |
229 | // Send connect event to subscribers
230 | for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
231 | (*_mqtt_callbacks[i])(MQTT_CONNECT_EVENT, NULL, NULL);
232 | }
233 |
234 | }
235 |
236 | void _mqttOnDisconnect() {
237 |
238 | DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n"));
239 |
240 | // Send disconnect event to subscribers
241 | for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
242 | (*_mqtt_callbacks[i])(MQTT_DISCONNECT_EVENT, NULL, NULL);
243 | }
244 |
245 | }
246 |
247 | void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
248 |
249 | if (len == 0) return;
250 |
251 | char message[len + 1];
252 | strlcpy(message, (char *) payload, len + 1);
253 |
254 | #if MQTT_SKIP_RETAINED
255 | if (millis() - _mqtt_connected_at < MQTT_SKIP_TIME) {
256 | DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message);
257 | return;
258 | }
259 | #endif
260 | DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message);
261 |
262 | // Send message event to subscribers
263 | for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
264 | (*_mqtt_callbacks[i])(MQTT_MESSAGE_EVENT, topic, message);
265 | }
266 |
267 | }
268 |
269 | #if MQTT_USE_ASYNC
270 |
271 | bool mqttFormatFP(const char * fingerprint, unsigned char * bytearray) {
272 |
273 | // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
274 | if (strlen(fingerprint) != 59) return false;
275 |
276 | DEBUG_MSG_P(PSTR("[MQTT] Fingerprint %s\n"), fingerprint);
277 |
278 | // walk the fingerprint
279 | for (unsigned int i=0; i<20; i++) {
280 | bytearray[i] = strtol(fingerprint + 3*i, NULL, 16);
281 | }
282 |
283 | return true;
284 |
285 | }
286 |
287 | #else
288 |
289 | bool mqttFormatFP(const char * fingerprint, char * destination) {
290 |
291 | // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
292 | if (strlen(fingerprint) != 59) return false;
293 |
294 | DEBUG_MSG_P(PSTR("[MQTT] Fingerprint %s\n"), fingerprint);
295 |
296 | // copy it
297 | strncpy(destination, fingerprint, 59);
298 |
299 | // walk the fingerprint replacing ':' for ' '
300 | for (unsigned char i = 0; i<59; i++) {
301 | if (destination[i] == ':') destination[i] = ' ';
302 | }
303 |
304 | return true;
305 |
306 | }
307 |
308 | #endif
309 |
310 | void mqttEnabled(bool status) {
311 | _mqtt_enabled = status;
312 | setSetting("mqttEnabled", status ? 1 : 0);
313 | }
314 |
315 | bool mqttEnabled() {
316 | return _mqtt_enabled;
317 | }
318 |
319 | void mqttConnect() {
320 |
321 | // Do not connect if disabled
322 | if (!_mqtt_enabled) return;
323 |
324 | // Do not connect if already connected
325 | if (_mqtt.connected()) return;
326 |
327 | // Check reconnect interval
328 | static unsigned long last = 0;
329 | if (millis() - last < _mqtt_reconnect_delay) return;
330 | last = millis();
331 |
332 | // Increase the reconnect delay
333 | _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
334 | if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
335 | _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
336 | }
337 |
338 | char * host = strdup(getSetting("mqttServer", MQTT_SERVER).c_str());
339 | if (strlen(host) == 0) return;
340 | unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
341 |
342 | if (_mqtt_user) free(_mqtt_user);
343 | if (_mqtt_pass) free(_mqtt_pass);
344 | if (_mqtt_will) free(_mqtt_will);
345 |
346 | _mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str());
347 | _mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
348 | _mqtt_will = strdup((_mqtt_topic + MQTT_TOPIC_STATUS).c_str());
349 |
350 | DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
351 |
352 | #if MQTT_USE_ASYNC
353 |
354 | _mqtt.setServer(host, port);
355 | _mqtt.setKeepAlive(MQTT_KEEPALIVE).setCleanSession(false);
356 | _mqtt.setWill(_mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
357 | if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
358 | DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
359 | _mqtt.setCredentials(_mqtt_user, _mqtt_pass);
360 | }
361 |
362 | #if ASYNC_TCP_SSL_ENABLED
363 |
364 | bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
365 | _mqtt.setSecure(secure);
366 | if (secure) {
367 | DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
368 | unsigned char fp[20] = {0};
369 | if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
370 | _mqtt.addServerFingerprint(fp);
371 | } else {
372 | DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
373 | }
374 | }
375 |
376 | #endif // ASYNC_TCP_SSL_ENABLED
377 |
378 | DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
379 | DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), MQTT_QOS);
380 | DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), MQTT_RETAIN);
381 |
382 | _mqtt.connect();
383 |
384 | #else // not MQTT_USE_ASYNC
385 |
386 | bool response = true;
387 |
388 | #if ASYNC_TCP_SSL_ENABLED
389 |
390 | bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
391 | if (secure) {
392 | DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
393 | if (_mqtt_client_secure.connect(host, port)) {
394 | char fp[60] = {0};
395 | if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
396 | if (_mqtt_client_secure.verify(fp, host)) {
397 | _mqtt.setClient(_mqtt_client_secure);
398 | } else {
399 | DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint\n"));
400 | response = false;
401 | }
402 | _mqtt_client_secure.stop();
403 | yield();
404 | } else {
405 | DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
406 | response = false;
407 | }
408 | } else {
409 | DEBUG_MSG_P(PSTR("[MQTT] Client connection failed\n"));
410 | response = false;
411 | }
412 |
413 | } else {
414 | _mqtt.setClient(_mqtt_client);
415 | }
416 |
417 | #else // not ASYNC_TCP_SSL_ENABLED
418 |
419 | _mqtt.setClient(_mqtt_client);
420 |
421 | #endif // ASYNC_TCP_SSL_ENABLED
422 |
423 | if (response) {
424 |
425 | _mqtt.setServer(host, port);
426 |
427 | if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
428 | DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
429 | response = _mqtt.connect(getIdentifier().c_str(), _mqtt_user, _mqtt_pass, _mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
430 | } else {
431 | response = _mqtt.connect(getIdentifier().c_str(), _mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
432 | }
433 |
434 | DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
435 | DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), MQTT_QOS);
436 | DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), MQTT_RETAIN);
437 |
438 | }
439 |
440 | if (response) {
441 | _mqttOnConnect();
442 | } else {
443 | DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
444 | }
445 |
446 | #endif // MQTT_USE_ASYNC
447 |
448 | free(host);
449 |
450 | }
451 |
452 | void mqttConfigure() {
453 |
454 | // Replace identifier
455 | _mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC);
456 | _mqtt_topic.replace("{identifier}", getSetting("hostname"));
457 | if (!_mqtt_topic.endsWith("/")) _mqtt_topic = _mqtt_topic + "/";
458 |
459 | // Getters and setters
460 | _mqtt_setter = getSetting("mqttSetter", MQTT_USE_SETTER);
461 | _mqtt_getter = getSetting("mqttGetter", MQTT_USE_GETTER);
462 | _mqtt_forward = !_mqtt_getter.equals(_mqtt_setter);
463 |
464 | // Enable
465 | if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
466 | mqttEnabled(false);
467 | } else {
468 | _mqtt_enabled = getSetting("mqttEnabled", MQTT_ENABLED).toInt() == 1;
469 | }
470 | _mqtt_use_json = (getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1);
471 |
472 | _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
473 |
474 | }
475 |
476 | #if MDNS_SUPPORT
477 | boolean mqttDiscover() {
478 |
479 | int count = MDNS.queryService("mqtt", "tcp");
480 | DEBUG_MSG_P("[MQTT] MQTT brokers found: %d\n", count);
481 |
482 | for (int i=0; i
6 |
7 | */
8 |
9 | #if NOFUSS_SUPPORT
10 |
11 | #include "NoFUSSClient.h"
12 |
13 | unsigned long _nofussLastCheck = 0;
14 | unsigned long _nofussInterval = 0;
15 | bool _nofussEnabled = false;
16 |
17 | // -----------------------------------------------------------------------------
18 | // NOFUSS
19 | // -----------------------------------------------------------------------------
20 |
21 | void nofussRun() {
22 | NoFUSSClient.handle();
23 | _nofussLastCheck = millis();
24 | }
25 |
26 | void nofussConfigure() {
27 |
28 | String nofussServer = getSetting("nofussServer", NOFUSS_SERVER);
29 | if (nofussServer.length() == 0) {
30 | setSetting("nofussEnabled", 0);
31 | _nofussEnabled = false;
32 | } else {
33 | _nofussEnabled = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
34 | }
35 | _nofussInterval = getSetting("nofussInterval", NOFUSS_INTERVAL).toInt();
36 | _nofussLastCheck = 0;
37 |
38 | if (!_nofussEnabled) {
39 |
40 | DEBUG_MSG_P(PSTR("[NOFUSS] Disabled\n"));
41 |
42 | } else {
43 |
44 | char buffer[20];
45 | snprintf_P(buffer, sizeof(buffer), PSTR("%s-%s"), APP_NAME, DEVICE);
46 |
47 | NoFUSSClient.setServer(nofussServer);
48 | NoFUSSClient.setDevice(buffer);
49 | NoFUSSClient.setVersion(APP_VERSION);
50 |
51 | DEBUG_MSG_P(PSTR("[NOFUSS] Server : %s\n"), nofussServer.c_str());
52 | DEBUG_MSG_P(PSTR("[NOFUSS] Dervice: %s\n"), buffer);
53 | DEBUG_MSG_P(PSTR("[NOFUSS] Version: %s\n"), APP_VERSION);
54 | DEBUG_MSG_P(PSTR("[NOFUSS] Enabled\n"));
55 |
56 | }
57 |
58 | }
59 |
60 | void nofussSetup() {
61 |
62 | nofussConfigure();
63 |
64 | NoFUSSClient.onMessage([](nofuss_t code) {
65 |
66 | if (code == NOFUSS_START) {
67 | DEBUG_MSG_P(PSTR("[NoFUSS] Start\n"));
68 | }
69 |
70 | if (code == NOFUSS_UPTODATE) {
71 | DEBUG_MSG_P(PSTR("[NoFUSS] Already in the last version\n"));
72 | }
73 |
74 | if (code == NOFUSS_NO_RESPONSE_ERROR) {
75 | DEBUG_MSG_P(PSTR("[NoFUSS] Wrong server response: %d %s\n"), NoFUSSClient.getErrorNumber(), (char *) NoFUSSClient.getErrorString().c_str());
76 | }
77 |
78 | if (code == NOFUSS_PARSE_ERROR) {
79 | DEBUG_MSG_P(PSTR("[NoFUSS] Error parsing server response\n"));
80 | }
81 |
82 | if (code == NOFUSS_UPDATING) {
83 | DEBUG_MSG_P(PSTR("[NoFUSS] Updating\n"));
84 | DEBUG_MSG_P(PSTR(" New version: %s\n"), (char *) NoFUSSClient.getNewVersion().c_str());
85 | DEBUG_MSG_P(PSTR(" Firmware: %s\n"), (char *) NoFUSSClient.getNewFirmware().c_str());
86 | DEBUG_MSG_P(PSTR(" File System: %s\n"), (char *) NoFUSSClient.getNewFileSystem().c_str());
87 | #if WEB_SUPPORT
88 | wsSend_P(PSTR("{\"message\": 1}"));
89 | #endif
90 | }
91 |
92 | if (code == NOFUSS_FILESYSTEM_UPDATE_ERROR) {
93 | DEBUG_MSG_P(PSTR("[NoFUSS] File System Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
94 | }
95 |
96 | if (code == NOFUSS_FILESYSTEM_UPDATED) {
97 | DEBUG_MSG_P(PSTR("[NoFUSS] File System Updated\n"));
98 | }
99 |
100 | if (code == NOFUSS_FIRMWARE_UPDATE_ERROR) {
101 | DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str());
102 | }
103 |
104 | if (code == NOFUSS_FIRMWARE_UPDATED) {
105 | DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Updated\n"));
106 | }
107 |
108 | if (code == NOFUSS_RESET) {
109 | DEBUG_MSG_P(PSTR("[NoFUSS] Resetting board\n"));
110 | #if WEB_SUPPORT
111 | wsSend_P(PSTR("{\"action\": \"reload\"}"));
112 | #endif
113 | delay(100);
114 | }
115 |
116 | if (code == NOFUSS_END) {
117 | DEBUG_MSG_P(PSTR("[NoFUSS] End\n"));
118 | }
119 |
120 | });
121 |
122 | }
123 |
124 | void nofussLoop() {
125 |
126 | if (!_nofussEnabled) return;
127 | if (!wifiConnected()) return;
128 | if ((_nofussLastCheck > 0) && ((millis() - _nofussLastCheck) < _nofussInterval)) return;
129 |
130 | nofussRun();
131 |
132 | }
133 |
134 | #endif // NOFUSS_SUPPORT
135 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/ntp.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | NTP MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | #if NTP_SUPPORT
10 |
11 | #include
12 | #include
13 | #include
14 |
15 | // -----------------------------------------------------------------------------
16 | // NTP
17 | // -----------------------------------------------------------------------------
18 |
19 | void ntpConnect() {
20 | NTP.begin(
21 | getSetting("ntpServer1", NTP_SERVER),
22 | getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(),
23 | getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1
24 | );
25 | if (getSetting("ntpServer2")) NTP.setNtpServerName(getSetting("ntpServer2"), 1);
26 | if (getSetting("ntpServer3")) NTP.setNtpServerName(getSetting("ntpServer3"), 2);
27 | NTP.setInterval(NTP_UPDATE_INTERVAL);
28 | }
29 |
30 | bool ntpConnected() {
31 | return (timeStatus() == timeSet);
32 | }
33 |
34 | String ntpDateTime() {
35 | if (!ntpConnected()) return String("Not set");
36 | String value = NTP.getTimeDateString();
37 | int hour = value.substring(0, 2).toInt();
38 | int minute = value.substring(3, 5).toInt();
39 | int second = value.substring(6, 8).toInt();
40 | int day = value.substring(9, 11).toInt();
41 | int month = value.substring(12, 14).toInt();
42 | int year = value.substring(15, 19).toInt();
43 | char buffer[20];
44 | snprintf_P(buffer, sizeof(buffer), PSTR("%04d/%02d/%02d %02d:%02d:%02d"), year, month, day, hour, minute, second);
45 | return String(buffer);
46 | }
47 |
48 | void ntpSetup() {
49 | NTP.onNTPSyncEvent([](NTPSyncEvent_t error) {
50 | if (error) {
51 | if (error == noResponse) {
52 | DEBUG_MSG_P(PSTR("[NTP] Error: NTP server not reachable\n"));
53 | } else if (error == invalidAddress) {
54 | DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
55 | }
56 | #if WEB_SUPPORT
57 | wsSend_P(PSTR("{\"ntpStatus\": false}"));
58 | #endif
59 | } else {
60 | DEBUG_MSG_P(PSTR("[NTP] Time: %s\n"), (char *) ntpDateTime().c_str());
61 | #if WEB_SUPPORT
62 | wsSend_P(PSTR("{\"ntpStatus\": true}"));
63 | #endif
64 | }
65 | });
66 | }
67 |
68 | void ntpLoop() {
69 | now();
70 | }
71 |
72 | #endif // NTP_SUPPORT
73 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/ota.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | OTA MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | #include "ArduinoOTA.h"
10 | #include
11 |
12 | // -----------------------------------------------------------------------------
13 | // OTA
14 | // -----------------------------------------------------------------------------
15 |
16 | void otaConfigure() {
17 | ArduinoOTA.setPort(OTA_PORT);
18 | ArduinoOTA.setHostname(getSetting("hostname").c_str());
19 | ArduinoOTA.setPassword(getSetting("adminPass", ADMIN_PASS).c_str());
20 | }
21 |
22 | void otaSetup() {
23 |
24 | otaConfigure();
25 |
26 | ArduinoOTA.onStart([]() {
27 | DEBUG_MSG_P(PSTR("[OTA] Start\n"));
28 | wsSend("{\"message\": \"OTA update started\"}");
29 | });
30 |
31 | ArduinoOTA.onEnd([]() {
32 | customReset(CUSTOM_RESET_OTA);
33 | DEBUG_MSG_P(PSTR("\n[OTA] End\n"));
34 | wsSend("{\"action\": \"reload\"}");
35 | delay(100);
36 | });
37 |
38 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
39 | DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%%%\r"), (progress / (total / 100)));
40 | });
41 |
42 | ArduinoOTA.onError([](ota_error_t error) {
43 | #if DEBUG_PORT
44 | DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error);
45 | if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n"));
46 | else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n"));
47 | else if (error == OTA_CONNECT_ERROR) DEBUG_MSG_P(PSTR("Connect Failed\n"));
48 | else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n"));
49 | else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n"));
50 | #endif
51 | });
52 |
53 | ArduinoOTA.begin();
54 |
55 | // Public ESPurna related txt for OTA discovery
56 | MDNS.addServiceTxt("arduino", "tcp", "app_name", APP_NAME);
57 | MDNS.addServiceTxt("arduino", "tcp", "app_version", APP_VERSION);
58 | MDNS.addServiceTxt("arduino", "tcp", "target_board", DEVICE_NAME);
59 |
60 | }
61 |
62 | void otaLoop() {
63 | ArduinoOTA.handle();
64 | }
65 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/settings.h:
--------------------------------------------------------------------------------
1 | // -----------------------------------------------------------------------------
2 | // Stream Injector
3 | // -----------------------------------------------------------------------------
4 |
5 | #pragma once
6 |
7 | #define STREAM_INJECTOR_BUFFER_SIZE 32
8 |
9 | class StreamInjector : public Stream {
10 |
11 | public:
12 |
13 | typedef std::function writeCallback;
14 |
15 | StreamInjector(Stream& serial) : _stream(serial) {}
16 |
17 | virtual void callback(writeCallback c) {
18 | _callback = c;
19 | }
20 |
21 | virtual size_t write(uint8_t ch) {
22 | if (_callback) _callback(ch);
23 | return _stream.write(ch);
24 | }
25 |
26 | virtual int read() {
27 | int ch = _stream.read();
28 | if (ch == -1) {
29 | if (_buffer_read != _buffer_write) {
30 | ch = _buffer[_buffer_read];
31 | _buffer_read = (_buffer_read + 1) % STREAM_INJECTOR_BUFFER_SIZE;
32 | }
33 | }
34 | return ch;
35 | }
36 |
37 | virtual int available() {
38 | unsigned int bytes = _stream.available();
39 | if (_buffer_read > _buffer_write) {
40 | bytes += (_buffer_write - _buffer_read + STREAM_INJECTOR_BUFFER_SIZE);
41 | } else if (_buffer_read < _buffer_write) {
42 | bytes += (_buffer_write - _buffer_read);
43 | }
44 | return bytes;
45 | }
46 |
47 | virtual int peek() {
48 | int ch = _stream.peek();
49 | if (ch == -1) {
50 | if (_buffer_read != _buffer_write) {
51 | ch = _buffer[_buffer_read];
52 | }
53 | }
54 | return ch;
55 | }
56 |
57 | virtual void flush() {
58 | _stream.flush();
59 | _buffer_read = _buffer_write;
60 | }
61 |
62 | virtual void inject(char *data, size_t len) {
63 | for (int i=0; i
6 |
7 | */
8 |
9 | #include "Embedis.h"
10 | #include
11 | #include "spi_flash.h"
12 | #include
13 |
14 | #if TELNET_SUPPORT
15 | #include "settings.h"
16 | #ifdef DEBUG_PORT
17 | StreamInjector _serial = StreamInjector(DEBUG_PORT);
18 | #else
19 | StreamInjector _serial = StreamInjector(Serial);
20 | #endif
21 | Embedis embedis(_serial);
22 | #else
23 | #ifdef DEBUG_PORT
24 | Embedis embedis(DEBUG_PORT);
25 | #else
26 | Embedis embedis(_serial);
27 | #endif
28 | #endif
29 |
30 | bool _settings_save = false;
31 |
32 | // -----------------------------------------------------------------------------
33 | // Settings
34 | // -----------------------------------------------------------------------------
35 |
36 | #if TELNET_SUPPORT
37 | void settingsInject(void *data, size_t len) {
38 | _serial.inject((char *) data, len);
39 | }
40 | #endif
41 |
42 | size_t settingsMaxSize() {
43 | size_t size = EEPROM_SIZE;
44 | if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
45 | size = (size + 3) & (~3);
46 | return size;
47 | }
48 |
49 | unsigned long settingsSize() {
50 | unsigned pos = SPI_FLASH_SEC_SIZE - 1;
51 | while (size_t len = EEPROM.read(pos)) {
52 | pos = pos - len - 2;
53 | }
54 | return SPI_FLASH_SEC_SIZE - pos;
55 | }
56 |
57 | unsigned int settingsKeyCount() {
58 | unsigned count = 0;
59 | unsigned pos = SPI_FLASH_SEC_SIZE - 1;
60 | while (size_t len = EEPROM.read(pos)) {
61 | pos = pos - len - 2;
62 | count ++;
63 | }
64 | return count / 2;
65 | }
66 |
67 | String settingsKeyName(unsigned int index) {
68 |
69 | String s;
70 |
71 | unsigned count = 0;
72 | unsigned stop = index * 2 + 1;
73 | unsigned pos = SPI_FLASH_SEC_SIZE - 1;
74 | while (size_t len = EEPROM.read(pos)) {
75 | pos = pos - len - 2;
76 | count++;
77 | if (count == stop) {
78 | s.reserve(len);
79 | for (unsigned char i = 0 ; i < len; i++) {
80 | s += (char) EEPROM.read(pos + i + 1);
81 | }
82 | break;
83 | }
84 | }
85 |
86 | return s;
87 |
88 | }
89 |
90 | void settingsFactoryReset() {
91 | for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
92 | EEPROM.write(i, 0xFF);
93 | }
94 | EEPROM.commit();
95 | }
96 |
97 | void settingsSetup() {
98 |
99 | EEPROM.begin(SPI_FLASH_SEC_SIZE);
100 |
101 | #if TELNET_SUPPORT
102 | _serial.callback([](uint8_t ch) {
103 | telnetWrite(ch);
104 | });
105 | #endif
106 |
107 | Embedis::dictionary( F("EEPROM"),
108 | SPI_FLASH_SEC_SIZE,
109 | [](size_t pos) -> char { return EEPROM.read(pos); },
110 | [](size_t pos, char value) { EEPROM.write(pos, value); },
111 | #if SETTINGS_AUTOSAVE
112 | []() { _settings_save = true; }
113 | #else
114 | []() {}
115 | #endif
116 | );
117 |
118 | Embedis::hardware( F("WIFI"), [](Embedis* e) {
119 | StreamString s;
120 | WiFi.printDiag(s);
121 | e->response(s);
122 | }, 0);
123 |
124 | // -------------------------------------------------------------------------
125 |
126 | Embedis::command( F("RESET.WIFI"), [](Embedis* e) {
127 | wifiConfigure();
128 | wifiDisconnect();
129 | e->response(Embedis::OK);
130 | });
131 |
132 | Embedis::command( F("RESET.MQTT"), [](Embedis* e) {
133 | mqttConfigure();
134 | mqttDisconnect();
135 | e->response(Embedis::OK);
136 | });
137 |
138 | Embedis::command( F("INFO"), [](Embedis* e) {
139 | welcome();
140 | e->response(Embedis::OK);
141 | });
142 |
143 | Embedis::command( F("UPTIME"), [](Embedis* e) {
144 | e->stream->printf("Uptime: %d seconds\n", getUptime());
145 | e->response(Embedis::OK);
146 | });
147 |
148 | Embedis::command( F("RESET"), [](Embedis* e) {
149 | e->response(Embedis::OK);
150 | customReset(CUSTOM_RESET_TERMINAL);
151 | ESP.restart();
152 | });
153 |
154 | Embedis::command( F("ERASE.CONFIG"), [](Embedis* e) {
155 | e->response(Embedis::OK);
156 | customReset(CUSTOM_RESET_TERMINAL);
157 | ESP.eraseConfig();
158 | *((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
159 | });
160 |
161 | #if NOFUSS_SUPPORT
162 | Embedis::command( F("NOFUSS"), [](Embedis* e) {
163 | e->response(Embedis::OK);
164 | nofussRun();
165 | });
166 | #endif
167 |
168 | Embedis::command( F("FACTORY.RESET"), [](Embedis* e) {
169 | settingsFactoryReset();
170 | e->response(Embedis::OK);
171 | });
172 |
173 | Embedis::command( F("HEAP"), [](Embedis* e) {
174 | e->stream->printf("Free HEAP: %d bytes\n", ESP.getFreeHeap());
175 | e->response(Embedis::OK);
176 | });
177 |
178 | Embedis::command( F("EEPROM"), [](Embedis* e) {
179 | unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
180 | e->stream->printf("Number of keys: %d\n", settingsKeyCount());
181 | e->stream->printf("Free EEPROM: %d bytes (%d%%)\n", freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
182 | e->response(Embedis::OK);
183 | });
184 |
185 | Embedis::command( F("DUMP"), [](Embedis* e) {
186 | unsigned int size = settingsKeyCount();
187 | for (unsigned int i=0; istream->printf("+%s => %s\n", key.c_str(), value.c_str());
191 | }
192 | e->response(Embedis::OK);
193 | });
194 |
195 | #if DEBUG_SUPPORT
196 | Embedis::command( F("CRASH"), [](Embedis* e) {
197 | debugDumpCrashInfo();
198 | e->response(Embedis::OK);
199 | });
200 | #endif
201 |
202 | Embedis::command( F("DUMP.RAW"), [](Embedis* e) {
203 | for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
204 | if (i % 16 == 0) e->stream->printf("\n[%04X] ", i);
205 | e->stream->printf("%02X ", EEPROM.read(i));
206 | }
207 | e->stream->printf("\n");
208 | e->response(Embedis::OK);
209 | });
210 |
211 | DEBUG_MSG_P(PSTR("[SETTINGS] EEPROM size: %d bytes\n"), SPI_FLASH_SEC_SIZE);
212 | DEBUG_MSG_P(PSTR("[SETTINGS] Settings size: %d bytes\n"), settingsSize());
213 |
214 | }
215 |
216 | void settingsDump() {
217 | unsigned int size = settingsKeyCount();
218 | for (unsigned int i=0; i %s\n"), key.c_str(), value.c_str());
222 | }
223 | }
224 |
225 | void settingsLoop() {
226 | if (_settings_save) {
227 | //DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
228 | EEPROM.commit();
229 | _settings_save = false;
230 | }
231 | #if TERMINAL_SUPPORT
232 | embedis.process();
233 | #endif
234 | }
235 |
236 | void moveSetting(const char * from, const char * to) {
237 | String value = getSetting(from);
238 | if (value.length() > 0) setSetting(to, value);
239 | delSetting(from);
240 | }
241 |
242 | template String getSetting(const String& key, T defaultValue) {
243 | String value;
244 | if (!Embedis::get(key, value)) value = String(defaultValue);
245 | return value;
246 | }
247 |
248 | template String getSetting(const String& key, unsigned int index, T defaultValue) {
249 | return getSetting(key + String(index), defaultValue);
250 | }
251 |
252 | String getSetting(const String& key) {
253 | return getSetting(key, "");
254 | }
255 |
256 | template bool setSetting(const String& key, T value) {
257 | return Embedis::set(key, String(value));
258 | }
259 |
260 | template bool setSetting(const String& key, unsigned int index, T value) {
261 | return setSetting(key + String(index), value);
262 | }
263 |
264 | bool delSetting(const String& key) {
265 | return Embedis::del(key);
266 | }
267 |
268 | bool delSetting(const String& key, unsigned int index) {
269 | return delSetting(key + String(index));
270 | }
271 |
272 | bool hasSetting(const String& key) {
273 | return getSetting(key).length() != 0;
274 | }
275 |
276 | bool hasSetting(const String& key, unsigned int index) {
277 | return getSetting(key, index, "").length() != 0;
278 | }
279 |
280 | void saveSettings() {
281 | #if not SETTINGS_AUTOSAVE
282 | _settings_save = true;
283 | #endif
284 | //settingsDump();
285 | }
286 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/sonoffsc.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Sonoff SC
4 | Copyright (C) 2017 by Xose Pérez
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | */
20 |
21 | #include
22 | #include
23 | #include "config/all.h"
24 | #include "spi_flash.h"
25 |
26 | // -----------------------------------------------------------------------------
27 | // METHODS
28 | // -----------------------------------------------------------------------------
29 |
30 | bool ledStatus(bool status) {
31 | digitalWrite(LED_PIN, status ? LOW : HIGH);
32 | return status;
33 | }
34 |
35 | bool ledStatus() {
36 | return (digitalRead(LED_PIN) == LOW);
37 | }
38 |
39 | bool ledToggle() {
40 | return ledStatus(!ledStatus());
41 | }
42 |
43 | void ledBlink(unsigned long delayOff, unsigned long delayOn) {
44 | static unsigned long next = millis();
45 | if (next < millis()) {
46 | next += (ledToggle() ? delayOn : delayOff);
47 | }
48 | }
49 |
50 | void showStatus() {
51 | if (wifiConnected()) {
52 | if (WiFi.getMode() == WIFI_AP) {
53 | ledBlink(2000, 2000);
54 | } else {
55 | ledBlink(5000, 500);
56 | }
57 | } else {
58 | ledBlink(500, 500);
59 | }
60 | }
61 |
62 | void hardwareSetup() {
63 |
64 | EEPROM.begin(EEPROM_SIZE);
65 |
66 | #if DEBUG_SERIAL_SUPPORT
67 | DEBUG_PORT.begin(SERIAL_BAUDRATE);
68 | #if DEBUG_ESP_WIFI
69 | DEBUG_PORT.setDebugOutput(true);
70 | #endif
71 | #elif defined(SERIAL_BAUDRATE)
72 | Serial.begin(SERIAL_BAUDRATE);
73 | #endif
74 |
75 | #if SPIFFS_SUPPORT
76 | SPIFFS.begin();
77 | #endif
78 |
79 | pinMode(LED_PIN, OUTPUT);
80 |
81 | }
82 |
83 | void hardwareLoop() {
84 |
85 | showStatus();
86 |
87 | // System check
88 | static bool checked = false;
89 | if (!checked && (millis() > CRASH_SAFE_TIME)) {
90 | // Check system as stable
91 | systemCheck(true);
92 | checked = true;
93 | }
94 |
95 | // Heartbeat
96 | static unsigned long last_heartbeat = 0;
97 | if (mqttConnected()) {
98 | if ((millis() - last_heartbeat > HEARTBEAT_INTERVAL) || (last_heartbeat == 0)) {
99 | last_heartbeat = millis();
100 | heartbeat();
101 | }
102 | }
103 |
104 | }
105 |
106 | // -----------------------------------------------------------------------------
107 | // BOOTING
108 | // -----------------------------------------------------------------------------
109 |
110 | unsigned int sectors(size_t size) {
111 | return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
112 | }
113 |
114 | void welcome() {
115 |
116 | DEBUG_MSG_P(PSTR("\n\n"));
117 | DEBUG_MSG_P(PSTR("[INIT] %s %s\n"), (char *) APP_NAME, (char *) APP_VERSION);
118 | DEBUG_MSG_P(PSTR("[INIT] %s\n"), (char *) APP_AUTHOR);
119 | DEBUG_MSG_P(PSTR("[INIT] %s\n\n"), (char *) APP_WEBSITE);
120 | DEBUG_MSG_P(PSTR("[INIT] CPU chip ID: 0x%06X\n"), ESP.getChipId());
121 | DEBUG_MSG_P(PSTR("[INIT] CPU frequency: %d MHz\n"), ESP.getCpuFreqMHz());
122 | DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
123 | DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), ESP.getCoreVersion().c_str());
124 | DEBUG_MSG_P(PSTR("\n"));
125 |
126 | // -------------------------------------------------------------------------
127 |
128 | FlashMode_t mode = ESP.getFlashChipMode();
129 | DEBUG_MSG_P(PSTR("[INIT] Flash chip ID: 0x%06X\n"), ESP.getFlashChipId());
130 | DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
131 | DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
132 | DEBUG_MSG_P(PSTR("\n"));
133 | DEBUG_MSG_P(PSTR("[INIT] Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE);
134 | DEBUG_MSG_P(PSTR("[INIT] Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize());
135 | DEBUG_MSG_P(PSTR("[INIT] Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize()));
136 | DEBUG_MSG_P(PSTR("[INIT] Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize()));
137 | DEBUG_MSG_P(PSTR("[INIT] OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace()));
138 | #if SPIFFS_SUPPORT
139 | FSInfo fs_info;
140 | bool fs = SPIFFS.info(fs_info);
141 | if (fs) {
142 | DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
143 | }
144 | #else
145 | DEBUG_MSG_P(PSTR("[INIT] SPIFFS size: %8u bytes / %4d sectors\n"), 0, 0);
146 | #endif
147 | DEBUG_MSG_P(PSTR("[INIT] EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize()));
148 | DEBUG_MSG_P(PSTR("[INIT] Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE);
149 | DEBUG_MSG_P(PSTR("\n"));
150 |
151 | // -------------------------------------------------------------------------
152 |
153 | #if SPIFFS_SUPPORT
154 | if (fs) {
155 | DEBUG_MSG_P(PSTR("[INIT] SPIFFS total size: %8u bytes\n"), fs_info.totalBytes);
156 | DEBUG_MSG_P(PSTR("[INIT] used size: %8u bytes\n"), fs_info.usedBytes);
157 | DEBUG_MSG_P(PSTR("[INIT] block size: %8u bytes\n"), fs_info.blockSize);
158 | DEBUG_MSG_P(PSTR("[INIT] page size: %8u bytes\n"), fs_info.pageSize);
159 | DEBUG_MSG_P(PSTR("[INIT] max files: %8u\n"), fs_info.maxOpenFiles);
160 | DEBUG_MSG_P(PSTR("[INIT] max length: %8u\n"), fs_info.maxPathLength);
161 | } else {
162 | DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n"));
163 | }
164 | DEBUG_MSG_P(PSTR("\n"));
165 | #endif
166 |
167 | // -------------------------------------------------------------------------
168 |
169 | DEBUG_MSG_P(PSTR("[INIT] MANUFACTURER: %s\n"), MANUFACTURER);
170 | DEBUG_MSG_P(PSTR("[INIT] DEVICE: %s\n"), DEVICE_NAME);
171 | DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
172 |
173 | #if ALEXA_SUPPORT
174 | DEBUG_MSG_P(PSTR(" ALEXA"));
175 | #endif
176 | #if DEBUG_SERIAL_SUPPORT
177 | DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
178 | #endif
179 | #if DEBUG_UDP_SUPPORT
180 | DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
181 | #endif
182 | #if DOMOTICZ_SUPPORT
183 | DEBUG_MSG_P(PSTR(" DOMOTICZ"));
184 | #endif
185 | #if MDNS_SUPPORT
186 | DEBUG_MSG_P(PSTR(" MDNS"));
187 | #endif
188 | #if NOFUSS_SUPPORT
189 | DEBUG_MSG_P(PSTR(" NOFUSS"));
190 | #endif
191 | #if NTP_SUPPORT
192 | DEBUG_MSG_P(PSTR(" NTP"));
193 | #endif
194 | #if SPIFFS_SUPPORT
195 | DEBUG_MSG_P(PSTR(" SPIFFS"));
196 | #endif
197 | #if TERMINAL_SUPPORT
198 | DEBUG_MSG_P(PSTR(" TERMINAL"));
199 | #endif
200 |
201 | DEBUG_MSG_P(PSTR("\n\n"));
202 |
203 | // -------------------------------------------------------------------------
204 |
205 | unsigned char custom_reset = customReset();
206 | if (custom_reset > 0) {
207 | char buffer[32];
208 | strcpy_P(buffer, custom_reset_string[custom_reset-1]);
209 | DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
210 | } else {
211 | DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
212 | }
213 |
214 | DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), ESP.getFreeHeap());
215 | #if ADC_VCC_ENABLED
216 | DEBUG_MSG_P(PSTR("[INIT] Power: %d mV\n"), ESP.getVcc());
217 | #endif
218 | DEBUG_MSG_P(PSTR("\n"));
219 |
220 | }
221 |
222 | void setup() {
223 |
224 | // Wait for the ATMEGA to be ready
225 | delay(2000);
226 |
227 | // Init EEPROM, Serial and SPIFFS
228 | hardwareSetup();
229 |
230 | // Question system stability
231 | systemCheck(false);
232 |
233 | // Show welcome message and system configuration
234 | welcome();
235 |
236 | // Init persistance and terminal features
237 | settingsSetup();
238 | if (getSetting("hostname").length() == 0) {
239 | setSetting("hostname", getIdentifier());
240 | }
241 |
242 | wifiSetup();
243 | otaSetup();
244 | #if TELNET_SUPPORT
245 | telnetSetup();
246 | #endif
247 |
248 | // Do not run the next services if system is flagged stable
249 | if (!systemCheck()) return;
250 |
251 | buttonSetup();
252 | mqttSetup();
253 | webSetup();
254 | commsSetup();
255 | lightsSetup();
256 |
257 | #if NTP_SUPPORT
258 | ntpSetup();
259 | #endif
260 | #if ALEXA_SUPPORT
261 | alexaSetup();
262 | #endif
263 | #if NOFUSS_SUPPORT
264 | nofussSetup();
265 | #endif
266 | #if DOMOTICZ_SUPPORT
267 | domoticzSetup();
268 | #endif
269 |
270 | saveSettings();
271 |
272 | sendNotification(true, NOTIFICATION_TIME);
273 |
274 | }
275 |
276 | void loop() {
277 |
278 | hardwareLoop();
279 | settingsLoop();
280 | wifiLoop();
281 | otaLoop();
282 |
283 | // Do not run the next services if system is flagged stable
284 | if (!systemCheck()) return;
285 |
286 | buttonLoop();
287 | mqttLoop();
288 | commsLoop();
289 |
290 | #if NTP_SUPPORT
291 | ntpLoop();
292 | #endif
293 | #if ALEXA_SUPPORT
294 | alexaLoop();
295 | #endif
296 | #if NOFUSS_SUPPORT
297 | nofussLoop();
298 | #endif
299 |
300 | }
301 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/telnet.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | TELNET MODULE
4 |
5 | Copyright (C) 2017 by Xose Pérez
6 | Parts of the code have been borrowed from Thomas Sarlandie's NetServer
7 | (https://github.com/sarfata/kbox-firmware/tree/master/src/esp)
8 |
9 | */
10 |
11 | #if TELNET_SUPPORT
12 |
13 | #include
14 |
15 | AsyncServer * _telnetServer;
16 | AsyncClient * _telnetClients[TELNET_MAX_CLIENTS];
17 |
18 | // -----------------------------------------------------------------------------
19 | // Private methods
20 | // -----------------------------------------------------------------------------
21 |
22 | void _telnetDisconnect(unsigned char clientId) {
23 | _telnetClients[clientId]->free();
24 | _telnetClients[clientId] = NULL;
25 | delete _telnetClients[clientId];
26 | DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
27 | }
28 |
29 | bool _telnetWrite(unsigned char clientId, void *data, size_t len) {
30 | if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) {
31 | return (_telnetClients[clientId]->write((const char*) data, len) > 0);
32 | }
33 | return false;
34 | }
35 |
36 | unsigned char _telnetWrite(void *data, size_t len) {
37 | unsigned char count = 0;
38 | for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
39 | if (_telnetWrite(i, data, len)) ++count;
40 | }
41 | return count;
42 | }
43 |
44 | void _telnetData(unsigned char clientId, void *data, size_t len) {
45 |
46 | // Capture close connection
47 | char * p = (char *) data;
48 | if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) {
49 | _telnetClients[clientId]->close();
50 | return;
51 | }
52 |
53 | // Inject into Embedis stream
54 | settingsInject(data, len);
55 |
56 | }
57 |
58 | void _telnetNewClient(AsyncClient *client) {
59 |
60 | #if TELNET_ONLY_AP
61 | if (client->localIP() != WiFi.softAPIP()) {
62 | DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n"));
63 | client->onDisconnect([](void *s, AsyncClient *c) {
64 | c->free();
65 | delete c;
66 | });
67 | client->close(true);
68 | return;
69 | }
70 | #endif
71 |
72 | for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
73 | if (!_telnetClients[i] || !_telnetClients[i]->connected()) {
74 |
75 | _telnetClients[i] = client;
76 |
77 | client->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) {
78 | }, 0);
79 |
80 | client->onData([i](void *s, AsyncClient *c, void *data, size_t len) {
81 | _telnetData(i, data, len);
82 | }, 0);
83 |
84 | client->onDisconnect([i](void *s, AsyncClient *c) {
85 | _telnetDisconnect(i);
86 | }, 0);
87 |
88 | client->onError([i](void *s, AsyncClient *c, int8_t error) {
89 | DEBUG_MSG_P(PSTR("[TELNET] Error %s (%d) on client #%d\n"), c->errorToString(error), error, i);
90 | }, 0);
91 |
92 | client->onTimeout([i](void *s, AsyncClient *c, uint32_t time) {
93 | DEBUG_MSG_P(PSTR("[TELNET] Timeout on client #%d at %i\n"), i, time);
94 | c->close();
95 | }, 0);
96 |
97 | DEBUG_MSG_P(PSTR("[TELNET] Client #%d connected\n"), i);
98 |
99 | // send info and crash data
100 | welcome();
101 | debugDumpCrashInfo();
102 | debugClearCrashInfo();
103 |
104 | return;
105 |
106 | }
107 |
108 | }
109 |
110 | DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Too many connections\n"));
111 | client->onDisconnect([](void *s, AsyncClient *c) {
112 | c->free();
113 | delete c;
114 | });
115 | client->close(true);
116 |
117 | }
118 |
119 | // -----------------------------------------------------------------------------
120 | // Public API
121 | // -----------------------------------------------------------------------------
122 |
123 | unsigned char telnetWrite(unsigned char ch) {
124 | char data[1] = {ch};
125 | return _telnetWrite(data, 1);
126 | }
127 |
128 | void telnetSetup() {
129 |
130 | _telnetServer = new AsyncServer(TELNET_PORT);
131 | _telnetServer->onClient([](void *s, AsyncClient* c) {
132 | _telnetNewClient(c);
133 | }, 0);
134 | _telnetServer->begin();
135 |
136 | DEBUG_MSG_P(PSTR("[TELNET] Listening on port %d\n"), TELNET_PORT);
137 |
138 | }
139 |
140 | #endif // TELNET_SUPPORT
141 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/utils.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | UTILS MODULE
4 |
5 | Copyright (C) 2017 by Xose Pérez
6 |
7 | */
8 |
9 | String getIdentifier() {
10 | char buffer[20];
11 | snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), DEVICE_NAME, ESP.getChipId());
12 | return String(buffer);
13 | }
14 |
15 | String buildTime() {
16 |
17 | const char time_now[] = __TIME__; // hh:mm:ss
18 | unsigned int hour = atoi(&time_now[0]);
19 | unsigned int minute = atoi(&time_now[3]);
20 | unsigned int second = atoi(&time_now[6]);
21 |
22 | const char date_now[] = __DATE__; // Mmm dd yyyy
23 | const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
24 | unsigned int month = 0;
25 | for ( int i = 0; i < 12; i++ ) {
26 | if (strncmp(date_now, months[i], 3) == 0 ) {
27 | month = i + 1;
28 | break;
29 | }
30 | }
31 | unsigned int day = atoi(&date_now[3]);
32 | unsigned int year = atoi(&date_now[7]);
33 |
34 | char buffer[20];
35 | snprintf_P(
36 | buffer, sizeof(buffer), PSTR("%04d/%02d/%02d %02d:%02d:%02d"),
37 | year, month, day, hour, minute, second
38 | );
39 |
40 | return String(buffer);
41 |
42 | }
43 |
44 |
45 | unsigned long getUptime() {
46 |
47 | static unsigned long last_uptime = 0;
48 | static unsigned char uptime_overflows = 0;
49 |
50 | if (millis() < last_uptime) ++uptime_overflows;
51 | last_uptime = millis();
52 | unsigned long uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000);
53 |
54 | return uptime_seconds;
55 |
56 | }
57 |
58 | void heartbeat() {
59 | mqttSend(MQTT_TOPIC_HEARTBEAT, "1");
60 | DEBUG_MSG("[BEAT] Free heap: %d\n", ESP.getFreeHeap());
61 | }
62 |
63 | // -----------------------------------------------------------------------------
64 |
65 | void customReset(unsigned char status) {
66 | EEPROM.write(EEPROM_CUSTOM_RESET, status);
67 | EEPROM.commit();
68 | }
69 |
70 | unsigned char customReset() {
71 | static unsigned char status = 255;
72 | if (status == 255) {
73 | status = EEPROM.read(EEPROM_CUSTOM_RESET);
74 | if (status > 0) customReset(0);
75 | if (status > CUSTOM_RESET_MAX) status = 0;
76 | }
77 | return status;
78 | }
79 |
80 | // -----------------------------------------------------------------------------
81 |
82 | // Call this method on boot with start=true to increase the crash counter
83 | // Call it again once the system is stable to decrease the counter
84 | // If the counter reaches CRASH_COUNT_MAX then the system is flagged as unstable
85 | // setting _systemOK = false;
86 | //
87 | // An unstable system will only have serial access, WiFi in AP mode and OTA
88 |
89 | bool _systemStable = true;
90 |
91 | void systemCheck(bool stable) {
92 | unsigned char value = EEPROM.read(EEPROM_CRASH_COUNTER);
93 | if (stable) {
94 | value = 0;
95 | DEBUG_MSG_P(PSTR("[MAIN] System OK\n"));
96 | } else {
97 | if (++value > CRASH_COUNT_MAX) {
98 | _systemStable = false;
99 | value = 0;
100 | DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
101 | }
102 | }
103 | EEPROM.write(EEPROM_CRASH_COUNTER, value);
104 | EEPROM.commit();
105 | }
106 |
107 | bool systemCheck() {
108 | return _systemStable;
109 | }
110 |
111 | // -----------------------------------------------------------------------------
112 |
113 | char * ltrim(char * s) {
114 | char *p = s;
115 | while ((unsigned char) *p == ' ') ++p;
116 | return p;
117 | }
118 |
119 | double roundTo(double num, unsigned char positions) {
120 | double multiplier = 1;
121 | while (positions-- > 0) multiplier *= 10;
122 | return round(num * multiplier) / multiplier;
123 | }
124 |
--------------------------------------------------------------------------------
/esp8266/sonoffsc/wifi.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | WIFI MODULE
4 |
5 | Copyright (C) 2016-2017 by Xose Pérez
6 |
7 | */
8 |
9 | #include "JustWifi.h"
10 | #include
11 |
12 | // -----------------------------------------------------------------------------
13 | // WIFI
14 | // -----------------------------------------------------------------------------
15 |
16 | String getIP() {
17 | if (WiFi.getMode() == WIFI_AP) {
18 | return WiFi.softAPIP().toString();
19 | }
20 | return WiFi.localIP().toString();
21 | }
22 |
23 | String getNetwork() {
24 | if (WiFi.getMode() == WIFI_AP) {
25 | return jw.getAPSSID();
26 | }
27 | return WiFi.SSID();
28 | }
29 |
30 | void wifiDisconnect() {
31 | jw.disconnect();
32 | }
33 |
34 | void resetConnectionTimeout() {
35 | jw.resetReconnectTimeout();
36 | }
37 |
38 | bool wifiConnected() {
39 | return jw.connected();
40 | }
41 |
42 | bool createAP() {
43 | jw.disconnect();
44 | jw.resetReconnectTimeout();
45 | return jw.createAP();
46 | }
47 |
48 | void wifiConfigure() {
49 |
50 | jw.setHostname(getSetting("hostname").c_str());
51 | jw.setSoftAP(getSetting("hostname").c_str(), getSetting("adminPass", ADMIN_PASS).c_str());
52 | jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
53 | jw.setReconnectTimeout(WIFI_RECONNECT_INTERVAL);
54 | jw.setAPMode(WIFI_AP_MODE);
55 | jw.cleanNetworks();
56 |
57 | // If system is flagged unstable we do not init wifi networks
58 | if (!systemCheck()) return;
59 |
60 | int i;
61 | for (i = 0; i< WIFI_MAX_NETWORKS; i++) {
62 | if (getSetting("ssid" + String(i)).length() == 0) break;
63 | if (getSetting("ip" + String(i)).length() == 0) {
64 | jw.addNetwork(
65 | getSetting("ssid" + String(i)).c_str(),
66 | getSetting("pass" + String(i)).c_str()
67 | );
68 | } else {
69 | jw.addNetwork(
70 | getSetting("ssid" + String(i)).c_str(),
71 | getSetting("pass" + String(i)).c_str(),
72 | getSetting("ip" + String(i)).c_str(),
73 | getSetting("gw" + String(i)).c_str(),
74 | getSetting("mask" + String(i)).c_str(),
75 | getSetting("dns" + String(i)).c_str()
76 | );
77 | }
78 | }
79 |
80 | // Scan for best network only if we have more than 1 defined
81 | jw.scanNetworks(i>1);
82 |
83 | }
84 |
85 | void wifiStatus() {
86 |
87 | if (WiFi.getMode() == WIFI_AP_STA) {
88 | DEBUG_MSG_P(PSTR("[WIFI] MODE AP + STA --------------------------------\n"));
89 | } else if (WiFi.getMode() == WIFI_AP) {
90 | DEBUG_MSG_P(PSTR("[WIFI] MODE AP --------------------------------------\n"));
91 | } else if (WiFi.getMode() == WIFI_STA) {
92 | DEBUG_MSG_P(PSTR("[WIFI] MODE STA -------------------------------------\n"));
93 | } else {
94 | DEBUG_MSG_P(PSTR("[WIFI] MODE OFF -------------------------------------\n"));
95 | DEBUG_MSG_P(PSTR("[WIFI] No connection\n"));
96 | }
97 |
98 | if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) {
99 | DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), jw.getAPSSID().c_str());
100 | DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).c_str());
101 | DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.softAPIP().toString().c_str());
102 | DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
103 | }
104 |
105 | if ((WiFi.getMode() & WIFI_STA) == WIFI_STA) {
106 | DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
107 | DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
108 | DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
109 | DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
110 | DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
111 | DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
112 | DEBUG_MSG_P(PSTR("[WIFI] HOST %s\n"), WiFi.hostname().c_str());
113 | }
114 |
115 | DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
116 |
117 | }
118 |
119 | // Inject hardcoded networks
120 | void wifiInject() {
121 |
122 | #ifdef WIFI1_SSID
123 | if (getSetting("ssid", 0, "").length() == 0) setSetting("ssid", 0, WIFI1_SSID);
124 | #endif
125 | #ifdef WIFI1_PASS
126 | if (getSetting("pass", 0, "").length() == 0) setSetting("pass", 0, WIFI1_PASS);
127 | #endif
128 | #ifdef WIFI1_IP
129 | if (getSetting("ip", 0, "").length() == 0) setSetting("ip", 0, WIFI1_IP);
130 | #endif
131 | #ifdef WIFI1_GW
132 | if (getSetting("gw", 0, "").length() == 0) setSetting("gw", 0, WIFI1_GW);
133 | #endif
134 | #ifdef WIFI1_MASK
135 | if (getSetting("mask", 0, "").length() == 0) setSetting("mask", 0, WIFI1_MASK);
136 | #endif
137 | #ifdef WIFI1_DNS
138 | if (getSetting("dns", 0, "").length() == 0) setSetting("dns", 0, WIFI1_DNS);
139 | #endif
140 |
141 | #ifdef WIFI2_SSID
142 | if (getSetting("ssid", 1, "").length() == 0) setSetting("ssid", 1, WIFI2_SSID);
143 | #endif
144 | #ifdef WIFI2_PASS
145 | if (getSetting("pass", 1, "").length() == 0) setSetting("pass", 1, WIFI2_PASS);
146 | #endif
147 | #ifdef WIFI2_IP
148 | if (getSetting("ip", 1, "").length() == 0) setSetting("ip", 1, WIFI2_IP);
149 | #endif
150 | #ifdef WIFI2_GW
151 | if (getSetting("gw", 1, "").length() == 0) setSetting("gw", 1, WIFI2_GW);
152 | #endif
153 | #ifdef WIFI2_MASK
154 | if (getSetting("mask", 1, "").length() == 0) setSetting("mask", 1, WIFI2_MASK);
155 | #endif
156 | #ifdef WIFI2_DNS
157 | if (getSetting("dns", 1, "").length() == 0) setSetting("dns", 1, WIFI2_DNS);
158 | #endif
159 |
160 | }
161 |
162 | void wifiSetup() {
163 |
164 | wifiInject();
165 | wifiConfigure();
166 |
167 | // Message callbacks
168 | jw.onMessage([](justwifi_messages_t code, char * parameter) {
169 |
170 | #ifdef DEBUG_PORT
171 |
172 | if (code == MESSAGE_SCANNING) {
173 | DEBUG_MSG_P(PSTR("[WIFI] Scanning\n"));
174 | }
175 |
176 | if (code == MESSAGE_SCAN_FAILED) {
177 | DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n"));
178 | }
179 |
180 | if (code == MESSAGE_NO_NETWORKS) {
181 | DEBUG_MSG_P(PSTR("[WIFI] No networks found\n"));
182 | }
183 |
184 | if (code == MESSAGE_NO_KNOWN_NETWORKS) {
185 | DEBUG_MSG_P(PSTR("[WIFI] No known networks found\n"));
186 | }
187 |
188 | if (code == MESSAGE_FOUND_NETWORK) {
189 | DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter);
190 | }
191 |
192 | if (code == MESSAGE_CONNECTING) {
193 | DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter);
194 | }
195 |
196 | if (code == MESSAGE_CONNECT_WAITING) {
197 | // too much noise
198 | }
199 |
200 | if (code == MESSAGE_CONNECT_FAILED) {
201 | DEBUG_MSG_P(PSTR("[WIFI] Could not connect to %s\n"), parameter);
202 | }
203 |
204 | if (code == MESSAGE_CONNECTED) {
205 | wifiStatus();
206 | }
207 |
208 | if (code == MESSAGE_ACCESSPOINT_CREATED) {
209 | wifiStatus();
210 | }
211 |
212 | if (code == MESSAGE_DISCONNECTED) {
213 | DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n"));
214 | }
215 |
216 | if (code == MESSAGE_ACCESSPOINT_CREATING) {
217 | DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n"));
218 | }
219 |
220 | if (code == MESSAGE_ACCESSPOINT_FAILED) {
221 | DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n"));
222 | }
223 |
224 | #endif
225 |
226 | // Configure communications
227 | if (code == MESSAGE_CONNECTED) {
228 | commsConfigure();
229 | }
230 |
231 | // Configure mDNS
232 | #if MDNS_SUPPORT
233 |
234 | if (code == MESSAGE_CONNECTED || code == MESSAGE_ACCESSPOINT_CREATED) {
235 |
236 | if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) {
237 |
238 | DEBUG_MSG_P(PSTR("[MDNS] OK\n"));
239 |
240 | MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt());
241 | #if TELNET_SUPPORT
242 | MDNS.addService("telnet", "tcp", TELNET_PORT);
243 | #endif
244 |
245 | if (code == MESSAGE_CONNECTED) mqttDiscover();
246 |
247 | } else {
248 |
249 | DEBUG_MSG_P(PSTR("[MDNS] FAIL\n"));
250 |
251 | }
252 |
253 | }
254 |
255 | #endif
256 |
257 | // NTP connection reset
258 | #if NTP_SUPPORT
259 | if (code == MESSAGE_CONNECTED) {
260 | ntpConnect();
261 | }
262 | #endif
263 |
264 | });
265 |
266 | }
267 |
268 | void wifiLoop() {
269 | jw.loop();
270 | }
271 |
--------------------------------------------------------------------------------