Skip to content

Instantly share code, notes, and snippets.

@larsenglund
Created May 15, 2017 18:57
Show Gist options
  • Save larsenglund/b2714e6d9962ade2ed2a2b440a6e353d to your computer and use it in GitHub Desktop.
Save larsenglund/b2714e6d9962ade2ed2a2b440a6e353d to your computer and use it in GitHub Desktop.
const char* websocketPageHead = R"(
<!DOCTYPE html><html><head><title>RakuTemp</title>
<script src='Chart.bundle.min.js'></script>
<style>
html * {
font-family: Arial;
}
</style>
<script>
var waitingForData = false;
var numData = 0;
var dataIdx = 0;
var numChunks = 0;
var chunkIdx = 0;
var numDataAdded = 0;
var MAX_SAMPLES = 2048;
var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']);
connection.binaryType = 'arraybuffer';
var s1 = new Uint16Array(MAX_SAMPLES);
connection.onopen = function () {
connection.send('Connect ' + new Date());
};
connection.onerror = function (error) {
console.log('WebSocket Error ', error);
};
connection.onmessage = function (e) {
console.log('onmsg type: ', typeof(e.data));
console.log('onmsg data: ', e.data);
if (typeof(e.data) == 'object') {
var data = e.data;
var dv = new DataView(data);
/*var idx = dataIdx - numData;
if (idx < 0) idx += MAX_SAMPLES;
for (var n=0; n<numData; n++) {
var tmp = dv.getUint16(idx*2, true);
console.log("idx " + idx + ": " + tmp);
if (++idx >= MAX_SAMPLES) idx = 0;
}*/
for (var n=0; n<MAX_SAMPLES/2; n++) {
s1[chunkIdx++] = dv.getUint16(n*2, true);
}
if (chunkIdx == MAX_SAMPLES) {
console.log('All data received');
var idx = dataIdx - numData;
if (idx < 0) idx += MAX_SAMPLES;
for (var n=0; n<numData; n++) {
var tmp = s1[idx];
console.log("idx " + idx + ": " + tmp);
if (++idx >= MAX_SAMPLES) idx = 0;
}
waitingForData = false;
}
}
if (typeof(e.data) == 'string') {
var tmp = e.data.split(' ');
if (tmp[0] == "D") {
waitingForData = true;
cfg.data.labels = [];
cfg.data.datasets[0].data = [];
dataIdx = parseInt(tmp[1]);
numData = parseInt(tmp[2]);
}
/*else if (tmp.length == 1) {
var _temp = parseInt(tmp[0]);
cfg.data.labels.push(numDataAdded);
cfg.data.datasets[0].data.push(_temp);
numDataAdded++;
if (numDataAdded == numData) {
console.log('All data received!');
myChart.update();
waitingForData = false;
}
}*/
else if (tmp.length == 2) {
//console.log('Server: ', e.data);
var _temp = parseInt(tmp[0]);
var _millis = parseInt(tmp[1]);
if (waitingForData) {
}
else {
console.log(_temp);
document.getElementById('a').innerHTML = _temp;
//document.getElementById('b').value = _millis;
if(cfg.data.datasets[0].data.length == MAX_SAMPLES){
//myChart.removeData();
cfg.data.datasets[0].data.shift();
cfg.data.labels.shift();
}
//myChart.addData([temp], 'RakuTemp');
//cfg.data.labels.push(_millis);
cfg.data.labels.push(numDataAdded);
cfg.data.datasets[0].data.push(_temp);
numDataAdded++;
myChart.update();
}
}
}
};
</script>)";
const char* websocketPageBody = R"(
<center style='font-size: 200%;'>
RakuTemp <b><span id='a'></span></b><br/>
<canvas id='myChart' width='400' height='400'></canvas>
</center><br/>
<script>
var ctx = document.getElementById('myChart');
var cfg = {
type: 'line',
data: {
labels: [],
datasets: [{
radius: 0,
fillColor : 'rgba(252,233,79,0.5)',
strokeColor : 'rgba(82,75,25,1)',
label: 'RakuTemp',
data: []
}]
},
options: {
legend: {
display: false
},
scales:
{
xAxes: [{
display: false
}]
}
}
};
var myChart = new Chart(ctx, cfg);
</script>)";
/** Handle root or redirect to captive portal */
void handleRoot() {
if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
return;
}
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.sendContent(
"<html><head>"
);
server.sendContent(websocketPageHead);
server.sendContent(
"</head><body>"
);
server.sendContent(websocketPageBody);
if (server.client().localIP() == apIP) {
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
} else {
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
}
server.sendContent(
"<p>You may want to <a href='/wifi'>config the wifi connection</a> or <a href='/update'>update the firmware</a></p>"
"</body></html>"
);
server.client().stop(); // Stop is needed because we sent no content length
}
/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
boolean captivePortal() {
if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname)+".local")) {
Serial.print("Request redirected to captive portal");
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.client().stop(); // Stop is needed because we sent no content length
return true;
}
return false;
}
/** Wifi config page handler */
void handleWifi() {
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.sendContent(
"<html><head></head><body>"
"<h1>Wifi config</h1>"
);
if (server.client().localIP() == apIP) {
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
} else {
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
}
server.sendContent(
"\r\n<br />"
"<table><tr><th align='left'>SoftAP config</th></tr>"
);
server.sendContent(String() + "<tr><td>SSID " + String(softAP_ssid) + "</td></tr>");
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.softAPIP()) + "</td></tr>");
server.sendContent(
"</table>"
"\r\n<br />"
"<table><tr><th align='left'>WLAN config</th></tr>"
);
server.sendContent(String() + "<tr><td>SSID " + String(ssid) + "</td></tr>");
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.localIP()) + "</td></tr>");
server.sendContent(
"</table>"
"\r\n<br />"
"<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>"
);
Serial.println("scan start");
int n = WiFi.scanNetworks();
Serial.println("scan done");
if (n > 0) {
for (int i = 0; i < n; i++) {
server.sendContent(String() + "\r\n<tr><td>SSID " + WiFi.SSID(i) + String((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":" *") + " (" + WiFi.RSSI(i) + ")</td></tr>");
}
} else {
server.sendContent(String() + "<tr><td>No WLAN found</td></tr>");
}
server.sendContent(
"</table>"
"\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
"<input type='text' placeholder='network' name='n'/>"
"<br /><input type='password' placeholder='password' name='p'/>"
"<br /><input type='submit' value='Connect/Disconnect'/></form>"
"<p>You may want to <a href='/'>return to the home page</a>.</p>"
"</body></html>"
);
server.client().stop(); // Stop is needed because we sent no content length
}
/** Handle the WLAN save form and redirect to WLAN config page again */
void handleWifiSave() {
Serial.println("wifi save");
server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
server.arg("p").toCharArray(password, sizeof(password) - 1);
server.sendHeader("Location", "wifi", true);
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.client().stop(); // Stop is needed because we sent no content length
saveCredentials();
connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
}
void handleNotFound() {
if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
return;
}
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for ( uint8_t i = 0; i < server.args(); i++ ) {
message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
}
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send ( 404, "text/plain", message );
}
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <ESP8266mDNS.h>
#include <EEPROM.h>
#include <FS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <WebSocketsServer.h> // library: https://github.com/Links2004/arduinoWebSockets
#include <SPI.h>
#include "Adafruit_MAX31855.h"
// Example creating a thermocouple instance with software SPI on any three
// digital IO pins.
#define MAXDO 14
#define MAXCS 12
#define MAXCLK 13
// initialize the Thermocouple
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);
int sample_time_1 = 2000;
int sample_time_2 = 60000;
unsigned int sampleTimestamp1;
unsigned int sampleTimestamp2;
int sample_idx_1 = 0;
int num_samples_1 = 0;
int sample_idx_2 = 0;
int num_samples_2 = 0;
#define MAX_SAMPLES 2048
uint16_t samples2s[MAX_SAMPLES];
uint16_t samples60s[MAX_SAMPLES];
/*
* This example serves a "hello world" on a WLAN and a SoftAP at the same time.
* The SoftAP allow you to configure WLAN parameters at run time. They are not setup in the sketch but saved on EEPROM.
*
* Connect your computer or cell phone to wifi network ESP_ap with password 12345678. A popup may appear and it allow you to go to WLAN config. If it does not then navigate to http://192.168.4.1/wifi and config it there.
* Then wait for the module to connect to your wifi and take note of the WLAN IP it got. Then you can disconnect from ESP_ap and return to your regular WLAN.
*
* Now the ESP8266 is in your network. You can reach it through http://192.168.x.x/ (the IP you took note of) or maybe at http://esp8266.local too.
*
* This is a captive portal because through the softAP it will redirect any http request to http://192.168.4.1/
*/
/* Set these to your desired softAP credentials. They are not configurable at runtime */
const char *softAP_ssid = "RakuTemp";
//const char *softAP_password = "12345678";
/* hostname for mDNS. Should work at least on windows. Try http://esp8266.local */
const char *myHostname = "raku";
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
char ssid[32] = "";
char password[32] = "";
// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;
// Web server
ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdater;
WebSocketsServer webSocket = WebSocketsServer(81);
/* Soft AP network parameters */
IPAddress apIP(192, 168, 4, 1);
IPAddress netMsk(255, 255, 255, 0);
/** Should I connect to WLAN asap? */
boolean connect;
/** Last time I tried to connect to WLAN */
long lastConnectTry = 0;
/** Current WLAN status */
int status = WL_IDLE_STATUS;
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
switch (type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED: {
IPAddress ip = webSocket.remoteIP(num);
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
webSocket.sendTXT(num, String("D") + " " + sample_idx_1 + " " + num_samples_1 + " " + MAX_SAMPLES);
/*int idx = sample_idx_1 - num_samples_1;
if (idx < 0) idx = MAX_SAMPLES + idx;
for (int n=0; n<num_samples_1; n++) {
webSocket.sendTXT(num, String("") + samples2s[idx]);
idx++;
if (idx >= MAX_SAMPLES) {
idx = 0;
}
}*/
Serial.println("Sending binary");
webSocket.sendBIN(num, (uint8_t *) &samples2s[0], MAX_SAMPLES*2/2);
webSocket.sendBIN(num, (uint8_t *) &samples2s[MAX_SAMPLES/2], MAX_SAMPLES*2/2);
Serial.println("Binary sent");
}
break;
case WStype_TEXT:
Serial.printf("[%u] get Text: %s\n", num, payload);
/*if (payload[0] == '#') {
// decode dimmer data
uint32_t ab = (uint32_t)strtol((const char *)&payload[1], NULL, 16);
setBar(barD1, ((ab >> 8) & 0xFF));
setBar(barD2, ((ab >> 0) & 0xFF));
}*/
break;
}
}
void setup() {
delay(1000);
Serial.begin(9600);
Serial.println();
Serial.print("Configuring access point...");
/* You can remove the password parameter if you want the AP to be open. */
WiFi.softAPConfig(apIP, apIP, netMsk);
//WiFi.softAP(softAP_ssid, softAP_password);
WiFi.softAP(softAP_ssid);
delay(500); // Without delay I've seen the IP address blank
Serial.print("AP IP address: ");
Serial.println(WiFi.softAPIP());
/* Setup the DNS server redirecting all the domains to the apIP */
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(DNS_PORT, "*", apIP);
if (!SPIFFS.begin()) {
Serial.println("SPIFFS mount failed");
}
else {
Serial.println("SPIFFS mount succesfull");
}
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
server.on("/", handleRoot);
server.on("/wifi", handleWifi);
server.on("/wifisave", handleWifiSave);
server.on("/generate_204", handleRoot); //Android captive portal. Maybe not needed. Might be handled by notFound handler.
server.on("/fwlink", handleRoot); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
server.onNotFound ( handleNotFound );
server.serveStatic("/", SPIFFS, "/", "max-age=86400");
httpUpdater.setup(&server);
server.begin(); // Web server start
webSocket.begin();
webSocket.onEvent(webSocketEvent);
Serial.print("WEBSOCKETS_MAX_DATA_SIZE: ");
Serial.println(WEBSOCKETS_MAX_DATA_SIZE);
Serial.println("HTTP server started");
loadCredentials(); // Load WLAN credentials from network
connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID
sampleTimestamp1 = millis() + sample_time_1;
pinMode(LED_BUILTIN, OUTPUT);
for (int n=0; n<5; n++) {
digitalWrite(LED_BUILTIN, LOW);
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
}
}
void connectWifi() {
Serial.println("Connecting as wifi client...");
WiFi.disconnect();
WiFi.begin ( ssid, password );
int connRes = WiFi.waitForConnectResult();
Serial.print ( "connRes: " );
Serial.println ( connRes );
}
void loop() {
if (connect) {
Serial.println ( "Connect requested" );
connect = false;
connectWifi();
lastConnectTry = millis();
}
{
int s = WiFi.status();
if (s == 0 && millis() > (lastConnectTry + 10000) ) {
/* If WLAN disconnected and idle try to connect */
/* Don't set retry time too low as retry interfere the softAP operation */
connect = true;
}
if (status != s) { // WLAN status change
Serial.print ( "Status: " );
Serial.println ( s );
status = s;
if (s == WL_CONNECTED) {
/* Just connected to WLAN */
Serial.println ( "" );
Serial.print ( "Connected to " );
Serial.println ( ssid );
Serial.print ( "IP address: " );
Serial.println ( WiFi.localIP() );
// Setup MDNS responder
if (!MDNS.begin(myHostname)) {
Serial.println("Error setting up MDNS responder!");
} else {
Serial.println("mDNS responder started");
// Add service to MDNS-SD
MDNS.addService("http", "tcp", 80);
}
} else if (s == WL_NO_SSID_AVAIL) {
WiFi.disconnect();
}
}
}
// Do work:
if (millis() > sampleTimestamp1) {
// basic readout test, just print the current temp
Serial.print("Internal Temp = ");
Serial.println(thermocouple.readInternal());
double c = thermocouple.readCelsius();
unsigned int c_millis = millis();
samples2s[sample_idx_1] = (uint16_t)c;
num_samples_1++;
if (num_samples_1 > MAX_SAMPLES) num_samples_1 = MAX_SAMPLES;
sample_idx_1++;
if (sample_idx_1 >= MAX_SAMPLES) sample_idx_1 = 0;
if (isnan(c)) {
Serial.println("Something wrong with thermocouple!");
} else {
Serial.print("C = ");
Serial.println(c);
webSocket.broadcastTXT(String("") + c + " " + c_millis);
}
sampleTimestamp1 = millis() + sample_time_1;
}
//DNS
dnsServer.processNextRequest();
//HTTP
server.handleClient();
//Websockets
webSocket.loop();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment