fix flash config CRC; add MQTT web page

pull/47/head
Thorsten von Eicken 9 years ago
parent 9e4cbb0dbd
commit d698b94ac6
  1. 7
      esp-link/cgi.c
  2. 89
      esp-link/cgimqtt.c
  3. 8
      esp-link/cgimqtt.h
  4. 19
      esp-link/config.c
  5. 5
      esp-link/config.h
  6. 9
      esp-link/main.c
  7. 99
      html/mqtt.html
  8. 4
      html/style.css
  9. 69
      html/ui.js
  10. 19
      mqtt/mqtt.c

@ -64,8 +64,11 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) {
httpdEndHeaders(connData); httpdEndHeaders(connData);
// construct json response // construct json response
os_sprintf(buff, os_sprintf(buff,
"{\"menu\": [\"Home\", \"/home.html\", \"Wifi\", \"/wifi/wifi.html\"," "{\"menu\": [\"Home\", \"/home.html\", "
"\"\xC2\xB5" "C Console\", \"/console.html\", \"Debug log\", \"/log.html\" ],\n" "\"Wifi\", \"/wifi/wifi.html\","
"\"\xC2\xB5" "C Console\", \"/console.html\", "
"\"REST/MQTT\", \"/mqtt.html\","
"\"Debug log\", \"/log.html\" ],\n"
" \"version\": \"%s\" }", esp_link_version); " \"version\": \"%s\" }", esp_link_version);
httpdSend(connData, buff, -1); httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;

@ -0,0 +1,89 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
// // TCP Client settings
#include <esp8266.h>
#include "cgi.h"
#include "config.h"
#include "cgimqtt.h"
// Cgi to return MQTT settings
int ICACHE_FLASH_ATTR cgiMqttGet(HttpdConnData *connData) {
char buff[2048];
int len;
if (connData->conn==NULL) return HTTPD_CGI_DONE;
len = os_sprintf(buff, "{ "
"\"slip-enable\":%d, "
"\"mqtt-enable\":%d, "
"\"mqtt-status-enable\":%d, "
"\"mqtt-port\":%d, "
"\"mqtt-host\":\"%s\", "
"\"mqtt-client-id\":\"%s\", "
"\"mqtt-username\":\"%s\", "
"\"mqtt-password\":\"%s\", "
"\"mqtt-status-topic\":\"%s\", "
"\"mqtt-state\":\"%s\" }",
flashConfig.slip_enable, flashConfig.mqtt_enable, flashConfig.mqtt_status_enable,
flashConfig.mqtt_port, flashConfig.mqtt_hostname, flashConfig.mqtt_client,
flashConfig.mqtt_username, flashConfig.mqtt_password,
flashConfig.mqtt_status_topic, "connected");
jsonHeader(connData, 200);
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
}
// Cgi to change choice of pin assignments
int ICACHE_FLASH_ATTR cgiMqttSet(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE;
#if 0
// Handle tcp_enable flag
char buff[128];
int len = httpdFindArg(connData->getArgs, "tcp_enable", buff, sizeof(buff));
if (len <= 0) {
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}
flashConfig.tcp_enable = os_strcmp(buff, "true") == 0;
// Handle rssi_enable flag
len = httpdFindArg(connData->getArgs, "rssi_enable", buff, sizeof(buff));
if (len <= 0) {
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}
flashConfig.rssi_enable = os_strcmp(buff, "true") == 0;
// Handle api_key flag
len = httpdFindArg(connData->getArgs, "api_key", buff, sizeof(buff));
if (len < 0) {
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}
buff[sizeof(flashConfig.api_key)-1] = 0; // ensure we don't get an overrun
os_strcpy(flashConfig.api_key, buff);
#endif
if (configSave()) {
httpdStartResponse(connData, 200);
httpdEndHeaders(connData);
} else {
httpdStartResponse(connData, 500);
httpdEndHeaders(connData);
httpdSend(connData, "Failed to save config", -1);
}
return HTTPD_CGI_DONE;
}
int ICACHE_FLASH_ATTR cgiMqtt(HttpdConnData *connData) {
if (connData->requestType == HTTPD_METHOD_GET) {
return cgiMqttGet(connData);
} else if (connData->requestType == HTTPD_METHOD_POST) {
return cgiMqttSet(connData);
} else {
jsonHeader(connData, 404);
return HTTPD_CGI_DONE;
}
}

@ -0,0 +1,8 @@
#ifndef CGIMQTT_H
#define CGIMQTT_H
#include "httpd.h"
int cgiMqtt(HttpdConnData *connData);
#endif

@ -5,9 +5,7 @@
#include <osapi.h> #include <osapi.h>
#include "config.h" #include "config.h"
#include "espfs.h" #include "espfs.h"
#include "crc16.h"
// hack: this from LwIP
extern uint16_t inet_chksum(void *dataptr, uint16_t len);
FlashConfig flashConfig; FlashConfig flashConfig;
FlashConfig flashDefault = { FlashConfig flashDefault = {
@ -20,11 +18,14 @@ FlashConfig flashDefault = {
0, // swap uart (don't by default) 0, // swap uart (don't by default)
1, 0, // tcp_enable, rssi_enable 1, 0, // tcp_enable, rssi_enable
"\0", // api_key "\0", // api_key
0, 0, 0, // slip_enable, mqtt_enable, mqtt_status_enable
1833, // mqtt port
"\0", "\0", "\0", "\0", "\0", // mqtt host, client, user, password, status-topic
}; };
typedef union { typedef union {
FlashConfig fc; FlashConfig fc;
uint8_t block[128]; uint8_t block[1024];
} FlashFull; } FlashFull;
// magic number to recognize thet these are our flash settings as opposed to some random stuff // magic number to recognize thet these are our flash settings as opposed to some random stuff
@ -63,7 +64,7 @@ bool ICACHE_FLASH_ATTR configSave(void) {
ff.fc.crc = 0; ff.fc.crc = 0;
//os_printf("cksum of: "); //os_printf("cksum of: ");
//memDump(&ff, sizeof(ff)); //memDump(&ff, sizeof(ff));
ff.fc.crc = inet_chksum(&ff, sizeof(ff)); ff.fc.crc = crc16_data((unsigned char*)&ff, sizeof(ff), 0);
//os_printf("cksum is %04x\n", ff.fc.crc); //os_printf("cksum is %04x\n", ff.fc.crc);
// write primary with incorrect seq // write primary with incorrect seq
ff.fc.seq = 0xffffffff; ff.fc.seq = 0xffffffff;
@ -121,12 +122,16 @@ static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) {
// check CRC of ff0 // check CRC of ff0
uint16_t crc = ff0->fc.crc; uint16_t crc = ff0->fc.crc;
ff0->fc.crc = 0; ff0->fc.crc = 0;
bool ff0_crc_ok = inet_chksum(ff0, sizeof(FlashFull)) == crc; bool ff0_crc_ok = crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0) == crc;
os_printf("FLASH chk=0x%04x crc=0x%04x full_sz=%d sz=%d\n",
crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0),
crc, sizeof(FlashFull), sizeof(FlashConfig));
// check CRC of ff1 // check CRC of ff1
crc = ff1->fc.crc; crc = ff1->fc.crc;
ff1->fc.crc = 0; ff1->fc.crc = 0;
bool ff1_crc_ok = inet_chksum(ff1, sizeof(FlashFull)) == crc; bool ff1_crc_ok = crc16_data((unsigned char*)ff1, sizeof(FlashFull), 0) == crc;
// decided which we like better // decided which we like better
if (ff0_crc_ok) if (ff0_crc_ok)

@ -16,6 +16,11 @@ typedef struct {
uint8_t swap_uart; // swap uart0 to gpio 13&15 uint8_t swap_uart; // swap uart0 to gpio 13&15
uint8_t tcp_enable, rssi_enable; // TCP client settings uint8_t tcp_enable, rssi_enable; // TCP client settings
char api_key[48]; // RSSI submission API key (Grovestreams for now) char api_key[48]; // RSSI submission API key (Grovestreams for now)
uint8_t slip_enable, mqtt_enable, // SLIP protocol, MQTT client
mqtt_status_enable; // MQTT status reporting
uint16_t mqtt_port;
char mqtt_hostname[32], mqtt_client[48], mqtt_username[32], mqtt_password[32];
char mqtt_status_topic[32];
} FlashConfig; } FlashConfig;
extern FlashConfig flashConfig; extern FlashConfig flashConfig;

@ -17,6 +17,7 @@
#include "cgiwifi.h" #include "cgiwifi.h"
#include "cgipins.h" #include "cgipins.h"
#include "cgitcp.h" #include "cgitcp.h"
#include "cgimqtt.h"
#include "cgiflash.h" #include "cgiflash.h"
#include "auth.h" #include "auth.h"
#include "espfs.h" #include "espfs.h"
@ -90,6 +91,7 @@ HttpdBuiltInUrl builtInUrls[] = {
{ "/wifi/special", cgiWiFiSpecial, NULL }, { "/wifi/special", cgiWiFiSpecial, NULL },
{ "/pins", cgiPins, NULL }, { "/pins", cgiPins, NULL },
{ "/tcpclient", cgiTcp, NULL }, { "/tcpclient", cgiTcp, NULL },
{ "/mqtt", cgiMqtt, NULL },
{ "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem
{ NULL, NULL, NULL } { NULL, NULL, NULL }
@ -124,6 +126,9 @@ extern void app_init(void);
// Main routine to initialize esp-link. // Main routine to initialize esp-link.
void user_init(void) { void user_init(void) {
uart_init(115200, 115200);
logInit(); // must come after init of uart
os_delay_us(10000L);
// get the flash config so we know how to init things // get the flash config so we know how to init things
//configWipe(); // uncomment to reset the config for testing purposes //configWipe(); // uncomment to reset the config for testing purposes
bool restoreOk = configRestore(); bool restoreOk = configRestore();
@ -131,8 +136,8 @@ void user_init(void) {
gpio_init(); gpio_init();
gpio_output_set(0, 0, 0, (1<<15)); // some people tie it GND, gotta ensure it's disabled gpio_output_set(0, 0, 0, (1<<15)); // some people tie it GND, gotta ensure it's disabled
// init UART // init UART
uart_init(flashConfig.baud_rate, 115200); // uart_init(flashConfig.baud_rate, 115200);
logInit(); // must come after init of uart // logInit(); // must come after init of uart
// say hello (leave some time to cause break in TX after boot loader's msg // say hello (leave some time to cause break in TX after boot loader's msg
os_delay_us(10000L); os_delay_us(10000L);
os_printf("\n\n** %s\n", esp_link_version); os_printf("\n\n** %s\n", esp_link_version);

@ -0,0 +1,99 @@
<div id="main">
<div class="header">
<h1>REST &amp; MQTT</h1>
</div>
<div class="content">
<div class="pure-g">
<div class="pure-u-1"><div class="card">
<p>The REST &amp; MQTT support uses the SLIP protocol over the serial port to enable
the attached microcontroller to initiate outbound connections.
The REST support lets the uC initiate simple HTTP requests while the MQTT support
lets it communicate with an MQTT server bidirectionally at QoS 0 thru 2.</p>
<p>The MQTT support is in the form of a built-in client that connects to a server
using parameters set below and stored in esp-link's flash settings. This allows
esp-link to take care of connection parameters and disconnect/reconnect operations.</p>
<p>The MQTT client also supports sending periodic status messages about esp-link itself,
including Wifi RSSI, and free heap memory.</p>
<div class="form-horizontal">
<input type="checkbox" name="slip-enable"/>
<label>Enable SLIP on serial port</label>
</div>
</div></div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>MQTT
<div id="mqtt-spinner" class="spinner spinner-small"></div>
</h1>
<form action="#" id="mqtt-form" class="pure-form" hidden>
<div class="form-horizontal">
<input type="checkbox" name="mqtt-enable"/>
<label>Enable MQTT client</label>
</div>
<div class="form-horizontal">
<label>MQTT client state: </label>
<b id="mqtt-state"></b>
</div>
<br>
<legend>MQTT server settings</legend>
<div class="pure-form-stacked">
<label>Server hostname/ip</label>
<input type="text" name="mqtt-host"/>
<label>Server port/ip</label>
<input type="text" name="mqtt-port"/>
<label>Client ID</label>
<input type="text" name="mqtt-client-id"/>
<label>Username</label>
<input type="text" name="mqtt-username"/>
<label>Password</label>
<input type="password" name="mqtt-password"/>
</div>
<button id="mqtt-button" type="submit" class="pure-button button-primary">
Update server settings!
</button>
</form>
</div>
</div>
<div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>Status reporting
<div id="mqtt-status-spinner" class="spinner spinner-small"></div>
</h1>
<form action="#" id="mqtt-status-form" class="pure-form" hidden>
<div class="form-horizontal">
<input type="checkbox" name="mqtt-status-enable"/>
<label>Enable status reporting via MQTT</label>
</div>
<br>
<legend>Status reporting settings</legend>
<div class="pure-form-stacked">
<label>Topic prefix</label>
<input type="text" name="mqtt-status-topic"/>
</div>
</form>
</div>
<div class="card">
<h1>REST</h1>
<p>REST requests are enabled as soon as SLIP is enabled.
There are no REST-specific settings.</p>
<!-- div id="rest-spinner" class="spinner spinner-small"></div>
<div class="form-horizontal">
<input type="checkbox" name="tcp-enable"/>
<label>Enable REST client</label>
</div -->
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
onLoad(function() {
fetchMqtt();
bnd($("#mqtt-form"), "submit", changeMqtt);
});
</script>
</body></html>

@ -3,6 +3,10 @@ html, button, input, select, textarea, .pure-g [class *= "pure-u"] {
font-family: sans-serif; font-family: sans-serif;
} }
input[type="text"], input[type="password"] {
width: 100%;
}
body { body {
color: #777; color: #777;
} }

@ -374,11 +374,11 @@ function changeTcpClient(e) {
addClass(cb, 'pure-button-disabled'); addClass(cb, 'pure-button-disabled');
ajaxSpin("POST", url, function(resp) { ajaxSpin("POST", url, function(resp) {
removeClass(cb, 'pure-button-disabled'); removeClass(cb, 'pure-button-disabled');
getWifiInfo(); fetchTcpClient();
}, function(s, st) { }, function(s, st) {
showWarning("Error: "+st); showWarning("Error: "+st);
removeClass(cb, 'pure-button-disabled'); removeClass(cb, 'pure-button-disabled');
getWifiInfo(); fetchTcpClient();
}); });
} }
@ -394,4 +394,69 @@ function fetchTcpClient() {
}); });
} }
//===== MQTT cards
function changeMqtt(e) {
e.preventDefault();
var url = "mqtt?1=1";
var i, inputs = $("input");
for (i=0; i<inputs.length; i++) {
if (inputs[i].type != "checkbox")
url += "&" + inputs[i].name + "=" + inputs[i].value;
};
hideWarning();
var cb = $("#mqtt-button");
addClass(cb, 'pure-button-disabled');
ajaxSpin("POST", url, function(resp) {
removeClass(cb, 'pure-button-disabled');
fetchMqtt();
}, function(s, st) {
showWarning("Error: "+st);
removeClass(cb, 'pure-button-disabled');
fetchMqtt();
});
}
function displayMqtt(data) {
Object.keys(data).forEach(function(v) {
el = $("#" + v);
if (el != null) {
if (el.nodeName === "INPUT") el.value = data[v];
else el.innerHTML = data[v];
return;
}
el = document.querySelector('input[name="' + v + '"]');
if (el != null) {
if (el.type == "checkbox") el.checked = data[v] > 0;
else el.value = data[v];
}
});
$("#mqtt-spinner").setAttribute("hidden", "");
$("#mqtt-status-spinner").setAttribute("hidden", "");
$("#mqtt-form").removeAttribute("hidden");
$("#mqtt-status-form").removeAttribute("hidden");
var i, inputs = $("input");
for (i=0; i<inputs.length; i++) {
if (inputs[i].type == "checkbox")
inputs[i].onclick = function() { console.log(this); setMqtt(this.name, this.checked) };
}
}
function fetchMqtt() {
ajaxJson("GET", "/mqtt", displayMqtt, function() {
window.setTimeout(fetchMqtt, 1000);
});
}
function setMqtt(name, v) {
ajaxSpin("POST", "/mqtt?" + name + "=" + (v ? 1 : 0), function() {
showNotification(name + " is now " + (v ? "enabled" : "disabled"));
}, function() {
showNotification("Enable/disable failed");
window.setTimeout(fetchMqtt, 100);
});
}

@ -57,6 +57,11 @@ sint8 espconn_secure_sent(struct espconn *espconn, uint8 *psent, uint16 length)
// max message size for sending (except publish) // max message size for sending (except publish)
#define MQTT_MAX_SHORT_MESSAGE 128 #define MQTT_MAX_SHORT_MESSAGE 128
static char* mqtt_msg_type[] = {
"NULL", "TYPE_CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL", "PUBCOMP",
"SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT", "RESV",
};
// forward declarations // forward declarations
static void mqtt_enq_message(MQTT_Client *client, const uint8_t *data, uint16_t len); static void mqtt_enq_message(MQTT_Client *client, const uint8_t *data, uint16_t len);
static void mqtt_send_message(MQTT_Client* client); static void mqtt_send_message(MQTT_Client* client);
@ -143,8 +148,8 @@ mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) {
pending_msg_id = mqtt_get_id(client->pending_buffer->data, client->pending_buffer->filled); pending_msg_id = mqtt_get_id(client->pending_buffer->data, client->pending_buffer->filled);
} }
os_printf("MQTT: Recv type=%d id=%04X len=%d; Pend type=%d id=%02X\n", os_printf("MQTT: Recv type=%s id=%04X len=%d; Pend type=%s id=%02X\n",
msg_type, msg_id, msg_len, pending_msg_type, pending_msg_id); mqtt_msg_type[msg_type], msg_id, msg_len, mqtt_msg_type[pending_msg_type], pending_msg_id);
switch (msg_type) { switch (msg_type) {
case MQTT_MSG_TYPE_CONNACK: case MQTT_MSG_TYPE_CONNACK:
@ -177,7 +182,7 @@ mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) {
case MQTT_MSG_TYPE_PUBREC: // rec for a publish we sent case MQTT_MSG_TYPE_PUBREC: // rec for a publish we sent
if (pending_msg_type == MQTT_MSG_TYPE_PUBLISH && pending_msg_id == msg_id) { if (pending_msg_type == MQTT_MSG_TYPE_PUBLISH && pending_msg_id == msg_id) {
os_printf("MQTT: Recv PUBREC, cont QoS2 publish\n"); os_printf("MQTT: QoS2 publish cont\n");
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer);
// we need to send PUBREL // we need to send PUBREL
mqtt_msg_pubrel(&client->mqtt_connection, msg_id); mqtt_msg_pubrel(&client->mqtt_connection, msg_id);
@ -202,7 +207,6 @@ mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) {
if (msg_qos == 1) mqtt_msg_puback(&client->mqtt_connection, msg_id); if (msg_qos == 1) mqtt_msg_puback(&client->mqtt_connection, msg_id);
if (msg_qos == 2) mqtt_msg_pubrec(&client->mqtt_connection, msg_id); if (msg_qos == 2) mqtt_msg_pubrec(&client->mqtt_connection, msg_id);
if (msg_qos == 1 || msg_qos == 2) { if (msg_qos == 1 || msg_qos == 2) {
os_printf("MQTT: Queue response QoS: %d\n", msg_qos);
mqtt_enq_message(client, client->mqtt_connection.message.data, mqtt_enq_message(client, client->mqtt_connection.message.data,
client->mqtt_connection.message.length); client->mqtt_connection.message.length);
} }
@ -213,7 +217,7 @@ mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) {
case MQTT_MSG_TYPE_PUBREL: // rel for a rec we sent (originally publish received) case MQTT_MSG_TYPE_PUBREL: // rel for a rec we sent (originally publish received)
if (pending_msg_type == MQTT_MSG_TYPE_PUBREC && pending_msg_id == msg_id) { if (pending_msg_type == MQTT_MSG_TYPE_PUBREC && pending_msg_id == msg_id) {
os_printf("MQTT: Recv PUBREL, cont QoS2 recv\n"); os_printf("MQTT: Cont QoS2 recv\n");
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer);
// we need to send PUBCOMP // we need to send PUBCOMP
mqtt_msg_pubcomp(&client->mqtt_connection, msg_id); mqtt_msg_pubcomp(&client->mqtt_connection, msg_id);
@ -223,7 +227,6 @@ mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) {
break; break;
case MQTT_MSG_TYPE_PINGRESP: case MQTT_MSG_TYPE_PINGRESP:
os_printf("MQTT: Recv PINGRESP\n");
client->keepAliveAckTick = 0; client->keepAliveAckTick = 0;
break; break;
} }
@ -299,7 +302,7 @@ mqtt_timer(void* arg) {
// check whether we need to send a keep-alive message // check whether we need to send a keep-alive message
if (client->keepAliveTick > 0 && --client->keepAliveTick == 0) { if (client->keepAliveTick > 0 && --client->keepAliveTick == 0) {
// timeout: we need to send a ping message // timeout: we need to send a ping message
os_printf("MQTT: Send keepalive to %s:%d\n", client->host, client->port); //os_printf("MQTT: Send keepalive\n");
mqtt_msg_pingreq(&client->mqtt_connection); mqtt_msg_pingreq(&client->mqtt_connection);
PktBuf *buf = PktBuf_New(client->mqtt_connection.message.length); PktBuf *buf = PktBuf_New(client->mqtt_connection.message.length);
os_memcpy(buf->data, client->mqtt_connection.message.data, os_memcpy(buf->data, client->mqtt_connection.message.data,
@ -432,7 +435,7 @@ mqtt_send_message(MQTT_Client* client) {
// get some details about the message // get some details about the message
uint16_t msg_type = mqtt_get_type(buf->data); uint16_t msg_type = mqtt_get_type(buf->data);
uint8_t msg_id = mqtt_get_id(buf->data, buf->filled); uint8_t msg_id = mqtt_get_id(buf->data, buf->filled);
os_printf("MQTT: Send type=%d, id=%04X len=%d\n", msg_type, msg_id, buf->filled); os_printf("MQTT: Send type=%s id=%04X len=%d\n", mqtt_msg_type[msg_type], msg_id, buf->filled);
#if 0 #if 0
for (int i=0; i<buf->filled; i++) { for (int i=0; i<buf->filled; i++) {
if (buf->data[i] >= ' ' && buf->data[i] <= '~') os_printf("%c", buf->data[i]); if (buf->data[i] >= ' ' && buf->data[i] <= '~') os_printf("%c", buf->data[i]);

Loading…
Cancel
Save