diff --git a/Build-instructions-Bestway-WiFi-remote.pdf b/Build-instructions-Bestway-WiFi-remote.pdf index ce4bd0b7..1636fc4e 100644 Binary files a/Build-instructions-Bestway-WiFi-remote.pdf and b/Build-instructions-Bestway-WiFi-remote.pdf differ diff --git a/Code/4-wire-version/.vscode/extensions.json b/Code/4-wire-version/.vscode/extensions.json index 0f0d7401..080e70d0 100644 --- a/Code/4-wire-version/.vscode/extensions.json +++ b/Code/4-wire-version/.vscode/extensions.json @@ -1,7 +1,10 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/Code/4-wire-version/data/WebSocket.js b/Code/4-wire-version/data/WebSocket.js index 53036b65..b02f01b9 100644 --- a/Code/4-wire-version/data/WebSocket.js +++ b/Code/4-wire-version/data/WebSocket.js @@ -3,29 +3,29 @@ var connection; // command mapping const cmd = { - setTarget: 0, - toggleUnit: 1, - toggleBubbles: 2, - toggleHeater: 3, - togglePump: 4, - //resetq: 5, - restartEsp: 6, - //gettarget: 7, - resetTotals: 8, - resetTimerChlorine: 9, - resetTimerFilter: 10, - toggleHydroJets: 11, - toggleGodMode: 12 + setTarget: 0, + toggleUnit: 1, + toggleBubbles: 2, + toggleHeater: 3, + togglePump: 4, + //resetq: 5, + restartEsp: 6, + //gettarget: 7, + resetTotals: 8, + resetTimerChlorine: 9, + resetTimerFilter: 10, + toggleHydroJets: 11, + toggleGodMode: 12 }; // button element ID mapping const eid = { - toggleUnit: 'UNT', - toggleBubbles: 'AIR', - toggleHeater: 'HTR', - togglePump: 'FLT', - toggleHydroJets: 'JET', - toggleGodMode: 'GOD' + toggleUnit: 'UNT', + toggleBubbles: 'AIR', + toggleHeater: 'HTR', + togglePump: 'FLT', + toggleHydroJets: 'JET', + toggleGodMode: 'GOD' }; // to be used for setting the slider position once after loading to original values @@ -39,215 +39,212 @@ connect(); function connect() { - connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']); - - connection.onopen = function() - { - document.getElementById('header').style = "background-color: #00508F"; - initSlider = true; - }; - - connection.onerror = function(error) - { - console.log('WebSocket Error ', error); - document.getElementById('header').style = "background-color: #FF0000"; - connection.close(); - }; - - connection.onclose = function() - { - console.log('WebSocket connection closed, reconnecting in 5 s'); - document.getElementById('header').style = "background-color: #FF0000"; - setTimeout(function(){connect()}, 5000); - }; - - connection.onmessage = function(e) - { - handlemsg(e); - } + connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']); + + connection.onopen = function() + { + document.getElementById('header').style = "background-color: #00508F"; + initSlider = true; + }; + + connection.onerror = function(error) + { + console.log('WebSocket Error ', error); + document.getElementById('header').style = "background-color: #FF0000"; + connection.close(); + }; + + connection.onclose = function() + { + console.log('WebSocket connection closed, reconnecting in 5 s'); + document.getElementById('header').style = "background-color: #FF0000"; + setTimeout(function(){connect()}, 5000); + }; + + connection.onmessage = function(e) + { + handlemsg(e); + } } String.prototype.pad = function(String, len) { - var str = this; - while (str.length < len) - { - str = String + str; - } - return str; + var str = this; + while (str.length < len) + { + str = String + str; + } + return str; } function handlemsg(e) { - var msgobj = JSON.parse(e.data); - console.log(msgobj); - - if (msgobj.CONTENT == "OTHER") - { - // MQTT status - mqtt_states = [ - "CONNECTION_TIMEOUT", // -4 / the server didn't respond within the keepalive time - "CONNECTION_LOST", // -3 / the network connection was broken - "CONNECT_FAILED", // -2 / the network connection failed - "DISCONNECTED", // -1 / the client is disconnected cleanly - "CONNECTED", // 0 / the client is connected - "CONNECT_BAD_PROTOCOL", // 1 / the server doesn't support the requested version of MQTT - "CONNECT_BAD_CLIENT_ID", // 2 / the server rejected the client identifier - "CONNECT_UNAVAILABLE", // 3 / the server was unable to accept the connection - "CONNECT_BAD_CREDENTIALS", // 4 / the username/password were rejected - "CONNECT_UNAUTHORIZED" // 5 / the client was not authorized to connect - ] - document.getElementById('mqtt').innerHTML = "MQTT: " + mqtt_states[msgobj.MQTT + 4]; - - // hydro jets available - var jetsAvailable = false; - if (typeof(msgobj.HASJETS) != 'undefined') // TODO: figure out.. - { - jetsAvailable = true; - } - document.getElementById('jetstitle').style.display = (jetsAvailable ? 'inherit' : 'none'); - document.getElementById('jetsbutton').style.display = (jetsAvailable ? 'inherit' : 'none'); - document.getElementById('jetstotals').style.display = (jetsAvailable ? 'inherit' : 'none'); - - document.getElementById('ciotx').innerHTML = 'CIO TX: ' + (msgobj.CIOTX ? 'Active' : 'Dead'); - document.getElementById('dsptx').innerHTML = 'DSP TX: ' + (msgobj.DSPTX ? 'Active' : 'Dead'); - } - - if (msgobj.CONTENT == "STATES") - { - // temperature - document.getElementById('temp').min = (msgobj.UNT ? 20 : 68); - document.getElementById('temp').max = (msgobj.UNT ? 40 : 104); - document.getElementById('atlabel').innerHTML = (msgobj.UNT ? msgobj.TMP.toString() : String(msgobj.TMP * 1.8 + 32)); - document.getElementById('ttlabel').innerHTML = (msgobj.UNT ? msgobj.TGT.toString() : String(msgobj.TGT * 1.8 + 32)); - - // buttons - document.getElementById('AIR').checked = msgobj.AIR; - document.getElementById('UNT').checked = msgobj.UNT; - document.getElementById('FLT').checked = msgobj.FLT; - document.getElementById('JET').checked = msgobj.JET; - document.getElementById('GOD').checked = msgobj.GOD; - document.getElementById('HTR').checked = msgobj.RED || msgobj.GRN; - - // heater button color - var heaterColor = "CCC"; - if (msgobj.GRN == 2) heaterColor = "000"; - else if (msgobj.RED == 1) heaterColor = "FF0000"; - else if (msgobj.GRN == 1) heaterColor = "00FF00"; - document.getElementById('htrspan').style = "background-color: #" + heaterColor; - - // display - document.getElementById('dsp').innerHTML = "[" + String.fromCharCode(msgobj.CH1,msgobj.CH2,msgobj.CH3)+ "]"; - //document.getElementById('dsp').style.color = rgb((255-(dspBrtMultiplier*8))+(dspBrtMultiplier*(parseInt(msgobj.BRT)+1)), 0, 0); - - // set slider values (once) - if (initSlider) - { - document.getElementById('temp').value = msgobj.TGT; - //document.getElementById('brt').value = msgobj.BRT; - initSlider = false; - } - document.getElementById('sliderTempVal').innerHTML = document.getElementById('temp').value.toString(); - //document.getElementById('sliderBrtVal').innerHTML = document.getElementById('brt').value.toString(); - } - - if (msgobj.CONTENT == "TIMES") - { - var date = new Date(msgobj.TIME * 1000); - document.getElementById('time').innerHTML = date.toLocaleString(); - - // chlorine add reset timer - var clDate = (Date.now()/1000-msgobj.CLTIME)/(24*3600.0); - document.getElementById('cltimer').innerHTML = clDate.toFixed(2); - document.getElementById('cltimerbtn').className = (clDate > msgobj.CLINT ? "button_red" : "button"); - - // filter change reset timer - var fDate = (Date.now()/1000-msgobj.FTIME)/(24*3600.0); - document.getElementById('ftimer').innerHTML = fDate.toFixed(2); - document.getElementById('ftimerbtn').className = (fDate > msgobj.FINT ? "button_red" : "button"); - - // statistics - document.getElementById('heatingtime').innerHTML = s2dhms(msgobj.HEATINGTIME); - document.getElementById('uptime').innerHTML = s2dhms(msgobj.UPTIME); - document.getElementById('airtime').innerHTML = s2dhms(msgobj.AIRTIME); - document.getElementById('filtertime').innerHTML = s2dhms(msgobj.PUMPTIME); - document.getElementById('jettime').innerHTML = s2dhms(msgobj.JETTIME); - document.getElementById('cost').innerHTML = (msgobj.COST).toFixed(2); - document.getElementById('tttt').innerHTML = (msgobj.TTTT/3600).toFixed(2) + "h
(" + new Date(msgobj.TIME * 1000 + msgobj.TTTT * 1000).toLocaleString() + ")"; - } + var msgobj = JSON.parse(e.data); + console.log(msgobj); + + if (msgobj.CONTENT == "OTHER") + { + // MQTT status + mqtt_states = [ + "CONNECTION_TIMEOUT", // -4 / the server didn't respond within the keepalive time + "CONNECTION_LOST", // -3 / the network connection was broken + "CONNECT_FAILED", // -2 / the network connection failed + "DISCONNECTED", // -1 / the client is disconnected cleanly + "CONNECTED", // 0 / the client is connected + "CONNECT_BAD_PROTOCOL", // 1 / the server doesn't support the requested version of MQTT + "CONNECT_BAD_CLIENT_ID", // 2 / the server rejected the client identifier + "CONNECT_UNAVAILABLE", // 3 / the server was unable to accept the connection + "CONNECT_BAD_CREDENTIALS", // 4 / the username/password were rejected + "CONNECT_UNAUTHORIZED" // 5 / the client was not authorized to connect + ] + document.getElementById('mqtt').innerHTML = "MQTT: " + mqtt_states[msgobj.MQTT + 4]; + + // hydro jets available + document.getElementById('jetstitle').style.display = (msgobj.HASJETS ? 'table-row' : 'none'); + document.getElementById('jetsbutton').style.display = (msgobj.HASJETS ? 'table-row' : 'none'); + document.getElementById('jetstotals').style.display = (msgobj.HASJETS ? 'table-row' : 'none'); + + document.getElementById('ciotx').innerHTML = 'CIO TX: ' + (msgobj.CIOTX ? 'Active' : 'Dead'); + document.getElementById('dsptx').innerHTML = 'DSP TX: ' + (msgobj.DSPTX ? 'Active' : 'Dead'); + document.getElementById('fw').innerHTML = 'Firmware: ' + msgobj.FW; + document.getElementById('model').innerHTML = 'Model: ' + msgobj.MODEL; + } + + if (msgobj.CONTENT == "STATES") + { + // temperature + document.getElementById('temp').min = (msgobj.UNT ? 20 : 68); + document.getElementById('temp').max = (msgobj.UNT ? 40 : 104); + document.getElementById('atlabel').innerHTML = (msgobj.UNT ? msgobj.TMP.toString() : String(msgobj.TMP * 1.8 + 32)); + document.getElementById('ttlabel').innerHTML = (msgobj.UNT ? msgobj.TGT.toString() : String(msgobj.TGT * 1.8 + 32)); + + // buttons + document.getElementById('AIR').checked = msgobj.AIR; + document.getElementById('UNT').checked = msgobj.UNT; + document.getElementById('FLT').checked = msgobj.FLT; + document.getElementById('JET').checked = msgobj.JET; + document.getElementById('GOD').checked = msgobj.GOD; + document.getElementById('HTR').checked = msgobj.RED || msgobj.GRN; + + // heater button color + var heaterColor = "CCC"; + if (msgobj.GRN == 2) heaterColor = "000"; + else if (msgobj.RED == 1) heaterColor = "FF0000"; + else if (msgobj.GRN == 1) heaterColor = "00FF00"; + document.getElementById('htrspan').style = "background-color: #" + heaterColor; + + // display + document.getElementById('dsp').innerHTML = "[" + String.fromCharCode(msgobj.CH1,msgobj.CH2,msgobj.CH3)+ "]"; + //document.getElementById('dsp').style.color = rgb((255-(dspBrtMultiplier*8))+(dspBrtMultiplier*(parseInt(msgobj.BRT)+1)), 0, 0); + + // set slider values (once) + if (initSlider) + { + document.getElementById('temp').value = msgobj.TGT; + //document.getElementById('brt').value = msgobj.BRT; + initSlider = false; + } + document.getElementById('sliderTempVal').innerHTML = document.getElementById('temp').value.toString(); + //document.getElementById('sliderBrtVal').innerHTML = document.getElementById('brt').value.toString(); + } + + if (msgobj.CONTENT == "TIMES") + { + var date = new Date(msgobj.TIME * 1000); + document.getElementById('time').innerHTML = date.toLocaleString(); + + // chlorine add reset timer + var clDate = (Date.now()/1000-msgobj.CLTIME)/(24*3600.0); + document.getElementById('cltimer').innerHTML = clDate.toFixed(2); + document.getElementById('cltimerbtn').className = (clDate > msgobj.CLINT ? "button_red" : "button"); + + // filter change reset timer + var fDate = (Date.now()/1000-msgobj.FTIME)/(24*3600.0); + document.getElementById('ftimer').innerHTML = fDate.toFixed(2); + document.getElementById('ftimerbtn').className = (fDate > msgobj.FINT ? "button_red" : "button"); + + // statistics + document.getElementById('heatingtime').innerHTML = s2dhms(msgobj.HEATINGTIME); + document.getElementById('uptime').innerHTML = s2dhms(msgobj.UPTIME); + document.getElementById('airtime').innerHTML = s2dhms(msgobj.AIRTIME); + document.getElementById('filtertime').innerHTML = s2dhms(msgobj.PUMPTIME); + document.getElementById('jettime').innerHTML = s2dhms(msgobj.JETTIME); + document.getElementById('cost').innerHTML = (msgobj.COST).toFixed(2); + document.getElementById('tttt').innerHTML = (msgobj.TTTT/3600).toFixed(2) + "h
(" + new Date(msgobj.TIME * 1000 + msgobj.TTTT * 1000).toLocaleString() + ")"; + } }; function s2dhms(val) { - var day = 3600*24; - var hour = 3600; - var minute = 60; - var rem; - var days = Math.floor(val/day); - rem = val % day; - var hours = Math.floor(rem/hour); - rem = val % hour; - var minutes = Math.floor(rem/minute); - rem = val % minute; - var seconds = Math.floor(rem); - return days + "d " + hours.toString().pad("0", 2) + ":" + minutes.toString().pad("0", 2) + ":" + seconds.toString().pad("0", 2); + var day = 3600*24; + var hour = 3600; + var minute = 60; + var rem; + var days = Math.floor(val/day); + rem = val % day; + var hours = Math.floor(rem/hour); + rem = val % hour; + var minutes = Math.floor(rem/minute); + rem = val % minute; + var seconds = Math.floor(rem); + return days + "d " + hours.toString().pad("0", 2) + ":" + minutes.toString().pad("0", 2) + ":" + seconds.toString().pad("0", 2); } function sendCommand(val) { - // check command - if (typeof(cmd[val]) == 'undefined') - { - console.log("invalid command"); - return; - } - - // get and set value - var value = 0; - if (val == 'setTarget') - { - value = parseInt(document.getElementById('temp').value); - document.getElementById("sliderTempVal").innerHTML = value.toString(); - document.getElementById('ttlabel').innerHTML = value.toString(); - } - else if (val == 'toggleUnit') - { - var tmp; - value = document.getElementById('UNT').checked; - tmp = parseInt(document.getElementById('temp').value); - tmp = Math.floor(value ? String((tmp - 32) / 1.8) : String(tmp * 1.8 + 32)); - document.getElementById('temp').min = (value ? 20 : 68); - document.getElementById('temp').max = (value ? 40 : 104); - document.getElementById('temp').value = tmp; - document.getElementById('ttlabel').innerHTML = tmp; - document.getElementById("sliderTempVal").innerHTML = tmp; - } -// else if (val == 'setBrightness') -// { -// value = parseInt(document.getElementById('brt').value); -// document.getElementById("sliderBrtVal").innerHTML = value.toString(); -// document.getElementById("dsp").style.color = rgb((255-(dspBrtMultiplier*8))+(dspBrtMultiplier*(value+1)), 0, 0); -// } - else if (eid[val] && (val == 'toggleBubbles' || val == 'toggleHeater' || val == 'togglePump' || val == 'toggleHydroJets' || val == 'toggleGodMode')) - { - value = document.getElementById(eid[val]).checked; - } - - var obj = {}; - obj["CMD"] = cmd[val]; - obj["VALUE"] = value; - obj["XTIME"] = 0; - obj["INTERVAL"] = 0; - - var json = JSON.stringify(obj); - connection.send(json); - console.log(json); + // check command + if (typeof(cmd[val]) == 'undefined') + { + console.log("invalid command"); + return; + } + + // get and set value + var value = 0; + if (val == 'setTarget') + { + value = parseInt(document.getElementById('temp').value); + document.getElementById("sliderTempVal").innerHTML = value.toString(); + document.getElementById('ttlabel').innerHTML = value.toString(); + } + else if (val == 'toggleUnit') + { + var tmp; + value = document.getElementById('UNT').checked; + tmp = parseInt(document.getElementById('temp').value); + tmp = Math.floor(value ? String((tmp - 32) / 1.8) : String(tmp * 1.8 + 32)); + document.getElementById('temp').min = (value ? 20 : 68); + document.getElementById('temp').max = (value ? 40 : 104); + document.getElementById('temp').value = tmp; + document.getElementById('ttlabel').innerHTML = tmp; + document.getElementById("sliderTempVal").innerHTML = tmp; + } +// else if (val == 'setBrightness') +// { +// value = parseInt(document.getElementById('brt').value); +// document.getElementById("sliderBrtVal").innerHTML = value.toString(); +// document.getElementById("dsp").style.color = rgb((255-(dspBrtMultiplier*8))+(dspBrtMultiplier*(value+1)), 0, 0); +// } + else if (eid[val] && (val == 'toggleBubbles' || val == 'toggleHeater' || val == 'togglePump' || val == 'toggleHydroJets' || val == 'toggleGodMode')) + { + value = document.getElementById(eid[val]).checked; + } + + var obj = {}; + obj["CMD"] = cmd[val]; + obj["VALUE"] = value; + obj["XTIME"] = 0; + obj["INTERVAL"] = 0; + + var json = JSON.stringify(obj); + connection.send(json); + console.log(json); } function rgb(r, g, b) { - r = Math.floor(r); - g = Math.floor(g); - b = Math.floor(b); - return ["rgb(",r,",",g,",",b,")"].join(""); + r = Math.floor(r); + g = Math.floor(g); + b = Math.floor(b); + return ["rgb(",r,",",g,",",b,")"].join(""); } diff --git a/Code/4-wire-version/data/config.html b/Code/4-wire-version/data/config.html index 6d3364c3..b4a016bd 100644 --- a/Code/4-wire-version/data/config.html +++ b/Code/4-wire-version/data/config.html @@ -1,123 +1,123 @@ - Lay-Z-Spa Module | SPA Config - - - - - - - + Lay-Z-Spa Module | SPA Config + + + + + + + -
-
- SPA Config - -
- -
- Home - SPA Config - Network Config - MQTT Config - Directory - File Uploader - File Remover - -
- -
- - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - -
- -

seconds

(0=once, 1h=3600, 1d=86400, 1w=604800)

-
- -
-

Command queue

-

-

-
- - -
+
+
+ SPA Config + +
+ +
+ Home + SPA Config + Network Config + MQTT Config + Directory + File Uploader + File Remover + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +

seconds

(0=once, 1h=3600, 1d=86400, 1w=604800)

+
+ +
+

Command queue

+

+

+
+ + +
+ diff --git a/Code/4-wire-version/data/debug.html b/Code/4-wire-version/data/debug.html index cddc6a42..54f08350 100644 --- a/Code/4-wire-version/data/debug.html +++ b/Code/4-wire-version/data/debug.html @@ -1,49 +1,49 @@ - Lay-Z-Spa Module | Debug - - - - - - - + Lay-Z-Spa Module | Debug + + + + + + + -
-
- Debug - -
+
+
+ Debug + +
- + -
-

Check console (chrome: right click - inspect)

-
-
+
+

Check console (chrome: right click - inspect)

+
+
- + diff --git a/Code/4-wire-version/data/function.js b/Code/4-wire-version/data/function.js index e1f4a4cb..43ffc02d 100644 --- a/Code/4-wire-version/data/function.js +++ b/Code/4-wire-version/data/function.js @@ -1,27 +1,27 @@ function topNav() { - var x = document.getElementById("topnav"); - if (x.className === "topnav") { - x.className += " responsive"; - } else { - x.className = "topnav"; - } + var x = document.getElementById("topnav"); + if (x.className === "topnav") { + x.className += " responsive"; + } else { + x.className = "topnav"; + } } function togglePlainText(id) { - var x = document.getElementById(id); - if (x.type === "password") { - x.type = "text"; - } else { - x.type = "password"; - } + var x = document.getElementById(id); + if (x.type === "password") { + x.type = "text"; + } else { + x.type = "password"; + } } function validatePassword(id) { - var x = document.getElementById(id); - if (x.value == "") { - alert("Please enter a password to continue."); - return false; - } - return true; + var x = document.getElementById(id); + if (x.value == "") { + alert("Please enter a password to continue."); + return false; + } + return true; } diff --git a/Code/4-wire-version/data/index.html b/Code/4-wire-version/data/index.html index effa4538..8b0e20f8 100644 --- a/Code/4-wire-version/data/index.html +++ b/Code/4-wire-version/data/index.html @@ -1,184 +1,186 @@ - Lay-Z-Spa Module - - - - - - - - + Lay-Z-Spa Module + + + + + + + + -
-
- Lay-Z-Spa Module - -
+
+
+ Lay-Z-Spa Module + +
- + -
-
-

Temperature:

- - - - - - - - - -
Actual:n/a
Target:n/a
-
+
+
+

Temperature:

+ + + + + + + + + +
Actual:n/a
Target:n/a
+
-
- - - - -
-

[DSP]

-
-
+
+ + + + +
+

[DSP]

+
+
-
-

Control:

- - - - - - -
Set temperature:n/a
- - - - - - - - - - - - - - - - - - - -
Bubbles - - Heater - -
Pump - - Unit (F/C) - -
Take control - -
Hydrojets
-
- -
-
-
- -
-

Timer:

- - - - - - - - - -
Last chlorine add was n/a day(s) ago.
Last filter change was n/a day(s) ago.
-
- -
-

Totals:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Time:n/a
Heating Est.:n/a
Uptime:n/a
Pump:n/a
Heating:n/a
Air:n/a
Hydrojets:n/a
Estimated cost:n/a
-
+
+

Control:

+ + + + + + +
Set temperature:n/a
+ + + + + + + + + + + + + + + + + + + +
Bubbles + + Heater + +
Pump + + Unit (F/C) + +
Take control + +
Hydrojets
+
+ +
+
+
+ +
+

Timer:

+ + + + + + + + + +
Last chlorine add was n/a day(s) ago.
Last filter change was n/a day(s) ago.
+
+ +
+

Totals:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Time:n/a
Heating Est.:n/a
Uptime:n/a
Pump:n/a
Heating:n/a
Air:n/a
Hydrojets:n/a
Estimated cost:n/a
+
-
-

MQTT: loading status...

-

CIO TX: loading status...

-

DSP TX: loading status...

-
-
+
+

MQTT: loading status...

+

CIO TX: loading status...

+

DSP TX: loading status...

+

Firmware: loading status...

+

Model: loading status...

+
+
diff --git a/Code/4-wire-version/data/mqtt.html b/Code/4-wire-version/data/mqtt.html index 6fd591ff..701a9e1d 100644 --- a/Code/4-wire-version/data/mqtt.html +++ b/Code/4-wire-version/data/mqtt.html @@ -1,139 +1,139 @@ - Lay-Z-Spa Module | MQTT Config - - - - - - - + Lay-Z-Spa Module | MQTT Config + + + + + + + -
-
- MQTT Config - -
+
+
+ MQTT Config + +
- + -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IP Address: - - . - . - . -
Port:
Username:
Password:
Client ID:
Base Topic:
Telemetry Interval (s):
-
-
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IP Address: + + . + . + . +
Port:
Username:
Password:
Client ID:
Base Topic:
Telemetry Interval (s):
+
+
+ diff --git a/Code/4-wire-version/data/remove.html b/Code/4-wire-version/data/remove.html index 86fa2823..16569f71 100644 --- a/Code/4-wire-version/data/remove.html +++ b/Code/4-wire-version/data/remove.html @@ -1,41 +1,41 @@ - Lay-Z-Spa Module | File Remover - - - - - - - + Lay-Z-Spa Module | File Remover + + + + + + + -
-
- File Remover - -
+
+
+ File Remover + +
- + -
-

Use this page to remove a file from the ESP8266.

-
- - -
-
+
+

Use this page to remove a file from the ESP8266.

+
+ + +
+
diff --git a/Code/4-wire-version/data/success.html b/Code/4-wire-version/data/success.html index 532fceb3..309ee889 100644 --- a/Code/4-wire-version/data/success.html +++ b/Code/4-wire-version/data/success.html @@ -1,43 +1,43 @@ - Lay-Z-Spa Module | Success! - - - - - - - + Lay-Z-Spa Module | Success! + + + + + + + -
-
- Success! - -
+
+
+ Success! + +
- + -
-

The operation was successful.

-
+
+

The operation was successful.

+
-
-

Home

-

Upload a file

-

Remove a file

-
+
+

Home

+

Upload a file

+

Remove a file

+
diff --git a/Code/4-wire-version/data/upload.html b/Code/4-wire-version/data/upload.html index f9755528..ab599886 100644 --- a/Code/4-wire-version/data/upload.html +++ b/Code/4-wire-version/data/upload.html @@ -1,42 +1,42 @@ - Lay-Z-Spa Module | File Uploader - - - - - - - + Lay-Z-Spa Module | File Uploader + + + + + + + -
-
- File Uploader - -
+
+
+ File Uploader + +
- + -
-

Use this page to upload new files to the ESP8266.
- You can use compressed (deflated) files (files with a .gz extension) to save space and bandwidth.

-
- - -
-
+
+

Use this page to upload new files to the ESP8266.
+ You can use compressed (deflated) files (files with a .gz extension) to save space and bandwidth.

+
+ + +
+
diff --git a/Code/4-wire-version/data/wifi.html b/Code/4-wire-version/data/wifi.html index 5539c8af..6ed02d3a 100644 --- a/Code/4-wire-version/data/wifi.html +++ b/Code/4-wire-version/data/wifi.html @@ -1,238 +1,238 @@ - Lay-Z-Spa Module | Network Config - - - - - - - + Lay-Z-Spa Module | Network Config + + + + + + + -
-
- Network Config - -
+
+
+ Network Config + +
- + -
-

Access Point:

- - - - - - - - - - - - - - -
SSID:
Password:
-
+
+

Access Point:

+ + + + + + + + + + + + + + +
SSID:
Password:
+
-
-

Static IP:

- - - - - - - - - - - - - - - - - - - - - - - - - -
IP Address: - - . - . - . -
Gateway IP Address: - - . - . - . -
Subnet Mask: - - . - . - . -
DNS Server (primary): - - . - . - . -
DNS Server (secondary): - - . - . - . -
-
+
+

Static IP:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
IP Address: + + . + . + . +
Gateway IP Address: + + . + . + . +
Subnet Mask: + + . + . + . +
DNS Server (primary): + + . + . + . +
DNS Server (secondary): + + . + . + . +
+
-
- - - - -
- -
-
+
+ + + + +
+ +
+
-
-

Reset WiFi Config:

-

- This button resets the access point settings.
- The ESP will restart and start the "WiFi Configuration Manager". - Connect to it's access point and configure your WiFi (just like the first configuration). -

- - - - -
- -
-
-
+
+

Reset WiFi Config:

+

+ This button resets the access point settings.
+ The ESP will restart and start the "WiFi Configuration Manager". + Connect to it's access point and configure your WiFi (just like the first configuration). +

+ + + + +
+ +
+
+
+ diff --git a/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.cpp b/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.cpp index a47c1bf6..d9ee3a2c 100644 --- a/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.cpp +++ b/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.cpp @@ -1,17 +1,14 @@ #include "BWC_8266_4w.h" void CIO::begin() { - //Setup serial to CIO and DSP here according to my PCB + //Setup serial to CIO and DSP here according to chosen PCB. /* - CIO_RX = D3 - CIO_TX = D2 - DSP_TX = D6 - DSP_RX = D7 Devices are sending on their TX lines, so we read that with RX pins on the ESP + Hence the "backwards" parameters */ - cio_serial.begin(9600, SWSERIAL_8N1, D2, D3, false, 63); + cio_serial.begin(9600, SWSERIAL_8N1, CIO_TX, CIO_RX, false, 63); cio_serial.setTimeout(20); - dsp_serial.begin(9600, SWSERIAL_8N1, D6, D7, false, 63); + dsp_serial.begin(9600, SWSERIAL_8N1, DSP_TX, DSP_RX, false, 63); dsp_serial.setTimeout(20); states[TARGET] = 20; @@ -22,8 +19,8 @@ void CIO::begin() { states[CHAR1] = ' '; states[CHAR2] = ' '; states[CHAR3] = ' '; - cio_tx = false; - dsp_tx = false; + cio_tx_ok = false; + dsp_tx_ok = false; } void CIO::loop(void) { @@ -46,7 +43,7 @@ void CIO::loop(void) { } states[TEMPERATURE] = from_CIO_buf[TEMPINDEX]; states[ERROR] = from_CIO_buf[ERRORINDEX]; - cio_tx = true; //show the user that this line works (appears to work) + cio_tx_ok = true; //show the user that this line works (appears to work) //check if cio send error msg states[CHAR1] = ' '; states[CHAR2] = ' '; @@ -102,7 +99,7 @@ void CIO::loop(void) { } else { updateStates(); } - dsp_tx = true; //show the user that this line works (appears to work) + dsp_tx_ok = true; //show the user that this line works (appears to work) } } else { @@ -172,15 +169,11 @@ void CIO::updatePayload(){ void BWC::begin(void){ - _cio.begin(); - //_startNTP(); this is done from main.cpp - LittleFS.begin(); - _loadSettings(); - _loadCommandQueue(); - _saveRebootInfo(); - saveSettingsTimer.attach(3600.0, std::bind(&BWC::saveSettingsFlag, this)); - cio_tx = false; - dsp_tx = false; + _cio.begin(); + //_startNTP(); this is done from main.cpp + LittleFS.begin(); + cio_tx_ok = false; + dsp_tx_ok = false; _cltime = 0; _ftime = 0; _uptime = 0; @@ -199,6 +192,10 @@ void BWC::begin(void){ _tttt_time1 = DateTime.now(); _tttt_temp0 = 20; _tttt_temp1 = 20; + _loadSettings(); + _loadCommandQueue(); + _saveRebootInfo(); + saveSettingsTimer.attach(3600.0, std::bind(&BWC::saveSettingsFlag, this)); } void BWC::loop(){ @@ -217,69 +214,69 @@ void BWC::loop(){ if(_saveCmdqNeeded) _saveCommandQueue(); if(_saveSettingsNeeded) saveSettings(); ESP.wdtEnable(0); - cio_tx = _cio.cio_tx; - dsp_tx = _cio.dsp_tx; + cio_tx_ok = _cio.cio_tx_ok; + dsp_tx_ok = _cio.dsp_tx_ok; } bool BWC::qCommand(uint32_t cmd, uint32_t val, uint32_t xtime, uint32_t interval) { - //handle special commands - if(cmd == RESETQ){ - _qButtonLen = 0; - _qCommandLen = 0; - _saveCommandQueue(); - return true; - } - - //add parameters to _commandQ[rows][parameter columns] and sort the array on xtime. - int row = _qCommandLen; - if (_qCommandLen == MAXCOMMANDS) return false; - //sort array on xtime - for (int i = 0; i < _qCommandLen; i++) { - if (xtime < _commandQ[i][2]){ - //insert row at [i] - row = i; - break; - } - } - //make room for new row - for (int i = _qCommandLen; i > (row); i--){ - _commandQ[i][0] = _commandQ[i-1][0]; - _commandQ[i][1] = _commandQ[i-1][1]; - _commandQ[i][2] = _commandQ[i-1][2]; - _commandQ[i][3] = _commandQ[i-1][3]; - } - //add new command - _commandQ[row][0] = cmd; - _commandQ[row][1] = val; - _commandQ[row][2] = xtime; - _commandQ[row][3] = interval; - _qCommandLen++; - delay(0); - _saveCommandQueue(); - return true; + //handle special commands + if(cmd == RESETQ){ + _qButtonLen = 0; + _qCommandLen = 0; + _saveCommandQueue(); + return true; + } + + //add parameters to _commandQ[rows][parameter columns] and sort the array on xtime. + int row = _qCommandLen; + if (_qCommandLen == MAXCOMMANDS) return false; + //sort array on xtime + for (int i = 0; i < _qCommandLen; i++) { + if (xtime < _commandQ[i][2]){ + //insert row at [i] + row = i; + break; + } + } + //make room for new row + for (int i = _qCommandLen; i > (row); i--){ + _commandQ[i][0] = _commandQ[i-1][0]; + _commandQ[i][1] = _commandQ[i-1][1]; + _commandQ[i][2] = _commandQ[i-1][2]; + _commandQ[i][3] = _commandQ[i-1][3]; + } + //add new command + _commandQ[row][0] = cmd; + _commandQ[row][1] = val; + _commandQ[row][2] = xtime; + _commandQ[row][3] = interval; + _qCommandLen++; + delay(0); + _saveCommandQueue(); + return true; } void BWC::_handleCommandQ(void) { - bool restartESP = false; - if(_qCommandLen > 0) { - //cmp time with xtime. If more, then execute (adding buttons to buttonQ). - - if (_timestamp >= _commandQ[0][2]){ - switch (_commandQ[0][0]) { - - case SETTARGET: - _cio.states[TARGET] = _commandQ[0][1]; + bool restartESP = false; + if(_qCommandLen > 0) { + //cmp time with xtime. If more, then execute (adding buttons to buttonQ). + + if (_timestamp >= _commandQ[0][2]){ + switch (_commandQ[0][0]) { + + case SETTARGET: + _cio.states[TARGET] = _commandQ[0][1]; _cio.dataAvailable = true; if(_cio.states[TARGET] > 40) _cio.states[TARGET] = 40; //don't cook anyone - break; + break; - case SETUNIT: + case SETUNIT: _cio.states[UNITSTATE] = _commandQ[0][1]; _cio.dataAvailable = true; - break; + break; - case SETBUBBLES: + case SETBUBBLES: if(_cio.states[BUBBLESSTATE] == _commandQ[0][1]) break; //no change required _cio.dataAvailable = true; _currentStateIndex = JUMPTABLE[_currentStateIndex][BUBBLETOGGLE]; @@ -291,7 +288,7 @@ void BWC::_handleCommandQ(void) { if(ALLOWEDSTATES[_currentStateIndex][3] == 2) { qCommand(SETFULLPOWER, 1, _timestamp + 10, 0); } - // _cio.states[BUBBLESSTATE] = _commandQ[0][1]; + // _cio.states[BUBBLESSTATE] = _commandQ[0][1]; // if(_commandQ[0][1]){ // //bubbles is turned on. Limit H1, H2 and pump according to matrix // _cio.states[PUMPSTATE] = _cio.states[PUMPSTATE] && (COMB_MATRIX & 1<<1); @@ -305,9 +302,9 @@ void BWC::_handleCommandQ(void) { // //bubbles is turned off // _cio.heatbitmask = HEATBITMASK1 | HEATBITMASK2; //set full heating power // } - break; + break; - case SETHEATER: + case SETHEATER: if(_cio.states[HEATSTATE] == _commandQ[0][1]) break; //no change required _cio.dataAvailable = true; _currentStateIndex = JUMPTABLE[_currentStateIndex][HEATTOGGLE]; @@ -320,7 +317,7 @@ void BWC::_handleCommandQ(void) { qCommand(SETFULLPOWER, 1, _timestamp + 10, 0); } - // _cio.states[HEATSTATE] = _commandQ[0][1]; + // _cio.states[HEATSTATE] = _commandQ[0][1]; // if(_commandQ[0][1]) { // if(_commandQ[0][1] == 1) { // //start first heater element @@ -342,9 +339,9 @@ void BWC::_handleCommandQ(void) { // _cio.states[JETSSTATE] &= _cio.states[JETSSTATE] && (COMB_MATRIX & 1<<4); // } // } - break; + break; - case SETPUMP: + case SETPUMP: if(_cio.states[PUMPSTATE] == _commandQ[0][1]) break; //no change required //let pump run a bit to cool element _cio.dataAvailable = true; @@ -362,44 +359,44 @@ void BWC::_handleCommandQ(void) { qCommand(SETFULLPOWER, 1, _timestamp + 10, 0); } } - break; + break; - case REBOOTESP: - restartESP = true; - break; + case REBOOTESP: + restartESP = true; + break; - case GETTARGET: - - break; + case GETTARGET: + + break; - case RESETTIMES: - _uptime = 0; - _pumptime = 0; - _heatingtime = 0; - _airtime = 0; + case RESETTIMES: + _uptime = 0; + _pumptime = 0; + _heatingtime = 0; + _airtime = 0; _jettime = 0; - _uptime_ms = 0; - _pumptime_ms = 0; - _heatingtime_ms = 0; - _airtime_ms = 0; + _uptime_ms = 0; + _pumptime_ms = 0; + _heatingtime_ms = 0; + _airtime_ms = 0; _jettime_ms = 0; - _cost = 0; + _cost = 0; _kwh = 0; - _saveSettingsNeeded = true; + _saveSettingsNeeded = true; _cio.dataAvailable = true; - break; + break; - case RESETCLTIMER: - _cltime = _timestamp; - _saveSettingsNeeded = true; + case RESETCLTIMER: + _cltime = _timestamp; + _saveSettingsNeeded = true; _cio.dataAvailable = true; - break; + break; - case RESETFTIMER: - _ftime = _timestamp; - _saveSettingsNeeded = true; + case RESETFTIMER: + _ftime = _timestamp; + _saveSettingsNeeded = true; _cio.dataAvailable = true; - break; + break; case SETJETS: if(_cio.states[JETSSTATE] == _commandQ[0][1]) break; //no change required @@ -432,26 +429,29 @@ void BWC::_handleCommandQ(void) { else _cio.heatbitmask = HEATBITMASK1; break; - } - //If interval > 0 then append to commandQ with updated xtime. - if(_commandQ[0][3] > 0) qCommand(_commandQ[0][0],_commandQ[0][1],_commandQ[0][2]+_commandQ[0][3],_commandQ[0][3]); - //remove from commandQ and decrease qCommandLen - for(int i = 0; i < _qCommandLen-1; i++){ - _commandQ[i][0] = _commandQ[i+1][0]; - _commandQ[i][1] = _commandQ[i+1][1]; - _commandQ[i][2] = _commandQ[i+1][2]; - _commandQ[i][3] = _commandQ[i+1][3]; - } - _qCommandLen--; - _saveCommandQueue(); - if(restartESP) { - saveSettings(); - ESP.restart(); - } - } - } + } + //If interval > 0 then append to commandQ with updated xtime. + if(_commandQ[0][3] > 0) qCommand(_commandQ[0][0],_commandQ[0][1],_commandQ[0][2]+_commandQ[0][3],_commandQ[0][3]); + //remove from commandQ and decrease qCommandLen + for(int i = 0; i < _qCommandLen-1; i++){ + _commandQ[i][0] = _commandQ[i+1][0]; + _commandQ[i][1] = _commandQ[i+1][1]; + _commandQ[i][2] = _commandQ[i+1][2]; + _commandQ[i][3] = _commandQ[i+1][3]; + } + _qCommandLen--; + _saveCommandQueue(); + if(restartESP) { + Serial.println("saving settings"); + saveSettings(); + delay(3000); + Serial.println("restarting"); + ESP.restart(); + } + } + } } - + String BWC::getJSONStates() { // Allocate a temporary JsonDocument @@ -484,8 +484,8 @@ String BWC::getJSONStates() { String jsonmsg; if (serializeJson(doc, jsonmsg) == 0) { jsonmsg = "{\"error\": \"Failed to serialize message\"}"; - } - return jsonmsg; + } + return jsonmsg; } String BWC::getJSONTimes() { @@ -516,8 +516,8 @@ String BWC::getJSONTimes() { String jsonmsg; if (serializeJson(doc, jsonmsg) == 0) { jsonmsg = "{\"error\": \"Failed to serialize message\"}"; - } - return jsonmsg; + } + return jsonmsg; } String BWC::getJSONSettings(){ @@ -537,13 +537,13 @@ String BWC::getJSONSettings(){ doc["AUDIO"] = _audio; doc["REBOOTINFO"] = ESP.getResetReason(); doc["REBOOTTIME"] = DateTime.getBootTime(); - + // Serialize JSON to string String jsonmsg; if (serializeJson(doc, jsonmsg) == 0) { jsonmsg = "{\"error\": \"Failed to serialize message\"}"; - } - return jsonmsg; + } + return jsonmsg; } void BWC::setJSONSettings(String message){ @@ -567,7 +567,7 @@ void BWC::setJSONSettings(String message){ _finterval = doc["FINT"]; _clinterval = doc["CLINT"]; _audio = doc["AUDIO"]; - saveSettings(); + saveSettings(); } String BWC::getJSONCommandQueue(){ @@ -580,10 +580,10 @@ String BWC::getJSONCommandQueue(){ // Set the values in the document doc["LEN"] = _qCommandLen; for(int i = 0; i < _qCommandLen; i++){ - doc["CMD"][i] = _commandQ[i][0]; - doc["VALUE"][i] = _commandQ[i][1]; - doc["XTIME"][i] = _commandQ[i][2]; - doc["INTERVAL"][i] = _commandQ[i][3]; + doc["CMD"][i] = _commandQ[i][0]; + doc["VALUE"][i] = _commandQ[i][1]; + doc["XTIME"][i] = _commandQ[i][2]; + doc["INTERVAL"][i] = _commandQ[i][3]; } // Serialize JSON to file @@ -626,16 +626,16 @@ String BWC::getSerialBuffers(){ String json; if (serializeJson(doc, json) == 0) { json = "{\"error\": \"Failed to serialize message\"}"; - } - return json; + } + return json; } bool BWC::newData(){ bool result = _cio.dataAvailable; _cio.dataAvailable = false; - return result; + return result; } - + void BWC::_startNTP() { // setup this after wifi connected // you can use custom timezone,server and timeout @@ -693,11 +693,11 @@ void BWC::_loadSettings(){ } void BWC::saveSettingsFlag(){ - //ticker fails if duration is more than 71 min. So we use a counter every 60 minutes - if(++_tickerCount >= 3){ - _saveSettingsNeeded = true; - _tickerCount = 0; - } + //ticker fails if duration is more than 71 min. So we use a counter every 60 minutes + if(++_tickerCount >= 3){ + _saveSettingsNeeded = true; + _tickerCount = 0; + } } void BWC::saveSettings(){ @@ -715,15 +715,15 @@ void BWC::saveSettings(){ // Don't forget to change the capacity to match your requirements. // Use arduinojson.org/assistant to compute the capacity. DynamicJsonDocument doc(1024); - _heatingtime += _heatingtime_ms/1000; - _pumptime += _pumptime_ms/1000; - _airtime += _airtime_ms/1000; + _heatingtime += _heatingtime_ms/1000; + _pumptime += _pumptime_ms/1000; + _airtime += _airtime_ms/1000; _jettime += _jettime_ms/1000; - _uptime += _uptime_ms/1000; - _heatingtime_ms = 0; - _pumptime_ms = 0; - _airtime_ms = 0; - _uptime_ms = 0; + _uptime += _uptime_ms/1000; + _heatingtime_ms = 0; + _pumptime_ms = 0; + _airtime_ms = 0; + _uptime_ms = 0; // Set the values in the document doc["CLTIME"] = _cltime; doc["FTIME"] = _ftime; @@ -774,13 +774,13 @@ void BWC::_loadCommandQueue(){ // Set the values in the variables _qCommandLen = doc["LEN"]; for(int i = 0; i < _qCommandLen; i++){ - _commandQ[i][0] = doc["CMD"][i]; - _commandQ[i][1] = doc["VALUE"][i]; - _commandQ[i][2] = doc["XTIME"][i]; - _commandQ[i][3] = doc["INTERVAL"][i]; + _commandQ[i][0] = doc["CMD"][i]; + _commandQ[i][1] = doc["VALUE"][i]; + _commandQ[i][2] = doc["XTIME"][i]; + _commandQ[i][3] = doc["INTERVAL"][i]; } - file.close(); + file.close(); } void BWC::_saveCommandQueue(){ @@ -803,17 +803,17 @@ void BWC::_saveCommandQueue(){ // Set the values in the document doc["LEN"] = _qCommandLen; for(int i = 0; i < _qCommandLen; i++){ - doc["CMD"][i] = _commandQ[i][0]; - doc["VALUE"][i] = _commandQ[i][1]; - doc["XTIME"][i] = _commandQ[i][2]; - doc["INTERVAL"][i] = _commandQ[i][3]; + doc["CMD"][i] = _commandQ[i][0]; + doc["VALUE"][i] = _commandQ[i][1]; + doc["XTIME"][i] = _commandQ[i][2]; + doc["INTERVAL"][i] = _commandQ[i][3]; } // Serialize JSON to file if (serializeJson(doc, file) == 0) { Serial.println(F("Failed to write cmdq.txt")); } - file.close(); + file.close(); //revive the dog ESP.wdtEnable(0); @@ -837,7 +837,7 @@ void BWC::saveEventlog(){ // Set the values in the document for(uint16_t i = 0; i < sizeof(_cio.states); i++){ - doc[i] = _cio.states[i]; + doc[i] = _cio.states[i]; } doc["timestamp"] = DateTime.format(DateFormatter::SIMPLE); @@ -845,7 +845,7 @@ void BWC::saveEventlog(){ if (serializeJson(doc, file) == 0) { Serial.println(F("Failed to write eventlog.txt")); } - file.close(); + file.close(); //revive the dog ESP.wdtEnable(0); @@ -871,59 +871,59 @@ void BWC::_saveRebootInfo(){ Serial.println(F("Failed to write bootlog.txt")); } file.println(); - file.close(); + file.close(); } void BWC::_updateTimes(){ - uint32_t now = millis(); - static uint32_t prevtime; - int elapsedtime = now-prevtime; - prevtime = now; - if (elapsedtime < 0) return; //millis() rollover every 49 days - if(_cio.states[HEATREDSTATE]){ - _heatingtime_ms += elapsedtime; - } - if(_cio.states[PUMPSTATE]){ - _pumptime_ms += elapsedtime; - } - if(_cio.states[BUBBLESSTATE]){ - _airtime_ms += elapsedtime; - } - if(_cio.states[JETSSTATE]){ - _jettime_ms += elapsedtime; - } - _uptime_ms += elapsedtime; - - if(_uptime_ms > 1000000000){ - _heatingtime += _heatingtime_ms/1000; - _pumptime += _pumptime_ms/1000; - _airtime += _airtime_ms/1000; + uint32_t now = millis(); + static uint32_t prevtime; + int elapsedtime = now-prevtime; + prevtime = now; + if (elapsedtime < 0) return; //millis() rollover every 49 days + if(_cio.states[HEATREDSTATE]){ + _heatingtime_ms += elapsedtime; + } + if(_cio.states[PUMPSTATE]){ + _pumptime_ms += elapsedtime; + } + if(_cio.states[BUBBLESSTATE]){ + _airtime_ms += elapsedtime; + } + if(_cio.states[JETSSTATE]){ + _jettime_ms += elapsedtime; + } + _uptime_ms += elapsedtime; + + if(_uptime_ms > 1000000000){ + _heatingtime += _heatingtime_ms/1000; + _pumptime += _pumptime_ms/1000; + _airtime += _airtime_ms/1000; _jettime += _jettime_ms/1000; - _uptime += _uptime_ms/1000; - _heatingtime_ms = 0; - _pumptime_ms = 0; - _airtime_ms = 0; + _uptime += _uptime_ms/1000; + _heatingtime_ms = 0; + _pumptime_ms = 0; + _airtime_ms = 0; _jettime_ms = 0; - _uptime_ms = 0; - } - - _kwh = HEATER_WATTS * (_heatingtime+_heatingtime_ms/1000) / 3600 + + _uptime_ms = 0; + } + + _kwh = (HEATER_WATTS * (_heatingtime+_heatingtime_ms/1000) / 3600 + PUMP_WATTS * (_pumptime+_pumptime_ms/1000) / 3600 + BUBBLES_WATTS * (_airtime+_airtime_ms/1000) / 3600 + JETS_WATTS * (_jettime+_jettime_ms/1000) / 3600 + - IDLE_WATTS * (_uptime+_uptime_ms/1000) / 3600 / + IDLE_WATTS * (_uptime+_uptime_ms/1000) / 3600) / 1000.0; //Wh -> kWh - _cost = _price*_kwh; + _cost = _price*_kwh; } void BWC::print(String txt){ } uint8_t BWC::getState(int state){ - return _cio.states[state]; + return _cio.states[state]; } void BWC::reloadCommandQueue(){ - _loadCommandQueue(); - return; + _loadCommandQueue(); + return; } \ No newline at end of file diff --git a/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.h b/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.h index 2819f17e..05e13114 100644 --- a/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.h +++ b/Code/4-wire-version/lib/BWC4W/BWC_8266_4w.h @@ -19,6 +19,18 @@ #include #include +#ifdef PCB_V2 +const int CIO_RX = D1; +const int CIO_TX = D2; +const int DSP_TX = D4; +const int DSP_RX = D5; +#else +const int CIO_RX = D3; +const int CIO_TX = D2; +const int DSP_TX = D6; +const int DSP_RX = D7; +#endif + class CIO { public: @@ -37,8 +49,8 @@ class CIO { uint8_t to_DSP_buf[7]; //ESP to DSP uint8_t from_DSP_buf[7]; //DSP to ESP. We can ignore this message and send our own when ESP is in charge. uint8_t to_CIO_buf[7]; //Otherwise copy here. Buffer to send from ESP to CIO - bool cio_tx; //set to true when data received. Send to webinterface+serial for debugging - bool dsp_tx; //set to true when data received. Send to webinterface+serial for debugging + bool cio_tx_ok; //set to true when data received. Send to webinterface+serial for debugging + bool dsp_tx_ok; //set to true when data received. Send to webinterface+serial for debugging uint8_t heatbitmask; @@ -55,77 +67,77 @@ class CIO { class BWC { public: - void begin(void); + void begin(void); void loop(); - bool qCommand(uint32_t cmd, uint32_t val, uint32_t xtime, uint32_t interval); - bool newData(); - void saveEventlog(); - String getJSONStates(); - String getJSONTimes(); - String getJSONSettings(); - void setJSONSettings(String message); - String getJSONCommandQueue(); - void print(String txt); - uint8_t getState(int state); - void saveSettingsFlag(); - void saveSettings(); - Ticker saveSettingsTimer; - bool cio_tx; - bool dsp_tx; - void reloadCommandQueue(); - String encodeBufferToString(uint8_t buf[7]); - String getSerialBuffers(); + bool qCommand(uint32_t cmd, uint32_t val, uint32_t xtime, uint32_t interval); + bool newData(); + void saveEventlog(); + String getJSONStates(); + String getJSONTimes(); + String getJSONSettings(); + void setJSONSettings(String message); + String getJSONCommandQueue(); + void print(String txt); + uint8_t getState(int state); + void saveSettingsFlag(); + void saveSettings(); + Ticker saveSettingsTimer; + bool cio_tx_ok; + bool dsp_tx_ok; + void reloadCommandQueue(); + String encodeBufferToString(uint8_t buf[7]); + String getSerialBuffers(); private: CIO _cio; - uint32_t _commandQ[MAXCOMMANDS][4]; - int _qCommandLen = 0; //length of commandQ - uint32_t _buttonQ[MAXBUTTONS][4]; - int _qButtonLen = 0; //length of buttonQ - uint32_t _timestamp; - bool _newData = false; - uint32_t _cltime; - uint32_t _ftime; - uint32_t _uptime; - uint32_t _pumptime; - uint32_t _heatingtime; - uint32_t _airtime; - uint32_t _jettime; - uint32_t _uptime_ms; - uint32_t _pumptime_ms; - uint32_t _heatingtime_ms; - uint32_t _airtime_ms; - uint32_t _jettime_ms; - int32_t _timezone; - float _price; - uint32_t _finterval; - uint32_t _clinterval; - uint32_t _audio; - float _cost; - float _kwh; - bool _saveSettingsNeeded = false; - bool _saveEventlogNeeded = false; - bool _saveCmdqNeeded = false; - int _latestTarget; - int _tickerCount; - bool _sliderPrio = true; - uint8_t _currentStateIndex = 0; - uint32_t _tttt_time0; //time at previous temperature change - uint32_t _tttt_time1; //time at last temperature change - int _tttt_temp0; //temp after previous change - int _tttt_temp1; //temp after last change - int _tttt; //time to target temperature after subtracting running time since last calculation - int _tttt_calculated; //constant between calculations - - void _qButton(uint32_t btn, uint32_t state, uint32_t value, uint32_t maxduration); - void _handleCommandQ(void); - void _handleButtonQ(void); - void _startNTP(); - void _loadSettings(); - void _loadCommandQueue(); - void _saveCommandQueue(); - void _saveRebootInfo(); - void _updateTimes(); + uint32_t _commandQ[MAXCOMMANDS][4]; + int _qCommandLen = 0; //length of commandQ + uint32_t _buttonQ[MAXBUTTONS][4]; + int _qButtonLen = 0; //length of buttonQ + uint32_t _timestamp; + bool _newData = false; + uint32_t _cltime; + uint32_t _ftime; + uint32_t _uptime; + uint32_t _pumptime; + uint32_t _heatingtime; + uint32_t _airtime; + uint32_t _jettime; + uint32_t _uptime_ms; + uint32_t _pumptime_ms; + uint32_t _heatingtime_ms; + uint32_t _airtime_ms; + uint32_t _jettime_ms; + int32_t _timezone; + float _price; + uint32_t _finterval; + uint32_t _clinterval; + uint32_t _audio; + float _cost; + float _kwh; + bool _saveSettingsNeeded = false; + bool _saveEventlogNeeded = false; + bool _saveCmdqNeeded = false; + int _latestTarget; + int _tickerCount; + bool _sliderPrio = true; + uint8_t _currentStateIndex = 0; + uint32_t _tttt_time0; //time at previous temperature change + uint32_t _tttt_time1; //time at last temperature change + int _tttt_temp0; //temp after previous change + int _tttt_temp1; //temp after last change + int _tttt; //time to target temperature after subtracting running time since last calculation + int _tttt_calculated; //constant between calculations + + void _qButton(uint32_t btn, uint32_t state, uint32_t value, uint32_t maxduration); + void _handleCommandQ(void); + void _handleButtonQ(void); + void _startNTP(); + void _loadSettings(); + void _loadCommandQueue(); + void _saveCommandQueue(); + void _saveRebootInfo(); + void _updateTimes(); }; #endif diff --git a/Code/4-wire-version/lib/BWC4W/BWC_8266_4w_globals.h b/Code/4-wire-version/lib/BWC4W/BWC_8266_4w_globals.h index 9ece82ee..fa3fa0d8 100644 --- a/Code/4-wire-version/lib/BWC4W/BWC_8266_4w_globals.h +++ b/Code/4-wire-version/lib/BWC4W/BWC_8266_4w_globals.h @@ -2,11 +2,11 @@ #define BWC_8266_4W_GLOBALS_H #include //Commands to CIO -// 130 = bubbles -// 133 = filter pump -// 136 = Jets -// 138 = Jets and bubbles -// 181 = heating both elements with filter pump +// 130 = bubbles +// 133 = filter pump +// 136 = Jets +// 138 = Jets and bubbles +// 181 = heating both elements with filter pump //only declarations here please! Definitions belong in the cpp-file @@ -14,48 +14,48 @@ enum States: byte { - LOCKEDSTATE, - POWERSTATE, - UNITSTATE, - BUBBLESSTATE, - HEATGRNSTATE, - HEATREDSTATE, - HEATSTATE, - PUMPSTATE, - TEMPERATURE, - TARGET, - CHAR1, - CHAR2, - CHAR3, - JETSSTATE, - ERROR + LOCKEDSTATE, + POWERSTATE, + UNITSTATE, + BUBBLESSTATE, + HEATGRNSTATE, + HEATREDSTATE, + HEATSTATE, + PUMPSTATE, + TEMPERATURE, + TARGET, + CHAR1, + CHAR2, + CHAR3, + JETSSTATE, + ERROR }; enum Commands: byte { - SETTARGET, - SETUNIT, - SETBUBBLES, - SETHEATER, - SETPUMP, - RESETQ, - REBOOTESP, - GETTARGET, - RESETTIMES, - RESETCLTIMER, - RESETFTIMER, - SETJETS, - SETGODMODE, - SETFULLPOWER + SETTARGET, + SETUNIT, + SETBUBBLES, + SETHEATER, + SETPUMP, + RESETQ, + REBOOTESP, + GETTARGET, + RESETTIMES, + RESETCLTIMER, + RESETFTIMER, + SETJETS, + SETGODMODE, + SETFULLPOWER }; enum ToggleButtons: byte { - BUBBLETOGGLE, - JETSTOGGLE, - PUMPTOGGLE, - HEATTOGGLE + BUBBLETOGGLE, + JETSTOGGLE, + PUMPTOGGLE, + HEATTOGGLE }; const int MAXCOMMANDS = 11; @@ -69,42 +69,42 @@ const int JETS_WATTS = 800; #ifdef NO54173 /*combination matrix - Heater1 Heater2 Bubbles Jets Pump -H1 - 1 1 0 1 -H2 - 0 0 1 -B - 1 1 -J - 0 + Heater1 Heater2 Bubbles Jets Pump +H1 - 1 1 0 1 +H2 - 0 0 1 +B - 1 1 +J - 0 P ten boolean values above stored in a word "comb_matrix" Heater and pump values are still partly hardcoded in combination with the heatbitmasks. Do not change the heatbitmasks below unless you really know what you are doing. */ -// const uint16_t COMB_MATRIX = 0x3CF; // B0000001111001111; +// const uint16_t COMB_MATRIX = 0x3CF; // B0000001111001111; //what row in allowedstates to go to when pressing Bubbles, Jets, Pump, Heat (columns in that order) //Example: We are in state zero (first row). If we press Bubbles (first column) then there is a 6 //meaning current state (row) is now 6. According to ALLOWEDSTATES table, we turn on Bubbles and keep //everything else off. (1,0,0,0) const uint8_t JUMPTABLE[][4] = { - {6,4,1,3}, - {7,4,0,3}, - {3,5,6,7}, - {2,4,0,1}, - {5,0,1,3}, - {4,6,1,3}, - {0,5,7,2}, - {1,5,6,2} + {6,4,1,3}, + {7,4,0,3}, + {3,5,6,7}, + {2,4,0,1}, + {5,0,1,3}, + {4,6,1,3}, + {0,5,7,2}, + {1,5,6,2} }; //Bubbles, Jets, Pump, Heat const uint8_t ALLOWEDSTATES[][4] = { - {0,0,0,0}, - {0,0,1,0}, - {1,0,1,1}, - {0,0,1,2}, //the "2" means both heater elements - {0,1,0,0}, - {1,1,0,0}, - {1,0,0,0}, - {1,0,1,0} + {0,0,0,0}, + {0,0,1,0}, + {1,0,1,1}, + {0,0,1,2}, //the "2" means both heater elements + {0,1,0,0}, + {1,1,0,0}, + {1,0,0,0}, + {1,0,1,0} }; //cio @@ -117,12 +117,14 @@ const uint8_t DSP_CHECKSUMINDEX = 5; const uint8_t PAYLOADSIZE = 7; -const uint8_t PUMPBITMASK = B00000101; //5 -const uint8_t BUBBLESBITMASK = B00000010; //2 -const uint8_t JETSBITMASK = B00001000; //8 -const uint8_t HEATBITMASK1 = B00110000; //48 heater stage 1 = 50% -const uint8_t HEATBITMASK2 = B01000000; //64 heater stage 2 = 100% -const uint8_t POWERBITMASK = B10000000; //128 +const uint8_t PUMPBITMASK = B00000101; //5 +const uint8_t BUBBLESBITMASK = B00000010; //2 +const uint8_t JETSBITMASK = B00001000; //8 +const uint8_t HEATBITMASK1 = B00110000; //48 heater stage 1 = 50% +const uint8_t HEATBITMASK2 = B01000000; //64 heater stage 2 = 100% +const uint8_t POWERBITMASK = B10000000; //128 +const bool HASJETS = true; +const String MYMODEL = "NO54173"; #endif #ifdef NO54138 @@ -132,19 +134,19 @@ const uint8_t POWERBITMASK = B10000000; //128 //meaning current state (row) is now 6. According to ALLOWEDSTATES table, we turn on Bubbles and keep //everything else off. (1,0,0,0) const uint8_t JUMPTABLE[][4] = { - {1,2,3,4}, - {0,2,3,4}, - {1,0,3,4}, - {1,2,0,4}, - {1,2,0,3} + {1,2,3,4}, + {0,2,3,4}, + {1,0,3,4}, + {1,2,0,4}, + {1,2,0,3} }; //Bubbles, Jets, Pump, Heat const uint8_t ALLOWEDSTATES[][4] = { - {0,0,0,0}, - {1,0,0,0}, - {0,1,0,0}, - {0,0,1,0}, - {0,0,1,2} //the "2" means both heater elements + {0,0,0,0}, + {1,0,0,0}, + {0,1,0,0}, + {0,0,1,0}, + {0,0,1,2} //the "2" means both heater elements }; //cio @@ -157,16 +159,18 @@ const uint8_t DSP_CHECKSUMINDEX = 5; const uint8_t PAYLOADSIZE = 7; -const uint8_t PUMPBITMASK = B00000101; //5 -const uint8_t BUBBLESBITMASK = B00000010; //2 -const uint8_t JETSBITMASK = B00001000; //8 -const uint8_t HEATBITMASK1 = B00000000; //0 heater stage 1 = off -const uint8_t HEATBITMASK2 = B00110000; //48 heater stage 2 = on +const uint8_t PUMPBITMASK = B00000101; //5 +const uint8_t BUBBLESBITMASK = B00000010; //2 +const uint8_t JETSBITMASK = B00001000; //8 +const uint8_t HEATBITMASK1 = B00000000; //0 heater stage 1 = off +const uint8_t HEATBITMASK2 = B00110000; //48 heater stage 2 = on //lines below should be tested. It would be consistent with 54173 model. //If heating is slow this is probably the cause but I don't want to change it before someone tests it. -//const uint8_t HEATBITMASK1 = B00110000; //48 heater stage 1 = 50% -//const uint8_t HEATBITMASK2 = B01000000; //64 heater stage 2 = 100% -const uint8_t POWERBITMASK = B10000000; //128 +//const uint8_t HEATBITMASK1 = B00110000; //48 heater stage 1 = 50% +//const uint8_t HEATBITMASK2 = B01000000; //64 heater stage 2 = 100% +const uint8_t POWERBITMASK = B10000000; //128 +const bool HASJETS = true; +const String MYMODEL = "NO54138"; #endif #ifdef NO54123 @@ -177,17 +181,17 @@ const uint8_t POWERBITMASK = B10000000; //128 //meaning current state (row) is now 6. According to ALLOWEDSTATES table, we turn on Bubbles and keep //everything else off. (1,0,0,0) const uint8_t JUMPTABLE[][4] = { - {1,0,2,3}, - {0,1,2,3}, - {1,2,0,3}, - {1,3,0,2} + {1,0,2,3}, + {0,1,2,3}, + {1,2,0,3}, + {1,3,0,2} }; //Bubbles, Jets, Pump, Heat const uint8_t ALLOWEDSTATES[][4] = { - {0,0,0,0}, - {1,0,0,0}, - {0,0,1,0}, - {0,0,1,2} //the "2" means both heater elements + {0,0,0,0}, + {1,0,0,0}, + {0,0,1,0}, + {0,0,1,2} //the "2" means both heater elements }; //cio @@ -200,33 +204,35 @@ const uint8_t DSP_CHECKSUMINDEX = 5; const uint8_t PAYLOADSIZE = 7; -const uint8_t PUMPBITMASK = B00010000; //1 << 4; -const uint8_t BUBBLESBITMASK = B00100000; //1 << 5; -const uint8_t JETSBITMASK = B00000000; //0; //no jets on this machine. -const uint8_t HEATBITMASK1 = B00000010; //(1 << 1) "stage 1" -const uint8_t HEATBITMASK2 = B00001000; //(1 << 3) "stage 2" (thanks @dietmar-1 for testing and reporting this) -const uint8_t POWERBITMASK = B00000001; //1; +const uint8_t PUMPBITMASK = B00010000; //1 << 4; +const uint8_t BUBBLESBITMASK = B00100000; //1 << 5; +const uint8_t JETSBITMASK = B00000000; //0; //no jets on this machine. +const uint8_t HEATBITMASK1 = B00000010; //(1 << 1) "stage 1" +const uint8_t HEATBITMASK2 = B00001000; //(1 << 3) "stage 2" (thanks @dietmar-1 for testing and reporting this) +const uint8_t POWERBITMASK = B00000001; //1; +const bool HASJETS = false; +const String MYMODEL = "NO54123"; #endif #ifdef NO54154 //WARNING: THIS DEVICE HAS DIFFERENT PINOUTS!!! CHECK BEFORE USING //Requested by @chunkysteveo const uint8_t JUMPTABLE[][4] = { - {3,0,1,2}, - {4,1,0,2}, - {5,2,0,1}, - {0,3,4,5}, - {1,4,3,5}, - {2,5,3,4} + {3,0,1,2}, + {4,1,0,2}, + {5,2,0,1}, + {0,3,4,5}, + {1,4,3,5}, + {2,5,3,4} }; //Bubbles, Jets, Pump, Heat const uint8_t ALLOWEDSTATES[][4] = { - {0,0,0,0}, - {0,0,1,0}, - {0,0,1,2}, - {1,0,0,0}, //the "2" means both heater elements - {1,0,1,0}, - {1,0,1,1} + {0,0,0,0}, + {0,0,1,0}, + {0,0,1,2}, + {1,0,0,0}, //the "2" means both heater elements + {1,0,1,0}, + {1,0,1,1} }; //cio @@ -239,12 +245,14 @@ const uint8_t DSP_CHECKSUMINDEX = 5; const uint8_t PAYLOADSIZE = 7; -const uint8_t PUMPBITMASK = B00010000; //1 << 4; -const uint8_t BUBBLESBITMASK = B00100000; //1 << 5; -const uint8_t JETSBITMASK = B00000000; //0; //no jets on this machine. -const uint8_t HEATBITMASK1 = B00000010; //(1 << 1) "stage 1" -const uint8_t HEATBITMASK2 = B00001000; //(1 << 3) "stage 2" (thanks @dietmar-1 for testing and reporting this) -const uint8_t POWERBITMASK = B00000001; //1; +const uint8_t PUMPBITMASK = B00010000; //1 << 4; +const uint8_t BUBBLESBITMASK = B00100000; //1 << 5; +const uint8_t JETSBITMASK = B00000000; //0; //no jets on this machine. +const uint8_t HEATBITMASK1 = B00000010; //(1 << 1) "stage 1" +const uint8_t HEATBITMASK2 = B00001000; //(1 << 3) "stage 2" (thanks @dietmar-1 for testing and reporting this) +const uint8_t POWERBITMASK = B00000001; //1; +const bool HASJETS = false; +const String MYMODEL = "NO54154"; #endif #endif \ No newline at end of file diff --git a/Code/4-wire-version/lib/BWC4W/model.h b/Code/4-wire-version/lib/BWC4W/model.h index 7837b604..ffd99a54 100644 --- a/Code/4-wire-version/lib/BWC4W/model.h +++ b/Code/4-wire-version/lib/BWC4W/model.h @@ -7,4 +7,8 @@ //WARNING: DEVICES HAVE DIFFERENT PINOUTS!!! CHECK BEFORE USING -//The 54123 should work also for 54112 judging from a comment from @jenswalit in the forum \ No newline at end of file +//The 54123 should work also for 54112 judging from a comment from @jenswalit in the forum + +//If using/testing the new PCB choose PCB_V2 +#define PCB_V1 +//#define PCB_V2 //The PCB with rounded corners \ No newline at end of file diff --git a/Code/4-wire-version/lib/BWC4W/platformio.ini b/Code/4-wire-version/lib/BWC4W/platformio.ini deleted file mode 100644 index dc196415..00000000 --- a/Code/4-wire-version/lib/BWC4W/platformio.ini +++ /dev/null @@ -1,35 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[platformio] -src_dir = src -data_dir = data - -[env:nodemcuv2] -platform = espressif8266@^2 -board = nodemcuv2 -framework = arduino -lib_deps = - bblanchon/ArduinoJson - mcxiaoke/ESPDateTime - links2004/WebSockets@^2.3.3 - knolleary/PubSubClient@^2.8 - tzapu/WiFiManager@^0.16 -board_build.filesystem = littlefs -monitor_speed = 115200 - -; Uncomment the lines below by removing semicolons and edit IP for OTA upload. -; You have to upload via USB cable the first time. (upload_protocol = esptool) -; Make sure to use a data-USB cable. There is power only cables that wont work. -upload_protocol = esptool -;upload_protocol = espota -;upload_port = 192.168.4.121 -;upload_flags = -; --auth=esp8266 diff --git a/Code/4-wire-version/platformio.ini b/Code/4-wire-version/platformio.ini index 1013bb6d..f8a9c840 100644 --- a/Code/4-wire-version/platformio.ini +++ b/Code/4-wire-version/platformio.ini @@ -34,6 +34,6 @@ monitor_speed = 115200 ; Make sure to use a data-USB cable. There is power only cables that wont work. upload_protocol = esptool ;upload_protocol = espota -;upload_port = 192.168.4.121 +;upload_port = layzspa.local ;upload_flags = ; --auth=esp8266 diff --git a/Code/4-wire-version/src/config.h b/Code/4-wire-version/src/config.h index 83d0dffd..2163f78a 100644 --- a/Code/4-wire-version/src/config.h +++ b/Code/4-wire-version/src/config.h @@ -1,7 +1,8 @@ #include #include -#define LEGACY_NAME "BW4W_1.0" +#define LEGACY_NAME "layzspa" +#define FW_VERSION "4W_2022-05-20" /* * Miscellaneous diff --git a/Code/4-wire-version/src/main.cpp b/Code/4-wire-version/src/main.cpp index f7732c09..5477f3f4 100644 --- a/Code/4-wire-version/src/main.cpp +++ b/Code/4-wire-version/src/main.cpp @@ -198,13 +198,31 @@ void sendWS() } // send other info - String other = - String("{\"CONTENT\":\"OTHER\",\"MQTT\":") + String(mqttClient.state()) + - String(",\"CIOTX\":") + String(bwc.cio_tx) + - String(",\"DSPTX\":") + String(bwc.dsp_tx) + - String(",\"RSSI\":") + String(WiFi.RSSI()) + - String("}"); - webSocket.broadcastTXT(other); + json = getOtherInfo(); + webSocket.broadcastTXT(json); +} + +String getOtherInfo() +{ + DynamicJsonDocument doc(1024); + String json = ""; + // Set the values in the document + doc["CONTENT"] = "OTHER"; + doc["CIOTX"] = bwc.cio_tx_ok; + doc["DSPTX"] = bwc.dsp_tx_ok; + doc["HASJETS"] = HASJETS; + doc["RSSI"] = WiFi.RSSI(); + doc["IP"] = WiFi.localIP().toString(); + doc["SSID"] = WiFi.SSID(); + doc["FW"] = FW_VERSION; + doc["MODEL"] = MYMODEL; + + // Serialize JSON to string + if (serializeJson(doc, json) == 0) + { + json = "{\"error\": \"Failed to serialize message\"}"; + } + return json; } /** diff --git a/Code/4-wire-version/src/main.h b/Code/4-wire-version/src/main.h index 56b2d113..df5c7664 100644 --- a/Code/4-wire-version/src/main.h +++ b/Code/4-wire-version/src/main.h @@ -67,6 +67,7 @@ const int myoutputpin = D8; void handleAUX(); void sendWS(); +String getOtherInfo(); void sendMQTT(); void startWiFi(); diff --git a/Code/6-wire-version/data/WebSocket.js b/Code/6-wire-version/data/WebSocket.js index 58821c06..73c5bff5 100644 --- a/Code/6-wire-version/data/WebSocket.js +++ b/Code/6-wire-version/data/WebSocket.js @@ -98,11 +98,12 @@ function handlemsg(e) ] document.getElementById('mqtt').innerHTML = "MQTT: " + mqtt_states[msgobj.MQTT + 4]; document.getElementById('fw').innerHTML = "Firmware version: " + msgobj.FW; + document.getElementById('model').innerHTML = "Model: " + msgobj.MODEL; document.getElementById('rssi').innerHTML = "RSSI: " + msgobj.RSSI; // hydro jets available - document.getElementById('jets').style.display = (msgobj.HASJETS ? 'inherit' : 'none'); - document.getElementById('jetstotals').style.display = (msgobj.HASJETS ? 'inherit' : 'none'); + document.getElementById('jets').style.display = (msgobj.HASJETS ? 'table-row' : 'none'); + document.getElementById('jetstotals').style.display = (msgobj.HASJETS ? 'table-row' : 'none'); } if (msgobj.CONTENT == "STATES") diff --git a/Code/6-wire-version/data/config.html b/Code/6-wire-version/data/config.html index 3af77adf..cd334797 100644 --- a/Code/6-wire-version/data/config.html +++ b/Code/6-wire-version/data/config.html @@ -1,119 +1,126 @@ - Lay-Z-Spa Module | SPA Config - - - - - - - + Lay-Z-Spa Module | SPA Config + + + + + + + -
-
- SPA Config - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - -
- -

seconds

(0=once, 1h=3600, 1d=86400, 1w=604800)

-
- -
-

Command queue

-

-

-
- -
-

Last boot: loading...

-
-
+
+
+ SPA Config + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +

seconds

(0=once, 1h=3600, 1d=86400, 1w=604800)

+
+ +
+

Command queue

+

+

+
+ +
+

Last boot: loading...

+
+
+ diff --git a/Code/6-wire-version/data/index.html b/Code/6-wire-version/data/index.html index 9bf19c0a..67a61b85 100644 --- a/Code/6-wire-version/data/index.html +++ b/Code/6-wire-version/data/index.html @@ -68,7 +68,7 @@

Control:

- +
Set brightness: n/a
@@ -177,6 +177,7 @@

Totals:

MQTT: loading status...

Firmware version: loading status...

+

Model: loading status...

RSSI: loading status...

diff --git a/Code/6-wire-version/lib/BWC/BWC_6w_type1.cpp b/Code/6-wire-version/lib/BWC/BWC_6w_type1.cpp new file mode 100644 index 00000000..009aa2a0 --- /dev/null +++ b/Code/6-wire-version/lib/BWC/BWC_6w_type1.cpp @@ -0,0 +1,395 @@ +#include "BWC_6w_type1.h" +#include "pitches.h" + +CIO *pointerToClass; + +static void IRAM_ATTR chipselectpin(void) { + pointerToClass->packetHandler(); +} +static void IRAM_ATTR clockpin(void) { + pointerToClass->clkHandler(); +} + +void CIO::begin(int cio_data_pin, int cio_clk_pin, int cio_cs_pin) { + pointerToClass = this; + _DATA_PIN = cio_data_pin; + _CLK_PIN = cio_clk_pin; + _CS_PIN = cio_cs_pin; + pinMode(_CS_PIN, INPUT); + pinMode(_DATA_PIN, INPUT); + pinMode(_CLK_PIN, INPUT); + attachInterrupt(digitalPinToInterrupt(_CS_PIN), chipselectpin, CHANGE); + attachInterrupt(digitalPinToInterrupt(_CLK_PIN), clockpin, CHANGE); //Write on falling edge and read on rising edge +} + +void CIO::stop(){ + detachInterrupt(digitalPinToInterrupt(_CS_PIN)); + detachInterrupt(digitalPinToInterrupt(_CLK_PIN)); +} + + +void CIO::loop(void) { + //newdata is true when a data packet has arrived from cio + if(newData) { + newData = false; + static int capturePhase = 0; + static uint32_t buttonReleaseTime; + + //capture TARGET after UP/DOWN has been pressed... + if ((button == ButtonCodes[UP]) || (button == ButtonCodes[DOWN])) + { + buttonReleaseTime = millis(); + capturePhase = 1; + } + //require two consecutive messages to be equal before registering + static uint8_t prev_checksum = 0; + uint8_t checksum = 0; + for(int i = 0; i < 11; i++){ + checksum += _payload[i]; + } + if(checksum != prev_checksum) { + prev_checksum = checksum; + return; + } + + //copy private array to public array + for(unsigned int i = 0; i < sizeof(payload); i++){ + payload[i] = _payload[i]; + } + + //determine if anything changed, so we can update webclients + for(unsigned int i = 0; i < sizeof(payload); i++){ + if (payload[i] != _prevPayload[i]) dataAvailable = true; + _prevPayload[i] = payload[i]; + } + + brightness = _brightness & 7; //extract only the brightness bits (0-7) + //extract information from payload to a better format + states[LOCKEDSTATE] = (payload[LCK_IDX] & (1 << LCK_BIT)) > 0; + states[POWERSTATE] = (payload[PWR_IDX] & (1 << PWR_BIT)) > 0; + states[UNITSTATE] = (payload[C_IDX] & (1 << C_BIT)) > 0; + states[BUBBLESSTATE] = (payload[AIR_IDX] & (1 << AIR_BIT)) > 0; + states[HEATGRNSTATE] = (payload[GRNHTR_IDX] & (1 << GRNHTR_BIT)) > 0; + states[HEATREDSTATE] = (payload[REDHTR_IDX] & (1 << REDHTR_BIT)) > 0; + states[HEATSTATE] = states[HEATGRNSTATE] || states[HEATREDSTATE]; + states[PUMPSTATE] = (payload[FLT_IDX] & (1 << FLT_BIT)) > 0; + states[CHAR1] = (uint8_t)_getChar(payload[DGT1_IDX]); + states[CHAR2] = (uint8_t)_getChar(payload[DGT2_IDX]); + states[CHAR3] = (uint8_t)_getChar(payload[DGT3_IDX]); + if(HASJETS) states[JETSSTATE] = (payload[HJT_IDX] & (1 << HJT_BIT)) > 0; + else states[JETSSTATE] = 0; + //Determine if display is showing target temp or actual temp or anything else. + //...until 4 seconds after UP/DOWN released + if((millis()-buttonReleaseTime) > 2000) capturePhase = 0; + //convert text on display to a value if the chars are recognized + if(states[CHAR1] == '*' || states[CHAR2] == '*' || states[CHAR3] == '*') return; + String tempstring = String((char)states[CHAR1])+String((char)states[CHAR2])+String((char)states[CHAR3]); + uint8_t tmpTemp = tempstring.toInt(); + //capture only if showing plausible values (not blank screen while blinking) + if( (capturePhase == 1) && (tmpTemp > 19) ) { + states[TARGET] = tmpTemp; + } + //wait 6 seconds after UP/DOWN is released to be sure that actual temp is shown + if( (capturePhase == 0) && (states[CHAR3]!='H') && (states[CHAR3]!=' ') && ((millis()-buttonReleaseTime) > 6000) ) + { + if(states[TEMPERATURE] != tmpTemp) dataAvailable = true; + states[TEMPERATURE] = tmpTemp; + } + + if(states[UNITSTATE] != _prevUNT || states[HEATSTATE] != _prevHTR || states[PUMPSTATE] != _prevFLT) { + stateChanged = true; + _prevUNT = states[UNITSTATE]; + _prevHTR = states[HEATSTATE]; + _prevFLT = states[PUMPSTATE]; + } + } +} + +//end of packet +void IRAM_ATTR CIO::eopHandler(void) { + //process latest data and enter corresponding mode (like listen for DSP_STS or send BTN_OUT) + //pinMode(_DATA_PIN, INPUT); + WRITE_PERI_REG( PIN_DIR_INPUT, 1 << _DATA_PIN); + _byteCount = 0; + _bitCount = 0; + uint8_t msg = _receivedByte; + + switch (msg) { + case DSP_CMD1_MODE6_11_7: + _CIO_cmd_matches = 1; + break; + case DSP_CMD2_DATAWRITE: + if (_CIO_cmd_matches == 1) { + _CIO_cmd_matches = 2; + } else { + _CIO_cmd_matches = 0; //reset - DSP_CMD1_MODE6_11_7 must be followed by DSP_CMD2_DATAWRITE to activate command + } + break; + default: + if (_CIO_cmd_matches == 3) { + _brightness = msg; + _CIO_cmd_matches = 0; + newData = true; + } + if (_CIO_cmd_matches == 2) { + _CIO_cmd_matches = 3; + } + break; + } +} + +//CIO comm +//packet start +//arduino core 3.0.1+ should work with digitalWrite() now. +void IRAM_ATTR CIO::packetHandler(void) { + if (!(READ_PERI_REG(PIN_IN) & (1 << _CS_PIN))) { + //packet start + _packet = true; + } + else { + //end of packet + _packet = false; + _dataIsOutput = false; + eopHandler(); + } +} + +//CIO comm +//Read incoming bits, and take action after a complete byte +void IRAM_ATTR CIO::clkHandler(void) { + //sanity check on clock signal + static uint32_t prev_us = 0; + uint32_t us = micros(); + uint32_t period = 0; + if(us > prev_us) period = us - prev_us; //will be negative on rollover (once every hour-ish) + prev_us = us; + if(period < clk_per) clk_per = period; + + if (!_packet) return; + //CS line is active, so send/receive bits on DATA line + + bool clockstate = READ_PERI_REG(PIN_IN) & (1 << _CLK_PIN); + + //shift out bits on low clock (falling edge) + if (!clockstate & _dataIsOutput) { + //send BTN_OUT + if (button & (1 << _sendBit)) { + //digitalWrite(_DATA_PIN, HIGH); + WRITE_PERI_REG( PIN_OUT_SET, 1 << _DATA_PIN); + } + else { + //digitalWrite(_DATA_PIN, LOW); + WRITE_PERI_REG( PIN_OUT_CLEAR, 1 << _DATA_PIN); + } + _sendBit++; + if(_sendBit > 15) _sendBit = 0; + } + + //read bits on high clock (rising edge) + if (clockstate & !_dataIsOutput) { + //read data pin to a byte + //_receivedByte = (_receivedByte << 1) | digitalRead(_DATA_PIN); + //_receivedByte = (_receivedByte << 1) | ( ( (READ_PERI_REG(PIN_IN) & (1 << _DATA_PIN)) ) > 0); + //_receivedByte = (_receivedByte >> 1) | digitalRead(_DATA_PIN) << 7; + _receivedByte = (_receivedByte >> 1) | ( ( (READ_PERI_REG(PIN_IN) & (1 << _DATA_PIN)) ) > 0) << 7; + _bitCount++; + if (_bitCount == 8) { + _bitCount = 0; + if (_CIO_cmd_matches == 2) { //meaning we have received the header for 11 data bytes to come + _payload[_byteCount] = _receivedByte; + _byteCount++; + } + else if (_receivedByte == DSP_CMD2_DATAREAD) { + _sendBit = 8; + _dataIsOutput = true; + //pinMode(_DATA_PIN, OUTPUT); + WRITE_PERI_REG( PIN_DIR_OUTPUT, 1 << _DATA_PIN); + } + } + } +} + +uint16_t DSP::getButton(void) { + if(millis() - _dspLastGetButton > 50){ + uint16_t newButton = 0; + _dspLastGetButton = millis(); + //send request + //pinMode(_DATA_PIN, OUTPUT); + digitalWrite(_CS_PIN, LOW); //start of packet + delayMicroseconds(50); + _sendBitsToDSP(DSP_CMD2_DATAREAD, 8); //request button presses + newButton = _receiveBitsFromDSP(); + digitalWrite(_CS_PIN, HIGH); //end of packet + delayMicroseconds(30); + _oldButton = newButton; + return (newButton); + } else return (_oldButton); +} + + +//bitsToSend can only be 8 with this solution of LSB first +void DSP::_sendBitsToDSP(uint32_t outBits, int bitsToSend) { + pinMode(_DATA_PIN, OUTPUT); + delayMicroseconds(20); + for (int i = 0; i < bitsToSend; i++) { + digitalWrite(_CLK_PIN, LOW); + digitalWrite(_DATA_PIN, outBits & (1 << i)); + delayMicroseconds(20); + digitalWrite(_CLK_PIN, HIGH); + delayMicroseconds(20); + } +} + +uint16_t DSP::_receiveBitsFromDSP() { + //bitbanging the answer from Display + uint16_t result = 0; + pinMode(_DATA_PIN, INPUT); + + for (int i = 0; i < 16; i++) { + digitalWrite(_CLK_PIN, LOW); //clock leading edge + delayMicroseconds(20); + digitalWrite(_CLK_PIN, HIGH); //clock trailing edge + delayMicroseconds(20); + int j = (i+8)%16; //bit 8-16 then 0-7 + result |= digitalRead(_DATA_PIN) << j; + } + return result; +} + + +void DSP::updateDSP(uint8_t brightness) { + //refresh display with ~10Hz + if(millis() -_dspLastRefreshTime > 99) + { + _dspLastRefreshTime = millis(); + uint8_t enableLED = 0; + if(brightness > 0) + { + enableLED = DSP_DIM_ON; + brightness -= 1; + } + delayMicroseconds(30); + digitalWrite(_CS_PIN, LOW); //start of packet + _sendBitsToDSP(DSP_CMD1_MODE6_11_7, 8); + digitalWrite(_CS_PIN, HIGH); //end of packet + + delayMicroseconds(50); + digitalWrite(_CS_PIN, LOW);//start of packet + _sendBitsToDSP(DSP_CMD2_DATAWRITE, 8); + digitalWrite(_CS_PIN, HIGH);//end of packet + + //payload + delayMicroseconds(50); + digitalWrite(_CS_PIN, LOW);//start of packet + for (int i = 0; i < 11; i++) + _sendBitsToDSP(payload[i], 8); + digitalWrite(_CS_PIN, HIGH);//end of packet + + delayMicroseconds(50); + digitalWrite(_CS_PIN, LOW);//start of packet + _sendBitsToDSP(DSP_DIM_BASE|enableLED|brightness, 8); + digitalWrite(_CS_PIN, HIGH);//end of packet + delayMicroseconds(50); + } +} + +void DSP::textOut(String txt) { + int len = txt.length(); + //Set CMD3 (address 00H) + payload[0] = 0xC0; + if (len >= 3) { + for (int i = 0; i < len - 2; i++) { + payload[DGT1_IDX] = _getCode(txt.charAt(i)); + payload[DGT2_IDX] = _getCode(txt.charAt(i + 1)); + payload[DGT3_IDX] = _getCode(txt.charAt(i + 2)); + updateDSP(7); + delay(230); + } + } + else if (len == 2) { + payload[DGT1_IDX] = _getCode(' '); + payload[DGT2_IDX] = _getCode(txt.charAt(0)); + payload[DGT3_IDX] = _getCode(txt.charAt(1)); + updateDSP(7); + } + else if (len == 1) { + payload[DGT1_IDX] = _getCode(' '); + payload[DGT2_IDX] = _getCode(' '); + payload[DGT3_IDX] = _getCode(txt.charAt(0)); + updateDSP(7); + } +} + +void DSP::LEDshow() { + for(int y = 7; y < 11; y++){ + for(int x = 1; x < 9; x++){ + payload[y] = (1 << x) + 1; + updateDSP(7); + delay(200); + } + } +} + +void DSP::begin(int dsp_data_pin, int dsp_clk_pin, int dsp_cs_pin, int dsp_audio_pin) { + _DATA_PIN = dsp_data_pin; + _CLK_PIN = dsp_clk_pin; + _CS_PIN = dsp_cs_pin; + _AUDIO_PIN = dsp_audio_pin; + + pinMode(_CS_PIN, OUTPUT); + pinMode(_DATA_PIN, INPUT); + pinMode(_CLK_PIN, OUTPUT); + pinMode(_AUDIO_PIN, OUTPUT); + digitalWrite(_CS_PIN, HIGH); //Active LOW + digitalWrite(_CLK_PIN, HIGH); //shift on falling, latch on rising + digitalWrite(_AUDIO_PIN, LOW); +} + +void DSP::playIntro() { + int longnote = 125; + int shortnote = 63; + + tone(_AUDIO_PIN, NOTE_C7, longnote); + delay(2 * longnote); + tone(_AUDIO_PIN, NOTE_G6, shortnote); + delay(2 * shortnote); + tone(_AUDIO_PIN, NOTE_G6, shortnote); + delay(2 * shortnote); + tone(_AUDIO_PIN, NOTE_A6, longnote); + delay(2 * longnote); + tone(_AUDIO_PIN, NOTE_G6, longnote); + delay(2 * longnote); + //paus + delay(2 * longnote); + tone(_AUDIO_PIN, NOTE_B6, longnote); + delay(2 * longnote); + tone(_AUDIO_PIN, NOTE_C7, longnote); + delay(2 * longnote); + noTone(_AUDIO_PIN); +} + +//silent beep instead of annoying beeps every time something changes +void DSP::beep() { + //int longnote = 125; + // int shortnote = 63; + // tone(_AUDIO_PIN, NOTE_C6, shortnote); + // delay(shortnote); + // tone(_AUDIO_PIN, NOTE_C7, shortnote); + // delay(shortnote); + // noTone(_AUDIO_PIN); +} + +//new beep for button presses only +void DSP::beep2() { + //int longnote = 125; + int shortnote = 40; + tone(_AUDIO_PIN, NOTE_D6, shortnote); + delay(shortnote); + tone(_AUDIO_PIN, NOTE_D7, shortnote); + delay(shortnote); + tone(_AUDIO_PIN, NOTE_D8, shortnote); + delay(shortnote); + noTone(_AUDIO_PIN); +} + + diff --git a/Code/6-wire-version/lib/BWC/BWC_6w_type1.h b/Code/6-wire-version/lib/BWC/BWC_6w_type1.h new file mode 100644 index 00000000..9db34c57 --- /dev/null +++ b/Code/6-wire-version/lib/BWC/BWC_6w_type1.h @@ -0,0 +1,165 @@ +#ifndef BWC_TYPE1_H +#define BWC_TYPE1_H + +#include "Arduino.h" +#include "BWC_const.h" + +#ifdef PCB_V2 +const int ciopins[] = {D1, D2, D3}; +const int dsppins[] = {D4, D5, D6, D7}; +#else +const int ciopins[] = {D7, D2, D1}; +const int dsppins[] = {D5, D4, D3, D6}; +#endif + +//LSB +const uint8_t DSP_CMD2_DATAREAD = 0x42; +const uint8_t DSP_CMD1_MODE6_11_7 = 0x01; //real CIO is sending 0x01 which is illegal according to datasheet +const uint8_t DSP_CMD2_DATAWRITE = 0x40; + +//Payload byte index and bit numbers (see documentation in excel file on github) +//LSB first +const byte DGT1_IDX = 1; +const byte DGT2_IDX = 3; +const byte DGT3_IDX = 5; +const byte TMR2_IDX = 7; +const byte TMR2_BIT = 1; +const byte TMR1_IDX = 7; +const byte TMR1_BIT = 2; +const byte LCK_IDX = 7; +const byte LCK_BIT = 3; +const byte TMRBTNLED_IDX = 7; +const byte TMRBTNLED_BIT = 4; +const byte REDHTR_IDX = 7; +const byte REDHTR_BIT = 5; +const byte GRNHTR_IDX = 7; +const byte GRNHTR_BIT = 6; +const byte AIR_IDX = 7; +const byte AIR_BIT = 7; +const byte FLT_IDX = 9; +const byte FLT_BIT = 1; +const byte C_IDX = 9; +const byte C_BIT = 2; +const byte F_IDX = 9; +const byte F_BIT = 3; +const byte PWR_IDX = 9; +const byte PWR_BIT = 4; +const byte HJT_IDX = 9; +const byte HJT_BIT = 5; + +//7-segment codes. MSB always 1 +const uint8_t CHARCODES[] = { + 0x7F, 0x0D, 0xB7, 0x9F, 0xCD, 0xDB, 0xFB, 0x0F, 0xFF, 0xDF, 0x01, 0x81, 0xEF, 0xF9, 0x73, 0xBD, 0xF3, 0xE3, + 0xFB, 0xE9, 0xED, 0x61, 0x1D, 0xE1, 0x71, 0x01, 0xA9, 0xB9, 0xE7, 0xCF, 0xA1, 0xDB, 0xF1, 0x39, 0x7D, 0x01, 0xDD, 0xB7 +}; + +#if defined(PRE2021) +const uint16_t ButtonCodes[] = +{ + 0x1B1B, 0x0200, 0x0100, 0x0300, 0x1012, 0x1212, 0x1112, 0x1312, 0x0809, 0x0000 +}; +const bool HASJETS = false; +const String MYMODEL = "PRE2021"; + +#elif defined(MIAMI2021) +const uint16_t ButtonCodes[] = +{ + 0x1B1B, 0x0100, 0x0300, 0x1212, 0x0809, 0x1012, 0x1112, 0x1312, 0x0200, 0x0000 +}; +const bool HASJETS = false; +const String MYMODEL = "MIAMI2021"; + +#elif defined(MALDIVES2021) +const uint16_t ButtonCodes[] = +{ + 0x1B1B, 0x0100, 0x0300, 0x1212, 0x0a09, 0x1012, 0x1312, 0x0809, 0x0200, 0x0000, 0x1112 +}; +const bool HASJETS = true; +const String MYMODEL = "MALDIVES2021"; + +#else +//Make compiler happy. Will not be used. +const uint16_t ButtonCodes[] = +{ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; +const bool HASJETS = false; +const String MYMODEL = "ISCREWEDUP"; + +#endif + + +class CIO { + + public: + void begin(int cio_data_pin, int cio_clk_pin, int cio_cs_pin); + void loop(void); + void eopHandler(void); + void packetHandler(void); + void clkHandler(void); + void stop(void); + + volatile bool newData = false; + bool dataAvailable = false; + bool stateChanged = false; //save states when true + volatile uint16_t button = NOBTN; + uint8_t payload[11]; + uint8_t states[14]; + uint8_t brightness; + bool targetIsDisplayed = false; + //uint16_t lastPressedButton; + volatile uint32_t clk_per; //lowest time between clock edges. Should be ~20 us. + + + private: + volatile int _byteCount = 0; + volatile int _bitCount = 0; + volatile bool _dataIsOutput = false; + volatile byte _receivedByte; + volatile int _CIO_cmd_matches = 0; + volatile bool _packet = false; + volatile int _sendBit = 8; + volatile uint8_t _brightness; + volatile uint8_t _payload[11]; + int _CS_PIN; + int _CLK_PIN; + int _DATA_PIN; + uint8_t _prevPayload[11]; + bool _prevUNT; + bool _prevHTR; + bool _prevFLT; + + char _getChar(uint8_t value); +}; + +class DSP { + + public: + uint8_t payload[11] = {0xC0, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x0}; + + void begin(int dsp_data_pin, int dsp_clk_pin, int dsp_cs_pin, int dsp_audio_pin); + uint16_t getButton(void); + void updateDSP(uint8_t brightness); + void textOut(String txt); + void LEDshow(); + void playIntro(); + void beep(); + void beep2(); + + private: + void _sendBitsToDSP(uint32_t outBits, int bitsToSend); + uint16_t _receiveBitsFromDSP(); + char _getCode(char value); + + unsigned long _dspLastRefreshTime = 0; + unsigned long _dspLastGetButton = 0; + uint16_t _oldButton = ButtonCodes[NOBTN]; + //Pins + int _CS_PIN; + int _CLK_PIN; + int _DATA_PIN; + int _AUDIO_PIN; +}; + + +#endif diff --git a/Code/6-wire-version/lib/BWC/BWC_6w_type2.cpp b/Code/6-wire-version/lib/BWC/BWC_6w_type2.cpp new file mode 100644 index 00000000..ec0ceebc --- /dev/null +++ b/Code/6-wire-version/lib/BWC/BWC_6w_type2.cpp @@ -0,0 +1,361 @@ +#include "BWC_6w_type2.h" +#include "pitches.h" + +CIO *pointerToClass; + +static void IRAM_ATTR LEDdatapin(void) { + pointerToClass->packetHandler(); +} +static void IRAM_ATTR clockpin(void) { + pointerToClass->clkHandler(); +} + +void CIO::begin(int cio_td_pin, int cio_clk_pin, int cio_ld_pin) { + pointerToClass = this; + _CIO_TD_PIN = cio_td_pin; + _CIO_CLK_PIN = cio_clk_pin; + _CIO_LD_PIN = cio_ld_pin; + pinMode(_CIO_LD_PIN, INPUT); + pinMode(_CIO_TD_PIN, OUTPUT); + pinMode(_CIO_CLK_PIN, INPUT); + digitalWrite(_CIO_TD_PIN, 1); //idle high + attachInterrupt(digitalPinToInterrupt(_CIO_LD_PIN), LEDdatapin, CHANGE); + attachInterrupt(digitalPinToInterrupt(_CIO_CLK_PIN), clockpin, CHANGE); //Write on falling edge and read on rising edge +} + +void CIO::stop(){ + detachInterrupt(digitalPinToInterrupt(_CIO_LD_PIN)); + detachInterrupt(digitalPinToInterrupt(_CIO_CLK_PIN)); +} + + +void CIO::loop(void) { + //newdata is true when a data packet has arrived from cio + if(newData) { + newData = false; + static int capturePhase = 0; + static uint32_t buttonReleaseTime; + + //capture TARGET after UP/DOWN has been pressed... + if ((button == ButtonCodes[UP]) || (button == ButtonCodes[DOWN])) + { + buttonReleaseTime = millis(); + capturePhase = 1; + } + + /* + * This model is only sending messages when something updated + * so this section is not useful + */ + /* + //require two consecutive messages to be equal before registering + static uint8_t prev_checksum = 0; + uint8_t checksum = 0; + for(unsigned int i = 0; i < sizeof(payload); i++){ + checksum += _payload[i]; + } + if(checksum != prev_checksum) { + prev_checksum = checksum; + return; + } + */ + + //copy private array to public array + for(unsigned int i = 0; i < sizeof(payload); i++){ + payload[i] = _payload[i]; + } + + //determine if anything changed, so we can update webclients + for(unsigned int i = 0; i < sizeof(payload); i++){ + if (payload[i] != _prevPayload[i]) dataAvailable = true; + _prevPayload[i] = payload[i]; + } + + //brightness = _brightness & 7; //extract only the brightness bits (0-7) + //extract information from payload to a better format + states[LOCKEDSTATE] = (payload[LCK_IDX] & (1 << LCK_BIT)) > 0; + states[POWERSTATE] = 1; //(payload[PWR_IDX] & (1 << PWR_BIT)) > 0; + states[UNITSTATE] = (payload[C_IDX] & (1 << C_BIT)) > 0; + states[BUBBLESSTATE] = (payload[AIR_IDX] & (1 << AIR_BIT)) > 0; + states[HEATGRNSTATE] = (payload[GRNHTR_IDX] & (1 << GRNHTR_BIT)) > 0; + states[HEATREDSTATE] = (payload[REDHTR_IDX] & (1 << REDHTR_BIT)) > 0; + states[HEATSTATE] = states[HEATGRNSTATE] || states[HEATREDSTATE]; + states[PUMPSTATE] = (payload[FLT_IDX] & (1 << FLT_BIT)) > 0; + states[CHAR1] = (uint8_t)_getChar(payload[DGT1_IDX]); + states[CHAR2] = (uint8_t)_getChar(payload[DGT2_IDX]); + states[CHAR3] = (uint8_t)_getChar(payload[DGT3_IDX]); + if(HASJETS) states[JETSSTATE] = (payload[HJT_IDX] & (1 << HJT_BIT)) > 0; + else states[JETSSTATE] = 0; + //Determine if display is showing target temp or actual temp or anything else. + //...until 4 seconds after UP/DOWN released + if((millis()-buttonReleaseTime) > 2000) capturePhase = 0; + //convert text on display to a value if the chars are recognized + if(states[CHAR1] == '*' || states[CHAR2] == '*' || states[CHAR3] == '*') return; + String tempstring = String((char)states[CHAR1])+String((char)states[CHAR2])+String((char)states[CHAR3]); + uint8_t tmpTemp = tempstring.toInt(); + //capture only if showing plausible values (not blank screen while blinking) + if( (capturePhase == 1) && (tmpTemp > 19) ) { + states[TARGET] = tmpTemp; + } + //wait 6 seconds after UP/DOWN is released to be sure that actual temp is shown + if( (capturePhase == 0) && (states[CHAR3]!='H') && (states[CHAR3]!=' ') && ((millis()-buttonReleaseTime) > 6000) ) + { + if(states[TEMPERATURE] != tmpTemp) dataAvailable = true; + states[TEMPERATURE] = tmpTemp; + } + + if(states[UNITSTATE] != _prevUNT || states[HEATSTATE] != _prevHTR || states[PUMPSTATE] != _prevFLT) { + stateChanged = true; + _prevUNT = states[UNITSTATE]; + _prevHTR = states[HEATSTATE]; + _prevFLT = states[PUMPSTATE]; + } + } +} + +//CIO comm +//packet start/stop +void IRAM_ATTR CIO::packetHandler(void) { + //Check START/END condition: _LD_PIN change when _CLK_PIN is high. + if (READ_PERI_REG(PIN_IN) & (1 << _CIO_CLK_PIN)) { + _byteCount = 0; + _bitCount = 0; + _received_cmd = 0; + newData = READ_PERI_REG(PIN_IN) & (1 << _CIO_LD_PIN); + } +} + +void IRAM_ATTR CIO::clkHandler(void) { + //read data on _cio_ld_pin and write to _cio_td_pin (LSBF) + + uint16_t td_bitnumber = _bitCount % 10; + uint16_t ld_bitnumber = _bitCount % 8; + uint16_t buttonwrapper = (B11111110 << 8) | (button<<1); //startbit @ bit0, stopbit @ bit9 + + //rising or falling edge? + bool risingedge = READ_PERI_REG(PIN_IN) & (1 << _CIO_CLK_PIN); + if(risingedge){ + //clk rising edge + _byteCount = _bitCount / 8; + if(_byteCount == 0){ + _received_cmd |= ((READ_PERI_REG(PIN_IN) & (1 << _CIO_LD_PIN))>0) << ld_bitnumber; + } + else if( (_byteCount<6) && (_received_cmd == CMD2) ){ //only write to payload after CMD2. Also protect from buffer overflow + //overwrite the old payload bit with new bit + _payload[_byteCount-1] = (_payload[_byteCount-1] & ~(1 << ld_bitnumber)) | ((READ_PERI_REG(PIN_IN) & (1 << _CIO_LD_PIN))>0) << ld_bitnumber; + } + //store brightness in _cio local variable. It is not used, but put here in case we want to obey the pump. + if(_bitCount == 7 && (_received_cmd & B11000000) == B10000000) _brightness = _received_cmd; + _bitCount++; + } else { + //clk falling edge + //first and last bit is a dummy start/stop bit (0/1), then 8 data bits in btwn + if (buttonwrapper & (1 << td_bitnumber)) { + WRITE_PERI_REG( PIN_OUT_SET, 1 << _CIO_TD_PIN); + } else { + WRITE_PERI_REG( PIN_OUT_CLEAR, 1 << _CIO_TD_PIN); + } + } +} + +uint16_t DSP::getButton(void) { + if(millis() - _dspLastGetButton > 20){ + uint16_t newButton = 0; + _dspLastGetButton = millis(); + //startbit + digitalWrite(_DSP_CLK_PIN, LOW); + delayMicroseconds(CLKPW); + digitalWrite(_DSP_CLK_PIN, HIGH); + delayMicroseconds(CLKPW); + //clock in 8 data bits + for(int i = 0; i < 8; i++){ + digitalWrite(_DSP_CLK_PIN, LOW); + delayMicroseconds(CLKPW); + digitalWrite(_DSP_CLK_PIN, HIGH); + newButton |= digitalRead(_DSP_TD_PIN)< 99) + { + _dspLastRefreshTime = millis(); + uint8_t enableLED = 0; + if(brightness > 0) + { + enableLED = DSP_DIM_ON; + brightness -= 1; + } + digitalWrite(_DSP_LD_PIN, LOW); //start of packet + delayMicroseconds(CLKPW); + _sendBitsToDSP(CMD1, 8); + //end of packet: clock low, make sure LD is low before rising clock then LD + digitalWrite(_DSP_CLK_PIN, LOW); + digitalWrite(_DSP_LD_PIN, LOW); + delayMicroseconds(CLKPW); + digitalWrite(_DSP_CLK_PIN, HIGH); + delayMicroseconds(CLKPW); + digitalWrite(_DSP_LD_PIN, HIGH); + delayMicroseconds(CLKPW); + + digitalWrite(_DSP_LD_PIN, LOW); //start of packet + delayMicroseconds(CLKPW); + _sendBitsToDSP(CMD2, 8); + for(unsigned int i=0; i= 3) { + for (int i = 0; i < len - 2; i++) { + payload[DGT1_IDX] = _getCode(txt.charAt(i)); + payload[DGT2_IDX] = _getCode(txt.charAt(i + 1)); + payload[DGT3_IDX] = _getCode(txt.charAt(i + 2)); + updateDSP(7); + delay(230); + } + } + else if (len == 2) { + payload[DGT1_IDX] = _getCode(' '); + payload[DGT2_IDX] = _getCode(txt.charAt(0)); + payload[DGT3_IDX] = _getCode(txt.charAt(1)); + updateDSP(7); + } + else if (len == 1) { + payload[DGT1_IDX] = _getCode(' '); + payload[DGT2_IDX] = _getCode(' '); + payload[DGT3_IDX] = _getCode(txt.charAt(0)); + updateDSP(7); + } +} + +void DSP::LEDshow() { + //todo: clear payload first... + for(unsigned int y = 0; y < sizeof(payload); y++){ + for(int x = 0; x < 9; x++){ + payload[y] = (1 << x); + updateDSP(7); + delay(200); + } + } +} + +void DSP::begin(int dsp_td_pin, int dsp_clk_pin, int dsp_ld_pin, int dsp_audio_pin) { + _DSP_TD_PIN = dsp_td_pin; + _DSP_CLK_PIN = dsp_clk_pin; + _DSP_LD_PIN = dsp_ld_pin; + _DSP_AUDIO_PIN = dsp_audio_pin; + + pinMode(_DSP_LD_PIN, OUTPUT); + pinMode(_DSP_TD_PIN, INPUT); + pinMode(_DSP_CLK_PIN, OUTPUT); + pinMode(_DSP_AUDIO_PIN, OUTPUT); + digitalWrite(_DSP_LD_PIN, HIGH); //idle high + digitalWrite(_DSP_CLK_PIN, HIGH); //shift on falling, latch on rising + digitalWrite(_DSP_AUDIO_PIN, LOW); +} + +void DSP::playIntro() { + int longnote = 125; + int shortnote = 63; + + tone(_DSP_AUDIO_PIN, NOTE_C7, longnote); + delay(2 * longnote); + tone(_DSP_AUDIO_PIN, NOTE_G6, shortnote); + delay(2 * shortnote); + tone(_DSP_AUDIO_PIN, NOTE_G6, shortnote); + delay(2 * shortnote); + tone(_DSP_AUDIO_PIN, NOTE_A6, longnote); + delay(2 * longnote); + tone(_DSP_AUDIO_PIN, NOTE_G6, longnote); + delay(2 * longnote); + //paus + delay(2 * longnote); + tone(_DSP_AUDIO_PIN, NOTE_B6, longnote); + delay(2 * longnote); + tone(_DSP_AUDIO_PIN, NOTE_C7, longnote); + delay(2 * longnote); + noTone(_DSP_AUDIO_PIN); +} + +//silent beep instead of annoying beeps every time something changes +void DSP::beep() { + //int longnote = 125; + // int shortnote = 63; + // tone(_AUDIO_PIN, NOTE_C6, shortnote); + // delay(shortnote); + // tone(_AUDIO_PIN, NOTE_C7, shortnote); + // delay(shortnote); + // noTone(_AUDIO_PIN); +} + +//new beep for button presses only +void DSP::beep2() { + //int longnote = 125; + int shortnote = 40; + tone(_DSP_AUDIO_PIN, NOTE_D6, shortnote); + delay(shortnote); + tone(_DSP_AUDIO_PIN, NOTE_D7, shortnote); + delay(shortnote); + tone(_DSP_AUDIO_PIN, NOTE_D8, shortnote); + delay(shortnote); + noTone(_DSP_AUDIO_PIN); +} + + diff --git a/Code/6-wire-version/lib/BWC/BWC_6w_type2.h b/Code/6-wire-version/lib/BWC/BWC_6w_type2.h new file mode 100644 index 00000000..731db721 --- /dev/null +++ b/Code/6-wire-version/lib/BWC/BWC_6w_type2.h @@ -0,0 +1,138 @@ +#ifndef BWC_TYPE2_H +#define BWC_TYPE2_H + +#include "Arduino.h" +#include "BWC_const.h" + +#ifdef PCB_V2 +const int ciopins[] = {D1, D2, D3}; +const int dsppins[] = {D4, D5, D6, D7}; +#else +const int ciopins[] = {D7, D2, D1}; +const int dsppins[] = {D5, D4, D3, D6}; +#endif + +const uint16_t ButtonCodes[] = +{ + 0, 1<<7, 1<<6, 1<<5, 1<<4, 1<<3, 1<<2, 1<<1, 1<<0, 1<<8, 1<<9 +}; +const bool HASJETS = false; +const String MYMODEL = "MODEL54149E"; + +//LSB +const uint8_t CMD1 = B01000000; //normal mode, auto+1 address +const uint8_t CMD2 = B11000000; //start address 00H +const uint8_t CMD3 = DSP_DIM_BASE | DSP_DIM_ON | 7; //full brightness +const uint16_t CLKPW = 50; //clock pulse period in us. clockfreq = 1/2*CLKPW + + +//Payload byte index and bit numbers (see documentation in excel file on github) +//LSB first +const byte DGT1_IDX = 0; +const byte DGT2_IDX = 1; +const byte DGT3_IDX = 2; +const byte TMR2_IDX = 3; +const byte TMR2_BIT = 7; +const byte TMR1_IDX = 3; +const byte TMR1_BIT = 6; +const byte LCK_IDX = 3; +const byte LCK_BIT = 5; +const byte TMRBTNLED_IDX = 3; +const byte TMRBTNLED_BIT = 4; +const byte REDHTR_IDX = 3; +const byte REDHTR_BIT = 2; +const byte GRNHTR_IDX = 3; +const byte GRNHTR_BIT = 3; +const byte AIR_IDX = 3; +const byte AIR_BIT = 1; +const byte FLT_IDX = 4; +const byte FLT_BIT = 2; +const byte C_IDX = 4; +const byte C_BIT = 0; +const byte F_IDX = 4; +const byte F_BIT = 1; +const byte PWR_IDX = 4; //not used. Always considered ON +const byte PWR_BIT = 3; +const byte HJT_IDX = 4; //wild guess if it exists on any model +const byte HJT_BIT = 4; //wild guess + +//8-segment codes. MSB-> .gfedcba <-LSB +const uint8_t CHARCODES[] = { + 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x00, 0x40, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, + 0x7D, 0x74, 0x76, 0x30, 0x0E, 0x70, 0x38, 0x00, 0x54, 0x5C, 0x73, 0x67, 0x50, 0x6D, 0x78, 0x1C, 0x3E, 0x00, 0x6E, 0x5B +}; + +class CIO { + + public: + void begin(int cio_td_pin, int cio_clk_pin, int cio_ld_pin); + void loop(void); + void eopHandler(void); + void packetHandler(void); + void clkHandler(void); + void stop(void); + + volatile bool newData = false; + bool dataAvailable = false; + bool stateChanged = false; //save states when true + volatile uint16_t button = NOBTN; + uint8_t payload[5]; + uint8_t states[14]; + uint8_t brightness; + bool targetIsDisplayed = false; + //uint16_t lastPressedButton; + volatile uint32_t clk_per; //lowest time between clock edges. Should be ~20 us. + + + private: + volatile int _byteCount = 0; + volatile int _bitCount = 0; + volatile byte _receivedByte; + volatile bool _packet = false; + volatile int _sendBit = 8; + volatile uint8_t _brightness; + volatile uint8_t _payload[5]; + int _CIO_TD_PIN; + int _CIO_CLK_PIN; + int _CIO_LD_PIN; + uint8_t _prevPayload[5]; + bool _prevUNT; + bool _prevHTR; + bool _prevFLT; + uint8_t _received_cmd = 0; //temporary storage of command message + + char _getChar(uint8_t value); +}; + +class DSP { + + public: + uint8_t payload[5]; + + void begin(int dsp_td_pin, int dsp_clk_pin, int dsp_ld_pin, int dsp_audio_pin); + uint16_t getButton(void); + void updateDSP(uint8_t brightness); + void textOut(String txt); + void LEDshow(); + void playIntro(); + void beep(); + void beep2(); + + private: + void _sendBitsToDSP(uint32_t outBits, int bitsToSend); + uint16_t _receiveBitsFromDSP(); + char _getCode(char value); + + unsigned long _dspLastRefreshTime = 0; + unsigned long _dspLastGetButton = 0; + uint16_t _oldButton = ButtonCodes[NOBTN]; + uint16_t _prevButton = ButtonCodes[NOBTN]; + //Pins + int _DSP_TD_PIN; + int _DSP_CLK_PIN; + int _DSP_LD_PIN; + int _DSP_AUDIO_PIN; +}; + + +#endif diff --git a/Code/6-wire-version/lib/BWC/BWC_8266.h b/Code/6-wire-version/lib/BWC/BWC_8266.h deleted file mode 100644 index 65b11768..00000000 --- a/Code/6-wire-version/lib/BWC/BWC_8266.h +++ /dev/null @@ -1,185 +0,0 @@ -#ifndef BWC_8266_H -#define BWC_8266_H - -#ifndef ESP8266 -#error "This library supports 8266 only" -#endif - -#include "Arduino.h" - -#ifndef BWC_8266_globals_h -#include "BWC_8266_globals.h" -#endif - -//long long needed in arduino core v3+ -#define ARDUINOJSON_USE_LONG_LONG 1 -#include -#include "ESPDateTime.h" -#include -#include -#include "pitches.h" - - - -class CIO { - - public: - void begin(int cio_cs_pin, int cio_data_pin, int cio_clk_pin); - void loop(void); - void eopHandler(void); - void packetHandler(void); - void clkHandler(void); - void stop(void); - - volatile bool newData = false; - bool dataAvailable = false; - bool stateChanged = false; //save states when true - volatile uint16_t button = 0x1B1B; //no button - uint8_t payload[11]; - uint8_t states[14]; - uint8_t brightness; - bool targetIsDisplayed = false; - //uint16_t lastPressedButton; - volatile uint32_t clk_per; //lowest time between clock edges. Should be ~20 us. - - - private: - //add anti glitch method - volatile int _byteCount = 0; - volatile int _bitCount = 0; - volatile bool _dataIsOutput = false; - volatile byte _receivedByte; - volatile int _CIO_cmd_matches = 0; - volatile bool _packet = false; - volatile int _sendBit = 8; - volatile uint8_t _brightness; - volatile uint8_t _payload[11]; - int _CS_PIN; - int _CLK_PIN; - int _DATA_PIN; - uint8_t _prevPayload[11]; - bool _prevUNT; - bool _prevHTR; - bool _prevFLT; - - char _getChar(uint8_t value); -}; - -class DSP { - - public: - uint8_t payload[11] = {0xC0, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x0}; - - void begin(int dsp_cs_pin, int dsp_data_pin, int dsp_clk_pin, int dsp_audio_pin); - uint16_t getButton(void); - void updateDSP(uint8_t brightness); - void textOut(String txt); - void LEDshow(); - void playIntro(); - void beep(); - void beep2(); - - private: - void _sendBitsToDSP(uint32_t outBits, int bitsToSend); - uint16_t _receiveBitsFromDSP(); - char _getCode(char value); - - unsigned long _dspLastRefreshTime = 0; - unsigned long _dspLastGetButton = 0; - uint16_t _oldButton = ButtonCodes[NOBTN]; - //Pins - int _CS_PIN; - int _CLK_PIN; - int _DATA_PIN; - int _AUDIO_PIN; -}; - -class BWC { - - public: - BWC(); - void begin(void); - void begin2(); - void begin(int, int, int, int, int, int, int); - void loop(); - bool qCommand(uint32_t cmd, uint32_t val, uint32_t xtime, uint32_t interval); - bool newData(); - void saveEventlog(); - String getJSONStates(); - String getJSONTimes(); - String getJSONSettings(); - void setJSONSettings(String message); - String getJSONCommandQueue(); - void print(String txt); - uint8_t getState(int state); - Ticker saveSettingsTimer; - void saveSettingsFlag(); - void saveSettings(); - bool maxeffort = false; - String getPressedButton(); - void reloadCommandQueue(); - void reloadSettings(); - String getButtonName(); - void saveDebugInfo(String s); - void stop(void); - void saveRebootInfo(); - bool getBtnSeqMatch(); - - private: - CIO _cio; - DSP _dsp; - uint8_t _dspBrightness; - uint32_t _commandQ[MAXCOMMANDS][4]; - int _qCommandLen = 0; //length of commandQ - int32_t _buttonQ[MAXBUTTONS][4]; - int _qButtonLen = 0; //length of buttonQ - uint32_t _timestamp; - bool _newData = false; - uint32_t _cltime; - uint32_t _ftime; - uint32_t _uptime; - uint32_t _pumptime; - uint32_t _heatingtime; - uint32_t _airtime; - uint32_t _jettime; - uint32_t _uptime_ms; - uint32_t _pumptime_ms; - uint32_t _heatingtime_ms; - uint32_t _airtime_ms; - uint32_t _jettime_ms; - int32_t _timezone; - float _price; - uint32_t _finterval; - uint32_t _clinterval; - bool _audio; - float _cost; - bool _restoreStatesOnStart = false; - bool _saveSettingsNeeded = false; - bool _saveEventlogNeeded = false; - bool _saveCmdqNeeded = false; - bool _saveStatesNeeded = false; - int _latestTarget; - int _tickerCount; - bool _sliderPrio = true; - uint32_t _tttt_time0; //time at previous temperature change - uint32_t _tttt_time1; //time at last temperature change - int _tttt_temp0; //temp after previous change - int _tttt_temp1; //temp after last change - int _tttt; //time to target temperature after subtracting running time since last calculation - int _tttt_calculated; //constant between calculations - int _btnSequence[4] = {NOBTN,NOBTN,NOBTN,NOBTN}; //keep track of the four latest button presses - - void _qButton(uint32_t btn, uint32_t state, uint32_t value, int32_t maxduration); - void _handleCommandQ(void); - void _handleButtonQ(void); - void _startNTP(); - void _loadSettings(); - void _loadCommandQueue(); - void _saveCommandQueue(); - void _updateTimes(); - void _restoreStates(); - void _saveStates(); - int _CodeToButton(uint16_t val); -}; - -#endif diff --git a/Code/6-wire-version/lib/BWC/BWC_8266_globals.h b/Code/6-wire-version/lib/BWC/BWC_8266_globals.h deleted file mode 100644 index fc1db09d..00000000 --- a/Code/6-wire-version/lib/BWC/BWC_8266_globals.h +++ /dev/null @@ -1,171 +0,0 @@ -//only declarations here please! Definitions belong in the cpp-file -#include "model.h" - -#ifndef BWC_8266_globals_H -#define BWC_8266_globals_H - -//LSB -const uint8_t DSP_CMD2_DATAREAD = 0x42; -const uint8_t DSP_CMD1_MODE6_11_7 = 0x01; //real CIO is sending 0x01 which is illegal according to datasheet -const uint8_t DSP_CMD2_DATAWRITE = 0x40; -const uint8_t DSP_DIM_BASE = 0x80; -const uint8_t DSP_DIM_ON = 0x8; - -//Payload byte index and bit numbers (see documentation in excel file on github) -//LSB first -const byte DGT1_IDX = 1; -const byte DGT2_IDX = 3; -const byte DGT3_IDX = 5; -const byte TMR2_IDX = 7; -const byte TMR2_BIT = 1; -const byte TMR1_IDX = 7; -const byte TMR1_BIT = 2; -const byte LCK_IDX = 7; -const byte LCK_BIT = 3; -const byte TMRBTNLED_IDX = 7; -const byte TMRBTNLED_BIT = 4; -const byte REDHTR_IDX = 7; -const byte REDHTR_BIT = 5; -const byte GRNHTR_IDX = 7; -const byte GRNHTR_BIT = 6; -const byte AIR_IDX = 7; -const byte AIR_BIT = 7; -const byte FLT_IDX = 9; -const byte FLT_BIT = 1; -const byte C_IDX = 9; -const byte C_BIT = 2; -const byte F_IDX = 9; -const byte F_BIT = 3; -const byte PWR_IDX = 9; -const byte PWR_BIT = 4; -const byte HJT_IDX = 9; -const byte HJT_BIT = 5; - -//7-segment codes. MSB always 1 -const uint8_t CHARCODES[] = { - 0x7F, 0x0D, 0xB7, 0x9F, 0xCD, 0xDB, 0xFB, 0x0F, 0xFF, 0xDF, 0x01, 0x81, 0xEF, 0xF9, 0x73, 0xBD, 0xF3, 0xE3, - 0xFB, 0xE9, 0xED, 0x61, 0x1D, 0xE1, 0x71, 0x01, 0xA9, 0xB9, 0xE7, 0xCF, 0xA1, 0xDB, 0xF1, 0x39, 0x7D, 0x01, 0xDD, 0xB7 -}; -const uint8_t CHARS[] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '-', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'H', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y', 'z' -}; - -enum Buttons: byte -{ - NOBTN, - LOCK, - TIMER, - BUBBLES, - UNIT, - HEAT, - PUMP, - DOWN, - UP, - POWER, - HYDROJETS -}; - -//set to zero to disable display buttons. Order as above. -//Example: to disable UNIT and TIMER set 1,1,0,1,0,1,1,1,1,1,1 -const uint8_t EnabledButtons[] = {1,1,1,1,1,1,1,1,1,1,1}; -const String ButtonNames[] = { - "NOBTN", - "LOCK", - "TIMER", - "BUBBLES", - "UNIT", - "HEAT", - "PUMP", - "DOWN", - "UP", - "POWER", - "HYDROJETS" -}; - -#ifdef PRE2021 -const uint16_t ButtonCodes[] = -{ - 0x1B1B, 0x0200, 0x0100, 0x0300, 0x1012, 0x1212, 0x1112, 0x1312, 0x0809, 0x0000 -}; -const bool HASJETS = false; - -#elif defined(MIAMI2021) -const uint16_t ButtonCodes[] = -{ - 0x1B1B, 0x0100, 0x0300, 0x1212, 0x0809, 0x1012, 0x1112, 0x1312, 0x0200, 0x0000 -}; -const bool HASJETS = false; - -#elif defined(MALDIVES2021) -const uint16_t ButtonCodes[] = -{ - 0x1B1B, 0x0100, 0x0300, 0x1212, 0x0a09, 0x1012, 0x1312, 0x0809, 0x0200, 0x0000, 0x1112 -}; -const bool HASJETS = true; - -#else -//Make compiler happy. Will not be used. -const uint16_t ButtonCodes[] = -{ - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 -}; -const bool HASJETS = false; - -#endif - -enum States: byte -{ - LOCKEDSTATE, - POWERSTATE, - UNITSTATE, - BUBBLESSTATE, - HEATGRNSTATE, - HEATREDSTATE, - HEATSTATE, - PUMPSTATE, - TEMPERATURE, - TARGET, - CHAR1, - CHAR2, - CHAR3, - JETSSTATE -}; - - - -enum Commands: byte -{ - SETTARGET, - SETUNIT, - SETBUBBLES, - SETHEATER, - SETPUMP, - RESETQ, - REBOOTESP, - GETTARGET, - RESETTIMES, - RESETCLTIMER, - RESETFTIMER, - SETJETS, - SETBRIGHTNESS - //play song -}; - -const int MAXCOMMANDS = 11; -const int MAXBUTTONS = 200; - - -//direct port manipulation memory adresses. -#define PIN_OUT 0x60000300 -#define PIN_OUT_SET 0x60000304 -#define PIN_OUT_CLEAR 0x60000308 - -#define PIN_DIR 0x6000030C -#define PIN_DIR_OUTPUT 0x60000310 -#define PIN_DIR_INPUT 0x60000314 - -#define PIN_IN 0x60000318 - - -#endif diff --git a/Code/6-wire-version/lib/BWC/BWC_8266.cpp b/Code/6-wire-version/lib/BWC/BWC_common.cpp similarity index 61% rename from Code/6-wire-version/lib/BWC/BWC_8266.cpp rename to Code/6-wire-version/lib/BWC/BWC_common.cpp index e23f213e..2956221c 100644 --- a/Code/6-wire-version/lib/BWC/BWC_8266.cpp +++ b/Code/6-wire-version/lib/BWC/BWC_common.cpp @@ -1,36 +1,4 @@ -#include "BWC_8266.h" - -CIO *pointerToClass; -//BWC *pointerToBWC; - -//set flag instead of saving. This may avoid crashes. Some functions appears to crash when called from a timer. -// static void tick(void){ - // pointerToBWC->saveSettingsFlag(); -// } - -static void IRAM_ATTR chipselectpin(void) { - pointerToClass->packetHandler(); -} -static void IRAM_ATTR clockpin(void) { - pointerToClass->clkHandler(); -} - -void CIO::begin(int cio_cs_pin, int cio_data_pin, int cio_clk_pin) { - pointerToClass = this; - _CS_PIN = cio_cs_pin; - _DATA_PIN = cio_data_pin; - _CLK_PIN = cio_clk_pin; - pinMode(_CS_PIN, INPUT); - pinMode(_DATA_PIN, INPUT); - pinMode(_CLK_PIN, INPUT); - attachInterrupt(digitalPinToInterrupt(_CS_PIN), chipselectpin, CHANGE); - attachInterrupt(digitalPinToInterrupt(_CLK_PIN), clockpin, CHANGE); //Write on falling edge and read on rising edge -} - -void CIO::stop(){ - detachInterrupt(digitalPinToInterrupt(_CS_PIN)); - detachInterrupt(digitalPinToInterrupt(_CLK_PIN)); -} +#include "BWC_common.h" //match 7 segment pattern to a real digit char CIO::_getChar(uint8_t value) { @@ -42,229 +10,6 @@ char CIO::_getChar(uint8_t value) { return '*'; } -void CIO::loop(void) { - //newdata is true when a data packet has arrived from cio - if(newData) { - newData = false; - static int capturePhase = 0; - static uint32_t buttonReleaseTime; - static uint16_t prevButton = ButtonCodes[NOBTN]; - //require two consecutive messages to be equal before registering - static uint8_t prev_checksum = 0; - uint8_t checksum = 0; - for(int i = 0; i < 11; i++){ - checksum += _payload[i]; - } - if(checksum != prev_checksum) { - prev_checksum = checksum; - return; - } - prev_checksum = checksum; - - //copy private array to public array - for(int i = 0; i < 11; i++){ - payload[i] = _payload[i]; - } - - //determine if anything changed, so we can update webclients - for(int i = 0; i < 11; i++){ - if (payload[i] != _prevPayload[i]) dataAvailable = true; - _prevPayload[i] = payload[i]; - } - - brightness = _brightness & 7; //extract only the brightness bits (0-7) - //extract information from payload to a better format - states[LOCKEDSTATE] = (payload[LCK_IDX] & (1 << LCK_BIT)) > 0; - states[POWERSTATE] = (payload[PWR_IDX] & (1 << PWR_BIT)) > 0; - states[UNITSTATE] = (payload[C_IDX] & (1 << C_BIT)) > 0; - states[BUBBLESSTATE] = (payload[AIR_IDX] & (1 << AIR_BIT)) > 0; - states[HEATGRNSTATE] = (payload[GRNHTR_IDX] & (1 << GRNHTR_BIT)) > 0; - states[HEATREDSTATE] = (payload[REDHTR_IDX] & (1 << REDHTR_BIT)) > 0; - states[HEATSTATE] = states[HEATGRNSTATE] || states[HEATREDSTATE]; - states[PUMPSTATE] = (payload[FLT_IDX] & (1 << FLT_BIT)) > 0; - states[CHAR1] = (uint8_t)_getChar(payload[DGT1_IDX]); - states[CHAR2] = (uint8_t)_getChar(payload[DGT2_IDX]); - states[CHAR3] = (uint8_t)_getChar(payload[DGT3_IDX]); - if(HASJETS) states[JETSSTATE] = (payload[HJT_IDX] & (1 << HJT_BIT)) > 0; - else states[JETSSTATE] = 0; - //Determine if display is showing target temp or actual temp or anything else. - //capture TARGET after UP/DOWN has been pressed... - if( ((button == ButtonCodes[UP]) || (button == ButtonCodes[DOWN])) && (prevButton != ButtonCodes[UP]) && (prevButton != ButtonCodes[DOWN]) ) capturePhase = 1; - //...until 2 seconds after UP/DOWN released - if( (button == ButtonCodes[UP]) || (button == ButtonCodes[DOWN]) ) buttonReleaseTime = millis(); - if(millis()-buttonReleaseTime > 2000) capturePhase = 0; - //convert text on display to a value if the chars are recognized - if(states[CHAR1] == '*' || states[CHAR2] == '*' || states[CHAR3] == '*') return; - String tempstring = String((char)states[CHAR1])+String((char)states[CHAR2])+String((char)states[CHAR3]); - uint8_t tmpTemp = tempstring.toInt(); - //capture only if showing plausible values (not blank screen while blinking) - if( (capturePhase == 1) && (tmpTemp > 19) ) { - states[TARGET] = tmpTemp; - } - //wait 4 seconds after UP/DOWN is released to be sure that actual temp is shown - if( (capturePhase == 0) && (millis()-buttonReleaseTime > 10000) && payload[DGT3_IDX]!=0xED && payload[DGT3_IDX]!=0) states[TEMPERATURE] = tmpTemp; - prevButton = button; - - if(states[UNITSTATE] != _prevUNT || states[HEATSTATE] != _prevHTR || states[PUMPSTATE] != _prevFLT) { - stateChanged = true; - _prevUNT = states[UNITSTATE]; - _prevHTR = states[HEATSTATE]; - _prevFLT = states[PUMPSTATE]; - } - } -} - -//end of packet -void IRAM_ATTR CIO::eopHandler(void) { - //process latest data and enter corresponding mode (like listen for DSP_STS or send BTN_OUT) - //pinMode(_DATA_PIN, INPUT); - WRITE_PERI_REG( PIN_DIR_INPUT, 1 << _DATA_PIN); - _byteCount = 0; - _bitCount = 0; - uint8_t msg = _receivedByte; - - switch (msg) { - case DSP_CMD1_MODE6_11_7: - _CIO_cmd_matches = 1; - break; - case DSP_CMD2_DATAWRITE: - if (_CIO_cmd_matches == 1) { - _CIO_cmd_matches = 2; - } else { - _CIO_cmd_matches = 0; //reset - DSP_CMD1_MODE6_11_7 must be followed by DSP_CMD2_DATAWRITE to activate command - } - break; - default: - if (_CIO_cmd_matches == 3) { - _brightness = msg; - _CIO_cmd_matches = 0; - newData = true; - } - if (_CIO_cmd_matches == 2) { - _CIO_cmd_matches = 3; - } - break; - } -} - -//CIO comm -//packet start -//arduino core 3.0.1+ should work with digitalWrite() now. -void IRAM_ATTR CIO::packetHandler(void) { - if (!(READ_PERI_REG(PIN_IN) & (1 << _CS_PIN))) { - //packet start - _packet = true; - } - else { - //end of packet - _packet = false; - _dataIsOutput = false; - eopHandler(); - } -} - -//CIO comm -//Read incoming bits, and take action after a complete byte -void IRAM_ATTR CIO::clkHandler(void) { - //sanity check on clock signal - static uint32_t prev_us = 0; - uint32_t us = micros(); - uint32_t period = 0; - if(us > prev_us) period = us - prev_us; //will be negative on rollover (once every hour-ish) - prev_us = us; - if(period < clk_per) clk_per = period; - - if (!_packet) return; - //CS line is active, so send/receive bits on DATA line - - bool clockstate = READ_PERI_REG(PIN_IN) & (1 << _CLK_PIN); - - //shift out bits on low clock (falling edge) - if (!clockstate & _dataIsOutput) { - //send BTN_OUT - if (button & (1 << _sendBit)) { - //digitalWrite(_DATA_PIN, HIGH); - WRITE_PERI_REG( PIN_OUT_SET, 1 << _DATA_PIN); - } - else { - //digitalWrite(_DATA_PIN, LOW); - WRITE_PERI_REG( PIN_OUT_CLEAR, 1 << _DATA_PIN); - } - _sendBit++; - if(_sendBit > 15) _sendBit = 0; - } - - //read bits on high clock (rising edge) - if (clockstate & !_dataIsOutput) { - //read data pin to a byte - //_receivedByte = (_receivedByte << 1) | digitalRead(_DATA_PIN); - //_receivedByte = (_receivedByte << 1) | ( ( (READ_PERI_REG(PIN_IN) & (1 << _DATA_PIN)) ) > 0); - //_receivedByte = (_receivedByte >> 1) | digitalRead(_DATA_PIN) << 7; - _receivedByte = (_receivedByte >> 1) | ( ( (READ_PERI_REG(PIN_IN) & (1 << _DATA_PIN)) ) > 0) << 7; - _bitCount++; - if (_bitCount == 8) { - _bitCount = 0; - if (_CIO_cmd_matches == 2) { //meaning we have received the header for 11 data bytes to come - _payload[_byteCount] = _receivedByte; - _byteCount++; - } - else if (_receivedByte == DSP_CMD2_DATAREAD) { - _sendBit = 8; - _dataIsOutput = true; - //pinMode(_DATA_PIN, OUTPUT); - WRITE_PERI_REG( PIN_DIR_OUTPUT, 1 << _DATA_PIN); - } - } - } -} - -uint16_t DSP::getButton(void) { - if(millis() - _dspLastGetButton > 50){ - uint16_t newButton = 0; - _dspLastGetButton = millis(); - //send request - //pinMode(_DATA_PIN, OUTPUT); - digitalWrite(_CS_PIN, LOW); //start of packet - delayMicroseconds(50); - _sendBitsToDSP(DSP_CMD2_DATAREAD, 8); //request button presses - newButton = _receiveBitsFromDSP(); - digitalWrite(_CS_PIN, HIGH); //end of packet - delayMicroseconds(30); - _oldButton = newButton; - return (newButton); - } else return (_oldButton); -} - - -//bitsToSend can only be 8 with this solution of LSB first -void DSP::_sendBitsToDSP(uint32_t outBits, int bitsToSend) { - pinMode(_DATA_PIN, OUTPUT); - delayMicroseconds(20); - for (int i = 0; i < bitsToSend; i++) { - digitalWrite(_CLK_PIN, LOW); - digitalWrite(_DATA_PIN, outBits & (1 << i)); - delayMicroseconds(20); - digitalWrite(_CLK_PIN, HIGH); - delayMicroseconds(20); - } -} - -uint16_t DSP::_receiveBitsFromDSP() { - //bitbanging the answer from Display - uint16_t result = 0; - pinMode(_DATA_PIN, INPUT); - - for (int i = 0; i < 16; i++) { - digitalWrite(_CLK_PIN, LOW); //clock leading edge - delayMicroseconds(20); - digitalWrite(_CLK_PIN, HIGH); //clock trailing edge - delayMicroseconds(20); - int j = (i+8)%16; //bit 8-16 then 0-7 - result |= digitalRead(_DATA_PIN) << j; - } - return result; -} - char DSP::_getCode(char value) { for (unsigned int index = 0; index < sizeof(CHARS); index++) { if (value == CHARS[index]) { @@ -274,158 +19,33 @@ char DSP::_getCode(char value) { return 0x00; //no match, return 'space' } -void DSP::updateDSP(uint8_t brightness) { - //refresh display with ~10Hz - if(millis() -_dspLastRefreshTime > 99){ - _dspLastRefreshTime = millis(); - delayMicroseconds(30); - digitalWrite(_CS_PIN, LOW); //start of packet - _sendBitsToDSP(DSP_CMD1_MODE6_11_7, 8); - digitalWrite(_CS_PIN, HIGH); //end of packet - - delayMicroseconds(50); - digitalWrite(_CS_PIN, LOW);//start of packet - _sendBitsToDSP(DSP_CMD2_DATAWRITE, 8); - digitalWrite(_CS_PIN, HIGH);//end of packet - - //payload - delayMicroseconds(50); - digitalWrite(_CS_PIN, LOW);//start of packet - for (int i = 0; i < 11; i++) - _sendBitsToDSP(payload[i], 8); - digitalWrite(_CS_PIN, HIGH);//end of packet - - delayMicroseconds(50); - digitalWrite(_CS_PIN, LOW);//start of packet - _sendBitsToDSP(DSP_DIM_BASE+DSP_DIM_ON+brightness, 8); - digitalWrite(_CS_PIN, HIGH);//end of packet - delayMicroseconds(50); - } -} - -void DSP::textOut(String txt) { - int len = txt.length(); - //Set CMD3 (address 00H) - payload[0] = 0xC0; - if (len >= 3) { - for (int i = 0; i < len - 2; i++) { - payload[DGT1_IDX] = _getCode(txt.charAt(i)); - payload[DGT2_IDX] = _getCode(txt.charAt(i + 1)); - payload[DGT3_IDX] = _getCode(txt.charAt(i + 2)); - updateDSP(7); - delay(230); - } - } - else if (len == 2) { - payload[DGT1_IDX] = _getCode(' '); - payload[DGT2_IDX] = _getCode(txt.charAt(0)); - payload[DGT3_IDX] = _getCode(txt.charAt(1)); - updateDSP(7); - } - else if (len == 1) { - payload[DGT1_IDX] = _getCode(' '); - payload[DGT2_IDX] = _getCode(' '); - payload[DGT3_IDX] = _getCode(txt.charAt(0)); - updateDSP(7); - } -} - -void DSP::LEDshow() { - for(int y = 7; y < 11; y++){ - for(int x = 1; x < 9; x++){ - payload[y] = (1 << x) + 1; - updateDSP(7); - delay(200); - } - } -} - -void DSP::begin(int dsp_cs_pin, int dsp_data_pin, int dsp_clk_pin, int dsp_audio_pin) { - _CS_PIN = dsp_cs_pin; - _DATA_PIN = dsp_data_pin; - _CLK_PIN = dsp_clk_pin; - _AUDIO_PIN = dsp_audio_pin; - - pinMode(_CS_PIN, OUTPUT); - pinMode(_DATA_PIN, INPUT); - pinMode(_CLK_PIN, OUTPUT); - pinMode(_AUDIO_PIN, OUTPUT); - digitalWrite(_CS_PIN, HIGH); //Active LOW - digitalWrite(_CLK_PIN, HIGH); //shift on falling, latch on rising - digitalWrite(_AUDIO_PIN, LOW); -} - -void DSP::playIntro() { - int longnote = 125; - int shortnote = 63; - - tone(_AUDIO_PIN, NOTE_C7, longnote); - delay(2 * longnote); - tone(_AUDIO_PIN, NOTE_G6, shortnote); - delay(2 * shortnote); - tone(_AUDIO_PIN, NOTE_G6, shortnote); - delay(2 * shortnote); - tone(_AUDIO_PIN, NOTE_A6, longnote); - delay(2 * longnote); - tone(_AUDIO_PIN, NOTE_G6, longnote); - delay(2 * longnote); - //paus - delay(2 * longnote); - tone(_AUDIO_PIN, NOTE_B6, longnote); - delay(2 * longnote); - tone(_AUDIO_PIN, NOTE_C7, longnote); - delay(2 * longnote); - noTone(_AUDIO_PIN); -} - -//silent beep instead of annoying beeps every time something changes -void DSP::beep() { - //int longnote = 125; - // int shortnote = 63; - // tone(_AUDIO_PIN, NOTE_C6, shortnote); - // delay(shortnote); - // tone(_AUDIO_PIN, NOTE_C7, shortnote); - // delay(shortnote); - // noTone(_AUDIO_PIN); -} - -//new beep for button presses only -void DSP::beep2() { - //int longnote = 125; - int shortnote = 40; - tone(_AUDIO_PIN, NOTE_D6, shortnote); - delay(shortnote); - tone(_AUDIO_PIN, NOTE_D7, shortnote); - delay(shortnote); - tone(_AUDIO_PIN, NOTE_D8, shortnote); - delay(shortnote); - noTone(_AUDIO_PIN); -} - BWC::BWC(){} void BWC::begin(void){ - _cio.begin(D1, D7, D2); - _dsp.begin(D3, D5, D4, D6); + _cio.begin(ciopins[0], ciopins[1], ciopins[2]); + _dsp.begin(dsppins[0], dsppins[1], dsppins[2], dsppins[3]); begin2(); } +//overloaded function if user want to manually use different pinout void BWC::begin( - int cio_cs_pin, - int cio_data_pin, + //left symbol is for type1, right for type2 + int cio_data_td_pin, int cio_clk_pin, - int dsp_cs_pin, - int dsp_data_pin, + int cio_cs_ld_pin, + int dsp_data_td_pin, int dsp_clk_pin, + int dsp_cs_ld_pin, int dsp_audio_pin ) - { +{ //start CIO and DSP modules - _cio.begin(cio_cs_pin, cio_data_pin, cio_clk_pin); - _dsp.begin(dsp_cs_pin, dsp_data_pin, dsp_clk_pin, dsp_audio_pin); + _cio.begin(cio_data_td_pin, cio_clk_pin, cio_cs_ld_pin); + _dsp.begin(dsp_data_td_pin, dsp_clk_pin, dsp_cs_ld_pin, dsp_audio_pin); begin2(); } + void BWC::begin2(){ //Initialize variables _dspBrightness = 7; //default = max brightness @@ -469,8 +89,7 @@ void BWC::loop(){ _timestamp = DateTime.now(); //update DSP payload (memcpy(dest*, source*, len)) - //memcpy(&_dsp.payload[0], &_cio.payload[0], 11); - for(int i = 0; i < 11; i++){ + for(unsigned int i = 0; i < sizeof(_dsp.payload); i++){ _dsp.payload[i] = _cio.payload[i]; } _dsp.updateDSP(_dspBrightness); @@ -490,7 +109,7 @@ void BWC::loop(){ } if(_saveStatesNeeded) _saveStates(); //if set target command missed we need to correct that - if( (_cio.states[TARGET] != _latestTarget) && (_qButtonLen == 0) && (_latestTarget != 0) && (_sliderPrio) ) qCommand(SETTARGET, _latestTarget, 0, 0); + if( (_cio.states[TARGET] != _sliderTarget) && (_qButtonLen == 0) && (_sliderTarget != 0) && (_sliderPrio) ) qCommand(SETTARGET, _sliderTarget, 0, 0); //if target temp is unknown, find out. if( (_cio.states[TARGET] == 0) && (_qButtonLen == 0) ) qCommand(GETTARGET, (uint32_t)' ', 0, 0); @@ -581,7 +200,7 @@ void BWC::_handleButtonQ(void) { //set buttoncode _cio.button = ButtonCodes[_buttonQ[0][0]]; } - } + } else { static uint16_t prevbtn = ButtonCodes[NOBTN]; @@ -664,37 +283,43 @@ void BWC::_handleCommandQ(void) { if (_timestamp >= _commandQ[0][2]){ _qButton(POWER, POWERSTATE, 1, 5000); //press POWER button until states[POWERSTATE] is 1, max 5000 ms _qButton(LOCK, LOCKEDSTATE, 0, 5000); //press LOCK button until states[LOCKEDSTATE] is 0 - switch (_commandQ[0][0]) { + switch (_commandQ[0][0]) + { case SETTARGET: { - _latestTarget = _commandQ[0][1]; - //Fiddling with the hardware buttons is ignored while this command executes. + _sliderTarget = _commandQ[0][1]; _sliderPrio = true; - //Press up/down appropriate number of times. We need to time this well. int diff = (int)_commandQ[0][1] - (int)_cio.states[TARGET]; - //First press will just show current target temp - int pushtime = 500; //how fast can we do this??******************* + int pushtime = 500; int releasetime = 300; - _qButton(UP, TARGET, _commandQ[0][1], pushtime); - _qButton(NOBTN, TARGET, _commandQ[0][1], releasetime); uint32_t updown; diff<0 ? updown = DOWN : updown = UP; - for(int i = 0; i < abs(diff); i++) - { - _qButton(updown, CHAR1, 0xFF, pushtime); - _qButton(NOBTN, CHAR1, 0xFF, releasetime); - } - //Old method overshoots target too often: - //choose which direction to go (up or down) - // if(_cio.states[TARGET] > _commandQ[0][1]) _qButton(DOWN, TARGET, _commandQ[0][1], 10000); - // if(_cio.states[TARGET] < _commandQ[0][1]) _qButton(UP, TARGET, _commandQ[0][1], 10000); + _qButton(updown, CHAR1, 0xFF, pushtime); + _qButton(NOBTN, CHAR1, 0xFF, releasetime); break; } case SETUNIT: + /* + Quick convert temperature to other unit. + This will also be done automatically in 10 seconds. + But we are impatient. + */ + if(_commandQ[0][1] && !_cio.states[UNITSTATE]) + { + //F to C + _cio.states[TEMPERATURE] = round((_cio.states[TEMPERATURE]-32)/1.8); + _cio.states[TARGET] = round((_cio.states[TARGET]-32)/1.8); + } + if(!_commandQ[0][1] && _cio.states[UNITSTATE]) + { + //C to F + _cio.states[TEMPERATURE] = round((_cio.states[TEMPERATURE]*1.8)+32); + _cio.states[TARGET] = round((_cio.states[TARGET]*1.8)+32); + } _qButton(UNIT, UNITSTATE, _commandQ[0][1], 5000); - _qButton(NOBTN, CHAR3, 0xFF, 700); - _qButton(UP, CHAR3, _commandQ[0][1], 700); - _latestTarget = 0; //force update + _qButton(NOBTN, CHAR3, 0xFF, 300); + //_qButton(UP, CHAR3, _commandQ[0][1], 500); + _sliderTarget = 0; //force update break; case SETBUBBLES: _qButton(BUBBLES, BUBBLESSTATE, _commandQ[0][1], 5000); @@ -739,7 +364,10 @@ void BWC::_handleCommandQ(void) { _qButton(HYDROJETS, JETSSTATE, _commandQ[0][1], 5000); break; case SETBRIGHTNESS: - _dspBrightness = _commandQ[0][1] & 7; + if(_commandQ[0][1] < 9) _dspBrightness = _commandQ[0][1]; + break; + case SETBEEP: + _commandQ[0][1] == 0 ? _dsp.beep2() : _dsp.playIntro(); break; } //If interval > 0 then append to commandQ with updated xtime. @@ -755,6 +383,7 @@ void BWC::_handleCommandQ(void) { _saveCommandQueue(); if(restartESP) { saveSettings(); + delay(3000); ESP.restart(); } } @@ -848,6 +477,7 @@ String BWC::getJSONSettings(){ doc["REBOOTINFO"] = ESP.getResetReason(); doc["REBOOTTIME"] = DateTime.getBootTime(); doc["RESTORE"] = _restoreStatesOnStart; + doc["MODEL"] = MYMODEL; // Serialize JSON to string String jsonmsg; @@ -1138,10 +768,10 @@ void BWC::_restoreStates() { uint8_t unt = doc["UNT"]; uint8_t flt = doc["FLT"]; uint8_t htr = doc["HTR"]; - qCommand(SETUNIT, unt, DateTime.now()+10, 0); + qCommand(SETUNIT, unt, 0, 0); _cio.states[UNITSTATE] = unt; - qCommand(SETPUMP, flt, DateTime.now()+12, 0); - qCommand(SETHEATER, htr, DateTime.now()+14, 0); + qCommand(SETPUMP, flt, 0, 0); + qCommand(SETHEATER, htr, 0, 0); Serial.println("restoring states"); file.close(); } diff --git a/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266.h b/Code/6-wire-version/lib/BWC/BWC_common.h similarity index 54% rename from Code/6-wire-version/lib/BWC54149E/BWC54149E_8266.h rename to Code/6-wire-version/lib/BWC/BWC_common.h index 17dc373c..4ffa352a 100644 --- a/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266.h +++ b/Code/6-wire-version/lib/BWC/BWC_common.h @@ -1,16 +1,11 @@ -#ifndef BWC54149E_8266_H -#define BWC54149E_8266_H +#ifndef BWC_common_H +#define BWC_common_H #ifndef ESP8266 #error "This library supports 8266 only" #endif #include "Arduino.h" - -#ifndef BWC54149E_8266_globals_h -#include "BWC54149E_8266_globals.h" -#endif - //long long needed in arduino core v3+ #define ARDUINOJSON_USE_LONG_LONG 1 #include @@ -18,79 +13,14 @@ #include #include #include "pitches.h" +#include "model.h" +#include "BWC_const.h" - - -class CIO { - - public: - void begin(int cio_cs_pin, int cio_data_pin, int cio_clk_pin); - void loop(void); - void eopHandler(void); - void LEDdataHandler(void); - void clkHandler(void); - void stop(void); - - volatile bool newData = false; - bool dataAvailable = false; - bool stateChanged = false; //save states when true - volatile uint16_t button = NOBTN; - uint8_t payload[5]; - uint8_t states[14]; - uint8_t brightness; - bool targetIsDisplayed = false; - //uint16_t lastPressedButton; - - - private: - volatile int _byteCount = 0; - volatile int _bitCount = 0; - volatile byte _receivedByte; - volatile bool _packet = false; - volatile int _sendBit = 8; - volatile uint8_t _brightness; - volatile uint8_t _payload[5]; - int _CIO_TD_PIN; - int _CIO_CLK_PIN; - int _CIO_LD_PIN; - uint8_t _prevPayload[5]; - bool _prevUNT; - bool _prevHTR; - bool _prevFLT; - uint8_t _received_cmd = 0; //temporary storage of command message - - char _getChar(uint8_t value); -}; - -class DSP { - - public: - uint8_t payload[5]; - - void begin(int dsp_cs_pin, int dsp_data_pin, int dsp_clk_pin, int dsp_audio_pin); - uint16_t getButton(void); - void updateDSP(uint8_t brightness); - void textOut(String txt); - void LEDshow(); - void playIntro(); - void beep(); - void beep2(); - - private: - void _sendBitsToDSP(uint32_t outBits, int bitsToSend); - uint16_t _receiveBitsFromDSP(); - char _getCode(char value); - - unsigned long _dspLastRefreshTime = 0; - unsigned long _dspLastGetButton = 0; - uint16_t _oldButton = ButtonCodes[NOBTN]; - uint16_t _prevButton = ButtonCodes[NOBTN]; - //Pins - int _DSP_LD_PIN; - int _DSP_TD_PIN; - int _DSP_CLK_PIN; - int _DSP_AUDIO_PIN; -}; +#if defined(MODEL54149E) +#include "BWC_6w_type2.h" +#else +#include "BWC_6w_type1.h" +#endif class BWC { @@ -128,7 +58,7 @@ class BWC { DSP _dsp; uint8_t _dspBrightness; uint32_t _commandQ[MAXCOMMANDS][4]; - int _qCommandLen = 0; //length of commandQ + int _qCommandLen = 0; //length of commandQ int32_t _buttonQ[MAXBUTTONS][4]; int _qButtonLen = 0; //length of buttonQ uint32_t _timestamp; @@ -156,18 +86,18 @@ class BWC { bool _saveEventlogNeeded = false; bool _saveCmdqNeeded = false; bool _saveStatesNeeded = false; - int _latestTarget; + int _sliderTarget; int _tickerCount; bool _sliderPrio = true; uint32_t _tttt_time0; //time at previous temperature change uint32_t _tttt_time1; //time at last temperature change - int _tttt_temp0; //temp after previous change - int _tttt_temp1; //temp after last change - int _tttt; //time to target temperature after subtracting running time since last calculation + int _tttt_temp0; //temp after previous change + int _tttt_temp1; //temp after last change + int _tttt; //time to target temperature after subtracting running time since last calculation int _tttt_calculated; //constant between calculations int _btnSequence[4] = {NOBTN,NOBTN,NOBTN,NOBTN}; //keep track of the four latest button presses - void _qButton(uint32_t btn, uint32_t state, uint32_t value, uint32_t maxduration); + void _qButton(uint32_t btn, uint32_t state, uint32_t value, int32_t maxduration); void _handleCommandQ(void); void _handleButtonQ(void); void _startNTP(); @@ -180,4 +110,4 @@ class BWC { int _CodeToButton(uint16_t val); }; -#endif +#endif \ No newline at end of file diff --git a/Code/6-wire-version/lib/BWC/BWC_const.h b/Code/6-wire-version/lib/BWC/BWC_const.h new file mode 100644 index 00000000..5bee2ffe --- /dev/null +++ b/Code/6-wire-version/lib/BWC/BWC_const.h @@ -0,0 +1,97 @@ +#ifndef BWC_CONST_H +#define BWC_CONST_H +#include "Arduino.h" +#include "model.h" + +const uint8_t DSP_DIM_BASE = 0x80; +const uint8_t DSP_DIM_ON = 0x8; + +const uint8_t CHARS[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '-', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'H', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y', 'z' +}; + +enum Buttons: byte +{ + NOBTN, + LOCK, + TIMER, + BUBBLES, + UNIT, + HEAT, + PUMP, + DOWN, + UP, + POWER, + HYDROJETS +}; + +//set to zero to disable display buttons. Order as above. +//Example: to disable UNIT and TIMER set 1,1,0,1,0,1,1,1,1,1,1 +const uint8_t EnabledButtons[] = {1,1,1,1,1,1,1,1,1,1,1}; +const String ButtonNames[] = { + "NOBTN", + "LOCK", + "TIMER", + "BUBBLES", + "UNIT", + "HEAT", + "PUMP", + "DOWN", + "UP", + "POWER", + "HYDROJETS" +}; + +enum States: byte +{ + LOCKEDSTATE, + POWERSTATE, + UNITSTATE, + BUBBLESSTATE, + HEATGRNSTATE, + HEATREDSTATE, + HEATSTATE, + PUMPSTATE, + TEMPERATURE, + TARGET, + CHAR1, + CHAR2, + CHAR3, + JETSSTATE +}; + +enum Commands: byte +{ + SETTARGET, + SETUNIT, + SETBUBBLES, + SETHEATER, + SETPUMP, + RESETQ, + REBOOTESP, + GETTARGET, + RESETTIMES, + RESETCLTIMER, + RESETFTIMER, + SETJETS, + SETBRIGHTNESS, + SETBEEP +}; + +const int MAXCOMMANDS = 11; +const int MAXBUTTONS = 200; + + +//direct port manipulation memory adresses. +#define PIN_OUT 0x60000300 +#define PIN_OUT_SET 0x60000304 +#define PIN_OUT_CLEAR 0x60000308 + +#define PIN_DIR 0x6000030C +#define PIN_DIR_OUTPUT 0x60000310 +#define PIN_DIR_INPUT 0x60000314 + +#define PIN_IN 0x60000318 + +#endif \ No newline at end of file diff --git a/Code/6-wire-version/lib/BWC/model.h b/Code/6-wire-version/lib/BWC/model.h index 18d34c67..2ddf7ae6 100644 --- a/Code/6-wire-version/lib/BWC/model.h +++ b/Code/6-wire-version/lib/BWC/model.h @@ -1,6 +1,10 @@ // Uncomment your model and comment out the rest //#define MODEL54149E //Paris airjet 54149E -#define PRE2021 //the older one, no hydrojets -//#define MIAMI2021 //no hydrojets -//#define MALDIVES2021 //hydrojets \ No newline at end of file +#define PRE2021 //the older one, no hydrojets +//#define MIAMI2021 //no hydrojets +//#define MALDIVES2021 //hydrojets + +//If using/testing the new PCB choose PCB_V2 +#define PCB_V1 +//#define PCB_V2 //The PCB with rounded corners \ No newline at end of file diff --git a/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266.cpp b/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266.cpp deleted file mode 100644 index 4ed723d6..00000000 --- a/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266.cpp +++ /dev/null @@ -1,1224 +0,0 @@ -#include "BWC54149E_8266.h" - -CIO *pointerToClass; -//BWC *pointerToBWC; - -//set flag instead of saving. This may avoid crashes. Some functions appears to crash when called from a timer. -// static void tick(void){ - // pointerToBWC->saveSettingsFlag(); -// } - -static void IRAM_ATTR LEDdatapin(void) { - pointerToClass->LEDdataHandler(); -} -static void IRAM_ATTR clockpin(void) { - pointerToClass->clkHandler(); -} - -void CIO::begin(int cio_td_pin, int cio_clk_pin, int cio_ld_pin) { - pointerToClass = this; - _CIO_TD_PIN = cio_td_pin; - _CIO_CLK_PIN = cio_clk_pin; - _CIO_LD_PIN = cio_ld_pin; - pinMode(_CIO_LD_PIN, INPUT); - pinMode(_CIO_TD_PIN, OUTPUT); - pinMode(_CIO_CLK_PIN, INPUT); - digitalWrite(_CIO_TD_PIN, 1); //idle high - attachInterrupt(digitalPinToInterrupt(_CIO_LD_PIN), LEDdatapin, CHANGE); - attachInterrupt(digitalPinToInterrupt(_CIO_CLK_PIN), clockpin, CHANGE); //Write on falling edge and read on rising edge -} - -void CIO::stop(){ - detachInterrupt(digitalPinToInterrupt(_CIO_LD_PIN)); - detachInterrupt(digitalPinToInterrupt(_CIO_CLK_PIN)); -} - - -//match 7 segment pattern to a real digit -char CIO::_getChar(uint8_t value) { - for (unsigned int index = 0; index < sizeof(CHARCODES); index++) { - if (value == CHARCODES[index]) { - return CHARS[index]; - } - } - return '*'; -} - -void CIO::loop(void) { - //newdata is true when a data packet has arrived from cio - if(newData) { - newData = false; - static int capturePhase = 0; - static uint32_t buttonReleaseTime; - static uint16_t prevButton = ButtonCodes[NOBTN]; - /* - * This model is only sending messages when something updated - * so this section is not useful - */ - /* - //require two consecutive messages to be equal before registering - static uint8_t prev_checksum = 0; - uint8_t checksum = 0; - for(unsigned int i = 0; i < sizeof(payload); i++){ - checksum += _payload[i]; - } - if(checksum != prev_checksum) { - prev_checksum = checksum; - return; - } - prev_checksum = checksum; - */ - - //copy private array to public array - for(unsigned int i = 0; i < sizeof(payload); i++){ - payload[i] = _payload[i]; - } - - //determine if anything changed, so we can update webclients - for(unsigned int i = 0; i < sizeof(payload); i++){ - if (payload[i] != _prevPayload[i]) dataAvailable = true; - _prevPayload[i] = payload[i]; - } - - //brightness = _brightness & 7; //extract only the brightness bits (0-7) - //extract information from payload to a better format - states[LOCKEDSTATE] = (payload[LCK_IDX] & (1 << LCK_BIT)) > 0; - states[POWERSTATE] = 1; //(payload[PWR_IDX] & (1 << PWR_BIT)) > 0; - states[UNITSTATE] = (payload[C_IDX] & (1 << C_BIT)) > 0; - states[BUBBLESSTATE] = (payload[AIR_IDX] & (1 << AIR_BIT)) > 0; - states[HEATGRNSTATE] = (payload[GRNHTR_IDX] & (1 << GRNHTR_BIT)) > 0; - states[HEATREDSTATE] = (payload[REDHTR_IDX] & (1 << REDHTR_BIT)) > 0; - states[HEATSTATE] = states[HEATGRNSTATE] || states[HEATREDSTATE]; - states[PUMPSTATE] = (payload[FLT_IDX] & (1 << FLT_BIT)) > 0; - states[CHAR1] = (uint8_t)_getChar(payload[DGT1_IDX]); - states[CHAR2] = (uint8_t)_getChar(payload[DGT2_IDX]); - states[CHAR3] = (uint8_t)_getChar(payload[DGT3_IDX]); - if(HASJETS) states[JETSSTATE] = (payload[HJT_IDX] & (1 << HJT_BIT)) > 0; - else states[JETSSTATE] = 0; - //Determine if display is showing target temp or actual temp or anything else. - //capture TARGET after UP/DOWN has been pressed... - if( ((button == ButtonCodes[UP]) || (button == ButtonCodes[DOWN])) && (prevButton != ButtonCodes[UP]) && (prevButton != ButtonCodes[DOWN]) ) capturePhase = 1; - //...until 2 seconds after UP/DOWN released - if( (button == ButtonCodes[UP]) || (button == ButtonCodes[DOWN]) ) buttonReleaseTime = millis(); - if(millis()-buttonReleaseTime > 2000) capturePhase = 0; - //convert text on display to a value if the chars are recognized - if(states[CHAR1] == '*' || states[CHAR2] == '*' || states[CHAR3] == '*') return; - String tempstring = String((char)states[CHAR1])+String((char)states[CHAR2])+String((char)states[CHAR3]); - uint8_t tmpTemp = tempstring.toInt(); - //capture only if showing plausible values (not blank screen while blinking) - if( (capturePhase == 1) && (tmpTemp > 19) ) { - states[TARGET] = tmpTemp; - } - //wait 4 seconds after UP/DOWN is released to be sure that actual temp is shown - if( (capturePhase == 0) && (millis()-buttonReleaseTime > 10000) && payload[DGT3_IDX]!=0xED && payload[DGT3_IDX]!=0) states[TEMPERATURE] = tmpTemp; - prevButton = button; - - if(states[UNITSTATE] != _prevUNT || states[HEATSTATE] != _prevHTR || states[PUMPSTATE] != _prevFLT) { - stateChanged = true; - _prevUNT = states[UNITSTATE]; - _prevHTR = states[HEATSTATE]; - _prevFLT = states[PUMPSTATE]; - } - } -} - -//CIO comm -//packet start/stop -void IRAM_ATTR CIO::LEDdataHandler(void) { - //Check START/END condition: _LD_PIN change when _CLK_PIN is high. - if (READ_PERI_REG(PIN_IN) & (1 << _CIO_CLK_PIN)) { - _byteCount = 0; - _bitCount = 0; - _received_cmd = 0; - newData = READ_PERI_REG(PIN_IN) & (1 << _CIO_LD_PIN); - } -} - -void IRAM_ATTR CIO::clkHandler(void) { - //read data on _cio_ld_pin and write to _cio_td_pin (LSBF) - - uint16_t td_bitnumber = _bitCount % 10; - uint16_t ld_bitnumber = _bitCount % 8; - uint16_t buttonwrapper = (B11111110 << 8) | (button<<1); //startbit @ bit0, stopbit @ bit9 - - //rising or falling edge? - bool risingedge = READ_PERI_REG(PIN_IN) & (1 << _CIO_CLK_PIN); - if(risingedge){ - //clk rising edge - _byteCount = _bitCount / 8; - if(_byteCount == 0){ - _received_cmd |= ((READ_PERI_REG(PIN_IN) & (1 << _CIO_LD_PIN))>0) << ld_bitnumber; - } - else if( (_byteCount<6) && (_received_cmd == CMD2) ){ //only write to payload after CMD2. Also protect from buffer overflow - //overwrite the old payload bit with new bit - _payload[_byteCount-1] = (_payload[_byteCount-1] & ~(1 << ld_bitnumber)) | ((READ_PERI_REG(PIN_IN) & (1 << _CIO_LD_PIN))>0) << ld_bitnumber; - } - //store brightness in _cio local variable. It is not used, but put here in case we want to obey the pump. - if(_bitCount == 7 && (_received_cmd & B11000000) == B10000000) _brightness = _received_cmd; - _bitCount++; - } else { - //clk falling edge - //first and last bit is a dummy start/stop bit (0/1), then 8 data bits in btwn - if (buttonwrapper & (1 << td_bitnumber)) { - WRITE_PERI_REG( PIN_OUT_SET, 1 << _CIO_TD_PIN); - } else { - WRITE_PERI_REG( PIN_OUT_CLEAR, 1 << _CIO_TD_PIN); - } - } -} - -uint16_t DSP::getButton(void) { - if(millis() - _dspLastGetButton > 20){ - uint16_t newButton = 0; - _dspLastGetButton = millis(); - //startbit - digitalWrite(_DSP_CLK_PIN, LOW); - delayMicroseconds(CLKPW); - digitalWrite(_DSP_CLK_PIN, HIGH); - delayMicroseconds(CLKPW); - //clock in 8 data bits - for(int i = 0; i < 8; i++){ - digitalWrite(_DSP_CLK_PIN, LOW); - delayMicroseconds(CLKPW); - digitalWrite(_DSP_CLK_PIN, HIGH); - newButton |= digitalRead(_DSP_TD_PIN)< 99){ - _dspLastRefreshTime = millis(); - digitalWrite(_DSP_LD_PIN, LOW); //start of packet - delayMicroseconds(CLKPW); - _sendBitsToDSP(CMD1, 8); - //end of packet: clock low, make sure LD is low before rising clock then LD - digitalWrite(_DSP_CLK_PIN, LOW); - digitalWrite(_DSP_LD_PIN, LOW); - delayMicroseconds(CLKPW); - digitalWrite(_DSP_CLK_PIN, HIGH); - delayMicroseconds(CLKPW); - digitalWrite(_DSP_LD_PIN, HIGH); - delayMicroseconds(CLKPW); - - digitalWrite(_DSP_LD_PIN, LOW); //start of packet - delayMicroseconds(CLKPW); - _sendBitsToDSP(CMD2, 8); - for(unsigned int i=0; i= 3) { - for (int i = 0; i < len - 2; i++) { - payload[DGT1_IDX] = _getCode(txt.charAt(i)); - payload[DGT2_IDX] = _getCode(txt.charAt(i + 1)); - payload[DGT3_IDX] = _getCode(txt.charAt(i + 2)); - updateDSP(7); - delay(230); - } - } - else if (len == 2) { - payload[DGT1_IDX] = _getCode(' '); - payload[DGT2_IDX] = _getCode(txt.charAt(0)); - payload[DGT3_IDX] = _getCode(txt.charAt(1)); - updateDSP(7); - } - else if (len == 1) { - payload[DGT1_IDX] = _getCode(' '); - payload[DGT2_IDX] = _getCode(' '); - payload[DGT3_IDX] = _getCode(txt.charAt(0)); - updateDSP(7); - } -} - -void DSP::LEDshow() { - //todo: clear payload first... - for(unsigned int y = 0; y < sizeof(payload); y++){ - for(int x = 0; x < 9; x++){ - payload[y] = (1 << x); - updateDSP(7); - delay(200); - } - } -} - -void DSP::begin(int dsp_td_pin, int dsp_clk_pin, int dsp_ld_pin, int dsp_audio_pin) { - _DSP_TD_PIN = dsp_td_pin; - _DSP_CLK_PIN = dsp_clk_pin; - _DSP_LD_PIN = dsp_ld_pin; - _DSP_AUDIO_PIN = dsp_audio_pin; - - pinMode(_DSP_LD_PIN, OUTPUT); - pinMode(_DSP_TD_PIN, INPUT); - pinMode(_DSP_CLK_PIN, OUTPUT); - pinMode(_DSP_AUDIO_PIN, OUTPUT); - digitalWrite(_DSP_LD_PIN, HIGH); //idle high - digitalWrite(_DSP_CLK_PIN, HIGH); //shift on falling, latch on rising - digitalWrite(_DSP_AUDIO_PIN, LOW); -} - -void DSP::playIntro() { - int longnote = 125; - int shortnote = 63; - - tone(_DSP_AUDIO_PIN, NOTE_C7, longnote); - delay(2 * longnote); - tone(_DSP_AUDIO_PIN, NOTE_G6, shortnote); - delay(2 * shortnote); - tone(_DSP_AUDIO_PIN, NOTE_G6, shortnote); - delay(2 * shortnote); - tone(_DSP_AUDIO_PIN, NOTE_A6, longnote); - delay(2 * longnote); - tone(_DSP_AUDIO_PIN, NOTE_G6, longnote); - delay(2 * longnote); - //paus - delay(2 * longnote); - tone(_DSP_AUDIO_PIN, NOTE_B6, longnote); - delay(2 * longnote); - tone(_DSP_AUDIO_PIN, NOTE_C7, longnote); - delay(2 * longnote); - noTone(_DSP_AUDIO_PIN); -} - -//silent beep instead of annoying beeps every time something changes -void DSP::beep() { - //int longnote = 125; - // int shortnote = 63; - // tone(_AUDIO_PIN, NOTE_C6, shortnote); - // delay(shortnote); - // tone(_AUDIO_PIN, NOTE_C7, shortnote); - // delay(shortnote); - // noTone(_AUDIO_PIN); -} - -//new beep for button presses only -void DSP::beep2() { - //int longnote = 125; - int shortnote = 40; - tone(_DSP_AUDIO_PIN, NOTE_D6, shortnote); - delay(shortnote); - tone(_DSP_AUDIO_PIN, NOTE_D7, shortnote); - delay(shortnote); - tone(_DSP_AUDIO_PIN, NOTE_D8, shortnote); - delay(shortnote); - noTone(_DSP_AUDIO_PIN); -} - -BWC::BWC(){} - -void BWC::begin(void){ - _cio.begin(D7, D2, D1); - _dsp.begin(D5, D4, D3, D6); - begin2(); -} - -void BWC::begin( - int cio_td_pin, - int cio_clk_pin, - int cio_ld_pin, - int dsp_td_pin, - int dsp_clk_pin, - int dsp_ld_pin, - int dsp_audio_pin - ) - { - //start CIO and DSP modules - _cio.begin(cio_td_pin, cio_clk_pin, cio_ld_pin); - _dsp.begin(dsp_td_pin, dsp_clk_pin, dsp_ld_pin, dsp_audio_pin); - begin2(); -} - -void BWC::begin2(){ - //Initialize variables - _dspBrightness = 7; //default = max brightness - _cltime = 0; - _ftime = 0; - _uptime = 0; - _pumptime = 0; - _heatingtime = 0; - _airtime = 0; - _jettime = 0; - _timezone = 0; - _price = 1; - _finterval = 30; - _clinterval = 14; - _audio = true; - _restoreStatesOnStart = false; - LittleFS.begin(); - _loadSettings(); - _loadCommandQueue(); - _restoreStates(); - if(_audio) _dsp.playIntro(); - _dsp.LEDshow(); - saveSettingsTimer.attach(3600.0, std::bind(&BWC::saveSettingsFlag, this)); - _tttt = 0; - _tttt_calculated = 0; - _tttt_time0 = DateTime.now()-3600; - _tttt_time1 = DateTime.now(); - _tttt_temp0 = 20; - _tttt_temp1 = 20; -} - -void BWC::stop(){ - _cio.stop(); -} - -void BWC::loop(){ - //feed the dog - ESP.wdtFeed(); - ESP.wdtDisable(); - - _timestamp = DateTime.now(); - - //update DSP payload (memcpy(dest*, source*, len)) - //memcpy(&_dsp.payload[0], &_cio.payload[0], 11); - for(unsigned int i = 0; i < sizeof(_dsp.payload); i++){ - _dsp.payload[i] = _cio.payload[i]; - } - _dsp.updateDSP(_dspBrightness); - _updateTimes(); - //update cio public payload - _cio.loop(); - //manage command queue - _handleCommandQ(); - //queue overrides real buttons - _handleButtonQ(); - if(_saveEventlogNeeded) saveEventlog(); - if(_saveCmdqNeeded) _saveCommandQueue(); - if(_saveSettingsNeeded) saveSettings(); - if(_cio.stateChanged) { - _saveStatesNeeded = true; - _cio.stateChanged = false; - } - if(_saveStatesNeeded) _saveStates(); - //if set target command overshot we need to correct that - if( (_cio.states[TARGET] != _latestTarget) && (_qButtonLen == 0) && (_latestTarget != 0) && (_sliderPrio) ) qCommand(SETTARGET, _latestTarget, 0, 0); - //if target temp is unknown, find out. - if( (_cio.states[TARGET] == 0) && (_qButtonLen == 0) ) qCommand(GETTARGET, (uint32_t)' ', 0, 0); - - //calculate time (in seconds) to target temperature - //these variables can change anytime in the interrupts so copy them first - uint8_t temperature = _cio.states[TEMPERATURE]; - uint8_t target = _cio.states[TARGET]; - //uint8_t unit = _cio.states[UNITSTATE]; - if(temperature != _tttt_temp1){ - _tttt_temp0 = _tttt_temp1; - _tttt_temp1 = temperature; - _tttt_time0 = _tttt_time1; - _tttt_time1 = _timestamp; - } - int dtemp = _tttt_temp1 - _tttt_temp0; //usually 1 or -1 - int dtime = _tttt_time1 - _tttt_time0; - if(dtemp != 0 && abs(dtemp)<2) { //if dtemp is larger we probably have a bad reading - _tttt_calculated = (target-_tttt_temp1) * dtime/dtemp; - } - _tttt = _tttt_calculated - _timestamp + _tttt_time1; - ESP.wdtEnable(0); -} - -//save out debug text to file "debug.txt" on littleFS -void BWC::saveDebugInfo(String s){ - File file = LittleFS.open("debug.txt", "a"); - if (!file) { - Serial.println(F("Failed to save debug.txt")); - return; - } - - DynamicJsonDocument doc(1024); - - // Set the values in the document - doc["timestamp"] = DateTime.format(DateFormatter::SIMPLE); - doc["message"] = s; - // Serialize JSON to file - if (serializeJson(doc, file) == 0) { - Serial.println(F("Failed to write debug.txt")); - } - file.close(); -} - - -int BWC::_CodeToButton(uint16_t val){ - for(unsigned int i = 0; i < sizeof(ButtonCodes)/sizeof(uint16_t); i++){ - if(val == ButtonCodes[i]) return i; - } - return 0; -} - -void BWC::_qButton(uint32_t btn, uint32_t state, uint32_t value, uint32_t maxduration) { - if(_qButtonLen == MAXBUTTONS) return; //maybe textout an error message if queue is full? - _buttonQ[_qButtonLen][0] = btn; - _buttonQ[_qButtonLen][1] = state; - _buttonQ[_qButtonLen][2] = value; - _buttonQ[_qButtonLen][3] = maxduration; - _qButtonLen++; -} - -void BWC::_handleButtonQ(void) { - static uint32_t prevMillis = millis(); - static uint32_t elapsedTime = 0; - - elapsedTime = millis() - prevMillis; - prevMillis = millis(); - if(_qButtonLen > 0){ - // First subtract elapsed time from maxduration - _buttonQ[0][3] -= elapsedTime; - //check if state is as desired, or duration is up. If so - remove row. Else set BTNCODE - if( (_cio.states[_buttonQ[0][1]] == _buttonQ[0][2]) || (_buttonQ[0][3] <= 0) ){ - if(_buttonQ[0][0] == UP || _buttonQ[0][0] == DOWN) maxeffort = false; - //remove row - for(int i = 0; i < _qButtonLen-1; i++){ - _buttonQ[i][0] = _buttonQ[i+1][0]; - _buttonQ[i][1] = _buttonQ[i+1][1]; - _buttonQ[i][2] = _buttonQ[i+1][2]; - _buttonQ[i][3] = _buttonQ[i+1][3]; - } - _qButtonLen--; - _cio.button = ButtonCodes[NOBTN]; - } else { - if(_buttonQ[0][0] == UP || _buttonQ[0][0] == DOWN) maxeffort = true; - //set buttoncode - _cio.button = ButtonCodes[_buttonQ[0][0]]; - } - } else { - static uint16_t prevbtn = ButtonCodes[NOBTN]; - //no queue so let dsp value through - uint16_t pressedButton = _dsp.getButton(); - int index = _CodeToButton(pressedButton); - //if button is not enabled, NOBTN will result (buttoncodes[0]) - _cio.button = ButtonCodes[index * EnabledButtons[index]]; - //prioritize manual temp setting by not competing with the set target command - if (pressedButton == ButtonCodes[UP] || pressedButton == ButtonCodes[DOWN]) _sliderPrio = false; - //do things when a new button is pressed - if(index && (prevbtn == ButtonCodes[NOBTN])) - { - //make noise - if(_audio && EnabledButtons[index]) _dsp.beep2(); - //store pressed buttons sequence - for(int i = 0; i < 3; i++) _btnSequence[i] = _btnSequence[i+1]; - _btnSequence[3] = index; - } - prevbtn = pressedButton; - } -} - -//check for special button sequence -bool BWC::getBtnSeqMatch() -{ - if( _btnSequence[0] == POWER && - _btnSequence[1] == LOCK && - _btnSequence[2] == TIMER && - _btnSequence[3] == POWER) - { - return true; - } - return false; -} - -bool BWC::qCommand(uint32_t cmd, uint32_t val, uint32_t xtime, uint32_t interval) { - //handle special commands - if(cmd == RESETQ){ - _qButtonLen = 0; - _qCommandLen = 0; - _saveCommandQueue(); - return true; - } - - //add parameters to _commandQ[rows][parameter columns] and sort the array on xtime. - int row = _qCommandLen; - if (_qCommandLen == MAXCOMMANDS) return false; - //sort array on xtime - for (int i = 0; i < _qCommandLen; i++) { - if (xtime < _commandQ[i][2]){ - //insert row at [i] - row = i; - break; - } - } - //make room for new row - for (int i = _qCommandLen; i > (row); i--){ - _commandQ[i][0] = _commandQ[i-1][0]; - _commandQ[i][1] = _commandQ[i-1][1]; - _commandQ[i][2] = _commandQ[i-1][2]; - _commandQ[i][3] = _commandQ[i-1][3]; - } - //add new command - _commandQ[row][0] = cmd; - _commandQ[row][1] = val; - _commandQ[row][2] = xtime; - _commandQ[row][3] = interval; - _qCommandLen++; - delay(0); - _saveCommandQueue(); - return true; -} - -void BWC::_handleCommandQ(void) { - bool restartESP = false; - if(_qCommandLen > 0) { - //cmp time with xtime. If more, then execute (adding buttons to buttonQ). - - if (_timestamp >= _commandQ[0][2]){ - _qButton(POWER, POWERSTATE, 1, 5000); //press POWER button until states[POWERSTATE] is 1, max 5000 ms - _qButton(LOCK, LOCKEDSTATE, 0, 5000); //press LOCK button until states[LOCKEDSTATE] is 0 - switch (_commandQ[0][0]) { - case SETTARGET: - { - _latestTarget = _commandQ[0][1]; - //Fiddling with the hardware buttons is ignored while this command executes. - _sliderPrio = true; - //Press up/down appropriate number of times. We need to time this well. - int diff = (int)_commandQ[0][1] - (int)_cio.states[TARGET]; - //First press will just show current target temp - int pushtime = 500; //how fast can we do this??******************* - int releasetime = 300; - _qButton(UP, TARGET, _commandQ[0][1], pushtime); - _qButton(NOBTN, TARGET, _commandQ[0][1], releasetime); - uint32_t updown; - diff<0 ? updown = DOWN : updown = UP; - for(int i = 0; i < abs(diff); i++) - { - _qButton(updown, CHAR1, 0xFF, pushtime); - _qButton(NOBTN, CHAR1, 0xFF, releasetime); - } - //Old method overshoots target too often: - //choose which direction to go (up or down) - // if(_cio.states[TARGET] > _commandQ[0][1]) _qButton(DOWN, TARGET, _commandQ[0][1], 10000); - // if(_cio.states[TARGET] < _commandQ[0][1]) _qButton(UP, TARGET, _commandQ[0][1], 10000); - break; - } - case SETUNIT: - _qButton(UNIT, UNITSTATE, _commandQ[0][1], 5000); - _qButton(NOBTN, CHAR3, 0xFF, 700); - _qButton(UP, CHAR3, _commandQ[0][1], 700); - _latestTarget = 0; //force update - break; - case SETBUBBLES: - _qButton(BUBBLES, BUBBLESSTATE, _commandQ[0][1], 5000); - break; - case SETHEATER: - _qButton(HEAT, HEATSTATE, _commandQ[0][1], 5000); - break; - case SETPUMP: - _qButton(PUMP, PUMPSTATE, _commandQ[0][1], 5000); - break; - case REBOOTESP: - restartESP = true; - break; - case GETTARGET: - _qButton(UP, CHAR3, 32, 500); //ignore desired value and wait for first blink. 32 = ' ' - _qButton(NOBTN, CHAR1, 0xFF, 5000); //block further presses until blinking stops - break; - case RESETTIMES: - _uptime = 0; - _pumptime = 0; - _heatingtime = 0; - _airtime = 0; - _uptime_ms = 0; - _pumptime_ms = 0; - _heatingtime_ms = 0; - _airtime_ms = 0; - _cost = 0; - _saveSettingsNeeded = true; - _cio.dataAvailable = true; - break; - case RESETCLTIMER: - _cltime = _timestamp; - _saveSettingsNeeded = true; - _cio.dataAvailable = true; - break; - case RESETFTIMER: - _ftime = _timestamp; - _saveSettingsNeeded = true; - _cio.dataAvailable = true; - break; - case SETJETS: - _qButton(HYDROJETS, JETSSTATE, _commandQ[0][1], 5000); - break; - case SETBRIGHTNESS: - _dspBrightness = _commandQ[0][1] & 7; - break; - } - //If interval > 0 then append to commandQ with updated xtime. - if(_commandQ[0][3] > 0) qCommand(_commandQ[0][0],_commandQ[0][1],_commandQ[0][2]+_commandQ[0][3],_commandQ[0][3]); - //remove from commandQ and decrease qCommandLen - for(int i = 0; i < _qCommandLen-1; i++){ - _commandQ[i][0] = _commandQ[i+1][0]; - _commandQ[i][1] = _commandQ[i+1][1]; - _commandQ[i][2] = _commandQ[i+1][2]; - _commandQ[i][3] = _commandQ[i+1][3]; - } - _qCommandLen--; - _saveCommandQueue(); - if(restartESP) { - saveSettings(); - ESP.restart(); - } - } - } -} - - -String BWC::getJSONStates() { - // Allocate a temporary JsonDocument - // Don't forget to change the capacity to match your requirements. - // Use arduinojson.org/assistant to compute the capacity. - //feed the dog - ESP.wdtFeed(); - DynamicJsonDocument doc(1024); - - // Set the values in the document - doc["CONTENT"] = "STATES"; - doc["TIME"] = _timestamp; - doc["LCK"] = _cio.states[LOCKEDSTATE]; - doc["PWR"] = _cio.states[POWERSTATE]; - doc["UNT"] = _cio.states[UNITSTATE]; - doc["AIR"] = _cio.states[BUBBLESSTATE]; - doc["GRN"] = _cio.states[HEATGRNSTATE]; - doc["RED"] = _cio.states[HEATREDSTATE]; - doc["FLT"] = _cio.states[PUMPSTATE]; - doc["TGT"] = _cio.states[TARGET]; - doc["TMP"] = _cio.states[TEMPERATURE]; - doc["CH1"] = _cio.states[CHAR1]; - doc["CH2"] = _cio.states[CHAR2]; - doc["CH3"] = _cio.states[CHAR3]; - doc["HJT"] = _cio.states[JETSSTATE]; - doc["BRT"] = _dspBrightness; - - // Serialize JSON to string - String jsonmsg; - if (serializeJson(doc, jsonmsg) == 0) { - jsonmsg = "{\"error\": \"Failed to serialize message\"}"; - } - return jsonmsg; -} - -String BWC::getJSONTimes() { - // Allocate a temporary JsonDocument - // Don't forget to change the capacity to match your requirements. - // Use arduinojson.org/assistant to compute the capacity. - //feed the dog - ESP.wdtFeed(); - DynamicJsonDocument doc(1024); - - // Set the values in the document - doc["CONTENT"] = "TIMES"; - doc["TIME"] = _timestamp; - doc["CLTIME"] = _cltime; - doc["FTIME"] = _ftime; - doc["UPTIME"] = _uptime + _uptime_ms/1000; - doc["PUMPTIME"] = _pumptime + _pumptime_ms/1000; - doc["HEATINGTIME"] = _heatingtime + _heatingtime_ms/1000; - doc["AIRTIME"] = _airtime + _airtime_ms/1000; - doc["JETTIME"] = _jettime + _jettime_ms/1000; - doc["COST"] = _cost; - doc["FINT"] = _finterval; - doc["CLINT"] = _clinterval; - doc["KWH"] = _cost/_price; - doc["TTTT"] = _tttt; - - // Serialize JSON to string - String jsonmsg; - if (serializeJson(doc, jsonmsg) == 0) { - jsonmsg = "{\"error\": \"Failed to serialize message\"}"; - } - return jsonmsg; -} - -String BWC::getJSONSettings(){ - // Allocate a temporary JsonDocument - // Don't forget to change the capacity to match your requirements. - // Use arduinojson.org/assistant to compute the capacity. - //feed the dog - ESP.wdtFeed(); - DynamicJsonDocument doc(1024); - - // Set the values in the document - doc["CONTENT"] = "SETTINGS"; - doc["TIMEZONE"] = _timezone; - doc["PRICE"] = _price; - doc["FINT"] = _finterval; - doc["CLINT"] = _clinterval; - doc["AUDIO"] = _audio; - doc["REBOOTINFO"] = ESP.getResetReason(); - doc["REBOOTTIME"] = DateTime.getBootTime(); - doc["RESTORE"] = _restoreStatesOnStart; - - // Serialize JSON to string - String jsonmsg; - if (serializeJson(doc, jsonmsg) == 0) { - jsonmsg = "{\"error\": \"Failed to serialize message\"}"; - } - return jsonmsg; -} - -void BWC::setJSONSettings(String message){ - //feed the dog - ESP.wdtFeed(); - DynamicJsonDocument doc(1024); - - // Deserialize the JSON document - DeserializationError error = deserializeJson(doc, message); - if (error) { - Serial.println(F("Failed to read config file")); - return; - } - - // Copy values from the JsonDocument to the variables - _timezone = doc["TIMEZONE"]; - _price = doc["PRICE"]; - _finterval = doc["FINT"]; - _clinterval = doc["CLINT"]; - _audio = doc["AUDIO"]; - _restoreStatesOnStart = doc["RESTORE"]; - saveSettings(); -} - -String BWC::getJSONCommandQueue(){ - //feed the dog - ESP.wdtFeed(); - DynamicJsonDocument doc(1024); - // Set the values in the document - doc["LEN"] = _qCommandLen; - for(int i = 0; i < _qCommandLen; i++){ - doc["CMD"][i] = _commandQ[i][0]; - doc["VALUE"][i] = _commandQ[i][1]; - doc["XTIME"][i] = _commandQ[i][2]; - doc["INTERVAL"][i] = _commandQ[i][3]; - } - - // Serialize JSON to file - String jsonmsg; - if (serializeJson(doc, jsonmsg) == 0) { - jsonmsg = "{\"error\": \"Failed to serialize message\"}"; - } - return jsonmsg; -} - -bool BWC::newData(){ - if(maxeffort) return false; - bool result = _cio.dataAvailable; - _cio.dataAvailable = false; - if (result && _audio) _dsp.beep(); - return result; -} - -void BWC::_startNTP() { - // setup this after wifi connected - DateTime.setServer("pool.ntp.org"); - DateTime.begin(); - DateTime.begin(); - int c = 0; - while (!DateTime.isTimeValid()) { - Serial.println(F("Failed to get time from server. Trying again.")); - delay(1000); - //DateTime.setServer("time.cloudflare.com"); - DateTime.begin(); - if (c++ > 5) break; - } - Serial.println(DateTime.format(DateFormatter::SIMPLE)); -} - -void BWC::_loadSettings(){ - File file = LittleFS.open("settings.txt", "r"); - if (!file) { - Serial.println(F("Failed to load settings.txt")); - return; - } - DynamicJsonDocument doc(1024); - - // Deserialize the JSON document - DeserializationError error = deserializeJson(doc, file); - if (error) { - Serial.println(F("Failed to read settings.txt")); - file.close(); - return; - } - - // Copy values from the JsonDocument to the variables - _cltime = doc["CLTIME"]; - _ftime = doc["FTIME"]; - _uptime = doc["UPTIME"]; - _pumptime = doc["PUMPTIME"]; - _heatingtime = doc["HEATINGTIME"]; - _airtime = doc["AIRTIME"]; - _jettime = doc["JETTIME"]; - _timezone = doc["TIMEZONE"]; - _price = doc["PRICE"]; - _finterval = doc["FINT"]; - _clinterval = doc["CLINT"]; - _audio = doc["AUDIO"]; - _restoreStatesOnStart = doc["RESTORE"]; - file.close(); -} - -void BWC::saveSettingsFlag(){ - //ticker fails if duration is more than 71 min. So we use a counter every 60 minutes - if(++_tickerCount >= 3){ - _saveSettingsNeeded = true; - _tickerCount = 0; - } -} - -void BWC::saveSettings(){ - if(maxeffort) { - _saveSettingsNeeded = true; - return; - } - //kill the dog - ESP.wdtDisable(); - _saveSettingsNeeded = false; - File file = LittleFS.open("settings.txt", "w"); - if (!file) { - Serial.println(F("Failed to save settings.txt")); - return; - } - - DynamicJsonDocument doc(1024); - _heatingtime += _heatingtime_ms/1000; - _pumptime += _pumptime_ms/1000; - _airtime += _airtime_ms/1000; - _jettime += _jettime_ms/1000; - _uptime += _uptime_ms/1000; - _heatingtime_ms = 0; - _pumptime_ms = 0; - _airtime_ms = 0; - _uptime_ms = 0; - // Set the values in the document - doc["CLTIME"] = _cltime; - doc["FTIME"] = _ftime; - doc["UPTIME"] = _uptime; - doc["PUMPTIME"] = _pumptime; - doc["HEATINGTIME"] = _heatingtime; - doc["AIRTIME"] = _airtime; - doc["JETTIME"] = _jettime; - doc["TIMEZONE"] = _timezone; - doc["PRICE"] = _price; - doc["FINT"] = _finterval; - doc["CLINT"] = _clinterval; - doc["AUDIO"] = _audio; - doc["SAVETIME"] = DateTime.format(DateFormatter::SIMPLE); - doc["RESTORE"] = _restoreStatesOnStart; - - // Serialize JSON to file - if (serializeJson(doc, file) == 0) { - Serial.println(F("Failed to write json to settings.txt")); - } - file.close(); - DateTime.begin(); - //revive the dog - ESP.wdtEnable(0); - -} - -void BWC::_loadCommandQueue(){ - File file = LittleFS.open("cmdq.txt", "r"); - if (!file) { - Serial.println(F("Failed to read cmdq.txt")); - return; - } - - DynamicJsonDocument doc(1024); - // Deserialize the JSON document - DeserializationError error = deserializeJson(doc, file); - if (error) { - Serial.println(F("Failed to deserialize cmdq.txt")); - file.close(); - return; - } - - // Set the values in the variables - _qCommandLen = doc["LEN"]; - for(int i = 0; i < _qCommandLen; i++){ - _commandQ[i][0] = doc["CMD"][i]; - _commandQ[i][1] = doc["VALUE"][i]; - _commandQ[i][2] = doc["XTIME"][i]; - _commandQ[i][3] = doc["INTERVAL"][i]; - } - - file.close(); -} - -void BWC::_saveCommandQueue(){ - if(maxeffort) { - _saveCmdqNeeded = true; - return; - } - //kill the dog - ESP.wdtDisable(); - - _saveCmdqNeeded = false; - File file = LittleFS.open("cmdq.txt", "w"); - if (!file) { - Serial.println(F("Failed to save cmdq.txt")); - return; - } - - DynamicJsonDocument doc(1024); - - // Set the values in the document - doc["LEN"] = _qCommandLen; - for(int i = 0; i < _qCommandLen; i++){ - doc["CMD"][i] = _commandQ[i][0]; - doc["VALUE"][i] = _commandQ[i][1]; - doc["XTIME"][i] = _commandQ[i][2]; - doc["INTERVAL"][i] = _commandQ[i][3]; - } - - // Serialize JSON to file - if (serializeJson(doc, file) == 0) { - Serial.println(F("Failed to write cmdq.txt")); - } - file.close(); - //revive the dog - ESP.wdtEnable(0); - -} - -void BWC::reloadCommandQueue(){ - _loadCommandQueue(); - return; -} - -void BWC::reloadSettings(){ - _loadSettings(); - return; -} - -void BWC::_saveStates() { - if(maxeffort) { - _saveStatesNeeded = true; - return; - } - //kill the dog - ESP.wdtDisable(); - - _saveStatesNeeded = false; - File file = LittleFS.open("states.txt", "w"); - if (!file) { - Serial.println(F("Failed to save states.txt")); - return; - } - - DynamicJsonDocument doc(1024); - - // Set the values in the document - doc["UNT"] = _cio.states[UNITSTATE]; - doc["HTR"] = _cio.states[HEATSTATE]; - doc["FLT"] = _cio.states[PUMPSTATE]; - - // Serialize JSON to file - if (serializeJson(doc, file) == 0) { - Serial.println(F("Failed to write states.txt")); - } - file.close(); - //revive the dog - ESP.wdtEnable(0); -} - -void BWC::_restoreStates() { - if(!_restoreStatesOnStart) return; - File file = LittleFS.open("states.txt", "r"); - if (!file) { - Serial.println(F("Failed to read states.txt")); - return; - } - DynamicJsonDocument doc(512); - // Deserialize the JSON document - DeserializationError error = deserializeJson(doc, file); - if (error) { - Serial.println(F("Failed to deserialize states.txt")); - file.close(); - return; - } - - uint8_t unt = doc["UNT"]; - uint8_t flt = doc["FLT"]; - uint8_t htr = doc["HTR"]; - qCommand(SETUNIT, unt, DateTime.now()+10, 0); - _cio.states[UNITSTATE] = unt; - qCommand(SETPUMP, flt, DateTime.now()+12, 0); - qCommand(SETHEATER, htr, DateTime.now()+14, 0); - - file.close(); -} - -void BWC::saveEventlog(){ - if(maxeffort) { - _saveEventlogNeeded = true; - return; - } - _saveEventlogNeeded = false; - //kill the dog - ESP.wdtDisable(); - File file = LittleFS.open("eventlog.txt", "a"); - if (!file) { - Serial.println(F("Failed to save eventlog.txt")); - return; - } - - DynamicJsonDocument doc(1024); - - // Set the values in the document - for(unsigned int i = 0; i < sizeof(_cio.states); i++){ - doc[i] = _cio.states[i]; - } - doc["timestamp"] = DateTime.format(DateFormatter::SIMPLE); - - // Serialize JSON to file - if (serializeJson(doc, file) == 0) { - Serial.println(F("Failed to write eventlog.txt")); - } - file.close(); - //revive the dog - ESP.wdtEnable(0); - -} - -void BWC::saveRebootInfo(){ - File file = LittleFS.open("bootlog.txt", "a"); - if (!file) { - Serial.println(F("Failed to save bootlog.txt")); - return; - } - - DynamicJsonDocument doc(1024); - - // Set the values in the document - doc["BOOTINFO"] = ESP.getResetReason() + " " + DateTime.format(DateFormatter::SIMPLE); - - // Serialize JSON to file - if (serializeJson(doc, file) == 0) { - Serial.println(F("Failed to write bootlog.txt")); - } - file.println(); - file.close(); -} - -void BWC::_updateTimes(){ - uint32_t now = millis(); - static uint32_t prevtime = now; - int elapsedtime = now-prevtime; - - prevtime = now; - if (elapsedtime < 0) return; //millis() rollover every 49 days - if(_cio.states[HEATREDSTATE]){ - _heatingtime_ms += elapsedtime; - } - if(_cio.states[PUMPSTATE]){ - _pumptime_ms += elapsedtime; - } - if(_cio.states[BUBBLESSTATE]){ - _airtime_ms += elapsedtime; - } - if(_cio.states[JETSSTATE]){ - _jettime_ms += elapsedtime; - } - _uptime_ms += elapsedtime; - - if(_uptime_ms > 1000000000){ - _heatingtime += _heatingtime_ms/1000; - _pumptime += _pumptime_ms/1000; - _airtime += _airtime_ms/1000; - _jettime += _jettime_ms/1000; - _uptime += _uptime_ms/1000; - _heatingtime_ms = 0; - _pumptime_ms = 0; - _airtime_ms = 0; - _jettime_ms = 0; - _uptime_ms = 0; - } - - _cost = _price*( - (_heatingtime+_heatingtime_ms/1000)/3600.0 * 1900 + //s -> h ->Wh - (_pumptime+_pumptime_ms/1000)/3600.0 * 40 + - (_airtime+_airtime_ms/1000)/3600.0 * 800 + - (_uptime+_uptime_ms/1000)/3600.0 * 2 + - (_jettime+_jettime_ms/1000)/3600.0 * 400 - )/1000.0; //Wh -> kWh -} - -void BWC::print(String txt){ - _dsp.textOut(txt); -} - -uint8_t BWC::getState(int state){ - return _cio.states[state]; -} - -String BWC::getPressedButton(){ - uint16_t btn = _dsp.getButton(); - uint8_t hib, lob; - String s; - hib = (uint8_t)(btn>>8); - lob = (uint8_t)(btn & 0xFF); - s = hib < 16 ? "0" + String(hib, HEX) : String(hib, HEX); - s += lob < 16 ? "0" + String(lob, HEX) : String(lob, HEX); - return s; -} - -String BWC::getButtonName() { - return ButtonNames[_CodeToButton(_dsp.getButton() )]; -} \ No newline at end of file diff --git a/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266_globals.h b/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266_globals.h deleted file mode 100644 index 167c682b..00000000 --- a/Code/6-wire-version/lib/BWC54149E/BWC54149E_8266_globals.h +++ /dev/null @@ -1,156 +0,0 @@ -//only declarations here please! Definitions belong in the cpp-file -#include "model.h" - -#ifndef BWC54149E_8266_globals_H -#define BWC54149E_8266_globals_H - -//LSB -const uint8_t DSP_DIM_BASE = 0x80; -const uint8_t DSP_DIM_ON = 0x8; -const uint8_t CMD1 = B01000000; //normal mode, auto+1 address -const uint8_t CMD2 = B11000000; //start address 00H -const uint8_t CMD3 = DSP_DIM_BASE | DSP_DIM_ON | 7; //full brightness -const uint16_t CLKPW = 50; //clock pulse period in us. clockfreq = 1/2*CLKPW - - -//Payload byte index and bit numbers (see documentation in excel file on github) -//LSB first -const byte DGT1_IDX = 0; -const byte DGT2_IDX = 1; -const byte DGT3_IDX = 2; -const byte TMR2_IDX = 3; -const byte TMR2_BIT = 7; -const byte TMR1_IDX = 3; -const byte TMR1_BIT = 6; -const byte LCK_IDX = 3; -const byte LCK_BIT = 5; -const byte TMRBTNLED_IDX = 3; -const byte TMRBTNLED_BIT = 4; -const byte REDHTR_IDX = 3; -const byte REDHTR_BIT = 2; -const byte GRNHTR_IDX = 3; -const byte GRNHTR_BIT = 3; -const byte AIR_IDX = 3; -const byte AIR_BIT = 1; -const byte FLT_IDX = 4; -const byte FLT_BIT = 2; -const byte C_IDX = 4; -const byte C_BIT = 0; -const byte F_IDX = 4; -const byte F_BIT = 1; -const byte PWR_IDX = 4; //not used. Always considered ON -const byte PWR_BIT = 3; -const byte HJT_IDX = 4; //wild guess if it exists on any model -const byte HJT_BIT = 4; //wild guess - -//8-segment codes. MSB-> .gfedcba <-LSB -const uint8_t CHARCODES[] = { - 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x00, 0x40, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, - 0x7D, 0x74, 0x76, 0x30, 0x0E, 0x70, 0x38, 0x00, 0x54, 0x5C, 0x73, 0x67, 0x50, 0x6D, 0x78, 0x1C, 0x3E, 0x00, 0x6E, 0x5B -}; -const uint8_t CHARS[] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '-', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'H', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y', 'z' -}; - -enum Buttons: byte -{ - NOBTN, - LOCK, - TIMER, - BUBBLES, - UNIT, - HEAT, - PUMP, - DOWN, - UP, - POWER, - HYDROJETS -}; - -//set to zero to disable display buttons. Order as above. -//Example: to disable UNIT and TIMER set 1,1,0,1,0,1,1,1,1,1,1 -const uint8_t EnabledButtons[] = {1,1,1,1,1,1,1,1,1,1,1}; -const String ButtonNames[] = { - "NOBTN", - "LOCK", - "TIMER", - "BUBBLES", - "UNIT", - "HEAT", - "PUMP", - "DOWN", - "UP", - "POWER", //not existing - "HYDROJETS" //not existing -}; - -#ifdef MODEL54149E -const uint16_t ButtonCodes[] = -{ - 0, 1<<7, 1<<6, 1<<5, 1<<4, 1<<3, 1<<2, 1<<1, 1<<0, 1<<8, 1<<9 -}; -const bool HASJETS = false; -#else //keep compiler happy with this dummy -const uint16_t ButtonCodes[] = -{ - 0, 1<<7, 1<<6, 1<<5, 1<<4, 1<<3, 1<<2, 1<<1, 1<<0, 1<<8, 1<<9 -}; -const bool HASJETS = false; -#endif - -enum States: byte -{ - LOCKEDSTATE, - POWERSTATE, - UNITSTATE, - BUBBLESSTATE, - HEATGRNSTATE, - HEATREDSTATE, - HEATSTATE, - PUMPSTATE, - TEMPERATURE, - TARGET, - CHAR1, - CHAR2, - CHAR3, - JETSSTATE -}; - - - -enum Commands: byte -{ - SETTARGET, - SETUNIT, - SETBUBBLES, - SETHEATER, - SETPUMP, - RESETQ, - REBOOTESP, - GETTARGET, - RESETTIMES, - RESETCLTIMER, - RESETFTIMER, - SETJETS, - SETBRIGHTNESS - //play song -}; - -const int MAXCOMMANDS = 11; -const int MAXBUTTONS = 200; - - -//direct port manipulation memory adresses. -#define PIN_OUT 0x60000300 -#define PIN_OUT_SET 0x60000304 -#define PIN_OUT_CLEAR 0x60000308 - -#define PIN_DIR 0x6000030C -#define PIN_DIR_OUTPUT 0x60000310 -#define PIN_DIR_INPUT 0x60000314 - -#define PIN_IN 0x60000318 - - -#endif diff --git a/Code/6-wire-version/platformio.ini b/Code/6-wire-version/platformio.ini index d97716ab..9c6e05bc 100644 --- a/Code/6-wire-version/platformio.ini +++ b/Code/6-wire-version/platformio.ini @@ -35,6 +35,6 @@ upload_speed = 921600 ; Make sure to use a data-USB cable. There is power only cables that wont work. upload_protocol = esptool ; upload_protocol = espota -; upload_port = 192.168.4.121 +; upload_port = layzspa.local ; upload_flags = ; --auth=esp8266 \ No newline at end of file diff --git a/Code/6-wire-version/src/config.h b/Code/6-wire-version/src/config.h index 187b2566..37c09330 100644 --- a/Code/6-wire-version/src/config.h +++ b/Code/6-wire-version/src/config.h @@ -2,7 +2,7 @@ #include #define DEVICE_NAME "layzspa" -#define FW_VERSION "2022-04-11" +#define FW_VERSION "2022-05-24" #define HA_PREFIX "homeassistant" /* diff --git a/Code/6-wire-version/src/main.cpp b/Code/6-wire-version/src/main.cpp index 3d4ce000..e3ce4223 100644 --- a/Code/6-wire-version/src/main.cpp +++ b/Code/6-wire-version/src/main.cpp @@ -1,7 +1,16 @@ #include "main.h" + +// initial stack +char *stack_start; + + void setup() { + // init record of stack + char stack; + stack_start = &stack; + // put your setup code here, to run once: pinMode(solarpin, INPUT_PULLUP); pinMode(myoutputpin, OUTPUT); @@ -231,6 +240,7 @@ String getOtherInfo() doc["IP"] = WiFi.localIP().toString(); doc["SSID"] = WiFi.SSID(); doc["FW"] = FW_VERSION; + doc["MODEL"] = MYMODEL; // Serialize JSON to string if (serializeJson(doc, json) == 0) @@ -401,7 +411,7 @@ void startOTA() ArduinoOTA.onStart([]() { Serial.println(F("OTA > Start")); - bwc.stop(); + stopall(); }); ArduinoOTA.onEnd([]() { Serial.println(F("OTA > End")); @@ -421,7 +431,16 @@ void startOTA() Serial.println(F("OTA > ready")); } - +void stopall() +{ + bwc.stop(); + periodicTimer.detach(); + updateWSTimer.detach(); + LittleFS.end(); + server.stop(); + webSocket.close(); + mqttClient.disconnect(); +} /** * start a web socket server @@ -1302,7 +1321,7 @@ void setupHA() devicedoc["device"]["connections"].add(serialized("[\"mac\",\"" + WiFi.macAddress()+"\"]" )); devicedoc["device"]["identifiers"] = mychipid; devicedoc["device"]["manufacturer"] = F("Visualapproach"); - devicedoc["device"]["model"] = F("NodeMCU 12E"); + devicedoc["device"]["model"] = MYMODEL; devicedoc["device"]["name"] = F("Layzspa WiFi controller"); devicedoc["device"]["sw_version"] = FW_VERSION; @@ -1809,6 +1828,39 @@ void setupHA() doc.clear(); doc.garbageCollect(); + // spa waterjets switch + if(HASJETS) + { + doc["device"] = devicedoc["device"]; + payload = ""; + topic = String(HA_PREFIX) + F("/switch/layzspa_jets/config"); + Serial.println(topic); + doc["name"] = F("Layzspa jets"); + doc["unique_id"] = "switch.layzspa_jets"+mychipid; + doc["state_topic"] = mqttBaseTopic+F("/message"); + doc["command_topic"] = mqttBaseTopic+F("/command"); + doc["value_template"] = F("{{ value_json.HJT }}"); + doc["expire_after"] = 700; + doc["icon"] = F("mdi:hydro-power"); + doc["availability_topic"] = mqttBaseTopic+F("/Status"); + doc["payload_available"] = F("Alive"); + doc["payload_not_available"] = F("Dead"); + doc["payload_on"] = F("{CMD:11,VALUE:true,XTIME:0,INTERVAL:0}"); + doc["payload_off"] = F("{CMD:11,VALUE:false,XTIME:0,INTERVAL:0}"); + doc["state_on"] = 1; + doc["state_off"] = 0; + if (serializeJson(doc, payload) == 0) + { + Serial.println(F("Failed to serialize HA message!")); + return; + } + mqttClient.publish(topic.c_str(), payload.c_str(), true); + mqttClient.loop(); + Serial.println(payload); + doc.clear(); + doc.garbageCollect(); + } + // spa airbubbles switch doc["device"] = devicedoc["device"]; payload = ""; @@ -2005,7 +2057,7 @@ void setupClimate() devicedoc["device"]["connections"].add(serialized("[\"mac\",\"" + WiFi.macAddress()+"\"]" )); devicedoc["device"]["identifiers"] = ESP.getChipId(); devicedoc["device"]["manufacturer"] = "Visualapproach"; - devicedoc["device"]["model"] = "NodeMCU 12E"; + devicedoc["device"]["model"] = MYMODEL; devicedoc["device"]["name"] = "Layzspa WiFi controller"; devicedoc["device"]["sw_version"] = FW_VERSION; @@ -2074,11 +2126,13 @@ void setupClimate() doc["min_temp"] = mintemp; doc["precision"] = 1.0; doc["temperature_unit"] = tempunit; - doc["modes"].add(serialized("\"off\", \"heat\"")); + doc["modes"].add(serialized("\"fan_only\", \"off\", \"heat\"")); + doc["mode_command_topic"] = mqttBaseTopic+F("/command"); + doc["mode_command_template"] = F("{CMD:3,VALUE:{%if value == \"heat\" %}1{% else %}0{% endif %},XTIME:0,INTERVAL:0}"); doc["mode_state_topic"] = mqttBaseTopic+F("/message"); doc["mode_state_template"] = F("{% if value_json.RED == 1 %}heat{% elif value_json.GRN == 1 %}heat{% else %}off{% endif %}"); doc["action_topic"] = mqttBaseTopic+F("/message"); - doc["action_template"] = F("{% if value_json.RED == 1 %}heating{% elif value_json.GRN == 1 %}idle{% else %}off{% endif %}"); + doc["action_template"] = F("{% if value_json.RED == 1 %}heating{% elif value_json.GRN == 1 %}idle{% elif value_json.FLT == 1 %}fan{% else %}off{% endif %}"); doc["temperature_state_topic"] = mqttBaseTopic+F("/message"); doc["temperature_state_template"] = F("{{ value_json.TGT }}"); doc["current_temperature_topic"] = mqttBaseTopic+F("/message"); @@ -2086,8 +2140,8 @@ void setupClimate() doc["temperature_command_topic"] = mqttBaseTopic+F("/command"); doc["temperature_command_template"] = F("{CMD:0,VALUE:{{ value|int }},XTIME:0,INTERVAL:0}"); doc["power_command_topic"] = mqttBaseTopic+F("/command"); - doc["payload_on"] = F("{CMD:3,VALUE:1,XTIME:0,INTERVAL:0}"); - doc["payload_off"] = F("{CMD:3,VALUE:0,XTIME:0,INTERVAL:0}"); + doc["payload_on"] = F("{CMD:4,VALUE:1,XTIME:0,INTERVAL:0}"); + doc["payload_off"] = F("{CMD:4,VALUE:0,XTIME:0,INTERVAL:0}"); doc["availability_topic"] = mqttBaseTopic+F("/Status"); doc["payload_available"] = F("Alive"); doc["payload_not_available"] = F("Dead"); @@ -2099,4 +2153,13 @@ void setupClimate() mqttClient.publish(topic.c_str(), payload.c_str(), true); mqttClient.loop(); Serial.println(payload); -} \ No newline at end of file +} + +void printStackSize() +{ + char stack; + Serial.print (F("stack size ")); + Serial.println (stack_start - &stack); + Serial.print (F("free heap ")); + Serial.println ((long)ESP.getFreeHeap()); +} diff --git a/Code/6-wire-version/src/main.h b/Code/6-wire-version/src/main.h index a782a5b6..d2e07578 100644 --- a/Code/6-wire-version/src/main.h +++ b/Code/6-wire-version/src/main.h @@ -2,13 +2,7 @@ #include #include "config.h" #include "model.h" - -#ifdef MODEL54149E -#include "BWC54149E_8266.h" -#else -#include "BWC_8266.h" -#endif - +#include "BWC_common.h" #include #include #include @@ -87,6 +81,7 @@ void startWiFiConfigPortal(); void startNTP(); void startOTA(); +void stopall(); void startWebSocket(); void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t len); @@ -119,5 +114,6 @@ void mqttCallback(char* topic, byte* payload, unsigned int length); void mqttConnect(); void setupHA(); void setupClimate(); +void printStackSize(); #warning "Don't forget to upload file system also" \ No newline at end of file diff --git a/Code/PCB/Gerber_PCB_Bestway Wi-Fi Controller 2.zip b/Code/PCB/Gerber_PCB_Bestway Wi-Fi Controller 2.zip new file mode 100644 index 00000000..04dc3689 Binary files /dev/null and b/Code/PCB/Gerber_PCB_Bestway Wi-Fi Controller 2.zip differ diff --git a/Code/PCB/Gerber_PCB_Bestway wifi controller_2022-04-15.zip b/Code/PCB/Gerber_PCB_Bestway wifi controller 1.zip similarity index 100% rename from Code/PCB/Gerber_PCB_Bestway wifi controller_2022-04-15.zip rename to Code/PCB/Gerber_PCB_Bestway wifi controller 1.zip diff --git a/README.md b/README.md index 334353c1..d332af9c 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,14 @@ Link to my version of the PCB (use with LLC below): Open the project in editor and download gerber files. Upload them to a PCB factory like [JLCPCB](https://jlcpcb.com/). +You can also find the gerber files in the code/PCB folder here. +There is a v2 also which has slots for both kinds of LLC, and room for low pass filter if needed. I have ordered this but not tested yet!
+New pin numbers. Must edit code before use!
Technical details in the [Documentation](bwc_docs.xlsx). Build instructions and more: [Instructions](Build-instructions-Bestway-WiFi-remote.pdf) +@misterpeee's wife made and shared this case for 3d printing https://github.com/visualapproach/WiFi-remote-for-Bestway-Lay-Z-SPA/discussions/265#discussion-4062382 #### Installation (Alternative) [Eric's PCB](https://easyeda.com/Naesstrom/lay-z-spa_remote) (use with LLC below, choose 1x8 ch or 2x4 ch according to the PCB). diff --git a/pics/pcb v2.png b/pics/pcb v2.png new file mode 100644 index 00000000..eb994603 Binary files /dev/null and b/pics/pcb v2.png differ