mirror of https://github.com/jeelabs/esp-link.git
commit
d092075818
@ -1,3 +0,0 @@ |
||||
[submodule "lib/heatshrink"] |
||||
path = lib/heatshrink |
||||
url = https://github.com/atomicobject/heatshrink.git |
@ -0,0 +1,211 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
//
|
||||
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
|
||||
|
||||
#include "esp8266.h" |
||||
#include "cmd.h" |
||||
#include "crc16.h" |
||||
#include "uart.h" |
||||
|
||||
extern const CmdList commands[]; |
||||
|
||||
//===== ESP -> Serial responses
|
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
CMD_ProtoWrite(uint8_t data) { |
||||
switch(data){ |
||||
case SLIP_START: |
||||
case SLIP_END: |
||||
case SLIP_REPL: |
||||
uart0_write_char(SLIP_REPL); |
||||
uart0_write_char(SLIP_ESC(data)); |
||||
break; |
||||
default: |
||||
uart0_write_char(data); |
||||
} |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
CMD_ProtoWriteBuf(uint8_t *data, short len) { |
||||
while (len--) CMD_ProtoWrite(*data++); |
||||
} |
||||
|
||||
// Start a response, returns the partial CRC
|
||||
uint16_t ICACHE_FLASH_ATTR |
||||
CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc) { |
||||
uint16_t crc = 0; |
||||
|
||||
uart0_write_char(SLIP_START); |
||||
CMD_ProtoWriteBuf((uint8_t*)&cmd, 2); |
||||
crc = crc16_data((uint8_t*)&cmd, 2, crc); |
||||
CMD_ProtoWriteBuf((uint8_t*)&callback, 4); |
||||
crc = crc16_data((uint8_t*)&callback, 4, crc); |
||||
CMD_ProtoWriteBuf((uint8_t*)&_return, 4); |
||||
crc = crc16_data((uint8_t*)&_return, 4, crc); |
||||
CMD_ProtoWriteBuf((uint8_t*)&argc, 2); |
||||
crc = crc16_data((uint8_t*)&argc, 2, crc); |
||||
return crc; |
||||
} |
||||
|
||||
// Adds data to a response, returns the partial CRC
|
||||
uint16_t ICACHE_FLASH_ATTR |
||||
CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len) { |
||||
short pad_len = len+3 - (len+3)%4; // round up to multiple of 4
|
||||
CMD_ProtoWriteBuf((uint8_t*)&pad_len, 2); |
||||
crc_in = crc16_data((uint8_t*)&pad_len, 2, crc_in); |
||||
|
||||
CMD_ProtoWriteBuf(data, len); |
||||
crc_in = crc16_data(data, len, crc_in); |
||||
|
||||
if (pad_len > len) { |
||||
uint32_t temp = 0; |
||||
CMD_ProtoWriteBuf((uint8_t*)&temp, pad_len-len); |
||||
crc_in = crc16_data((uint8_t*)&temp, pad_len-len, crc_in); |
||||
} |
||||
|
||||
return crc_in; |
||||
} |
||||
|
||||
// Ends a response
|
||||
void ICACHE_FLASH_ATTR |
||||
CMD_ResponseEnd(uint16_t crc) { |
||||
CMD_ProtoWriteBuf((uint8_t*)&crc, 2); |
||||
uart0_write_char(SLIP_END); |
||||
} |
||||
|
||||
//===== serial -> ESP commands
|
||||
|
||||
// Execute a parsed command
|
||||
static uint32_t ICACHE_FLASH_ATTR |
||||
CMD_Exec(const CmdList *scp, CmdPacket *packet) { |
||||
uint16_t crc = 0; |
||||
// Iterate through the command table and call the appropriate function
|
||||
while (scp->sc_function != NULL) { |
||||
if(scp->sc_name == packet->cmd) { |
||||
//os_printf("CMD: Dispatching cmd=%d\n", packet->cmd);
|
||||
// call command function
|
||||
uint32_t ret = scp->sc_function(packet); |
||||
// if requestor asked for a response, send it
|
||||
if (packet->_return){ |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD: Response: 0x%lx, cmd: %d\r\n", ret, packet->cmd); |
||||
#endif |
||||
crc = CMD_ResponseStart(packet->cmd, 0, ret, 0); |
||||
CMD_ResponseEnd(crc); |
||||
} else { |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD: no response (%lu)\n", packet->_return); |
||||
#endif |
||||
} |
||||
return ret; |
||||
} |
||||
scp++; |
||||
} |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD: cmd=%d not found\n", packet->cmd); |
||||
#endif |
||||
return 0; |
||||
} |
||||
|
||||
char *cmd_names[] = { |
||||
"NULL", "RESET", "IS_READY", "WIFI_CONNECT", |
||||
"MQTT_SETUP", "MQTT_CONNECT", "MQTT_DISCONNECT", |
||||
"MQTT_PUBLISH", "MQTT_SUBSCRIBE", "MQTT_LWT", "MQTT_EVENTS", |
||||
"REST_SETUP", "REST_REQUEST", "REST_SETHEADER", "REST_EVENTS", |
||||
"CB_ADD", "CB_EVENTS", |
||||
}; |
||||
|
||||
// Parse a packet and print info about it
|
||||
void ICACHE_FLASH_ATTR |
||||
CMD_parse_packet(uint8_t *buf, short len) { |
||||
// minimum command length
|
||||
if (len < 12) return; |
||||
|
||||
// init pointers into buffer
|
||||
CmdPacket *packet = (CmdPacket*)buf; |
||||
uint8_t *data_ptr = (uint8_t*)&packet->args; |
||||
uint8_t *data_limit = data_ptr+len; |
||||
#ifdef CMD_DBG |
||||
uint16_t argn = 0; |
||||
os_printf("CMD: cmd=%d(%s) argc=%d cb=%p ret=%lu\n", |
||||
packet->cmd, cmd_names[packet->cmd], packet->argc, (void *)packet->callback, packet->_return); |
||||
#endif |
||||
|
||||
#if 0 |
||||
// print out arguments
|
||||
uint16_t argc = packet->argc; |
||||
while (data_ptr+2 < data_limit && argc--) { |
||||
short l = *(uint16_t*)data_ptr; |
||||
os_printf("CMD: arg[%d] len=%d:", argn++, l); |
||||
data_ptr += 2; |
||||
while (data_ptr < data_limit && l--) { |
||||
os_printf(" %02X", *data_ptr++); |
||||
} |
||||
os_printf("\n"); |
||||
} |
||||
#endif |
||||
|
||||
if (data_ptr <= data_limit) { |
||||
CMD_Exec(commands, packet); |
||||
} else { |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD: packet length overrun, parsing arg %d\n", argn-1); |
||||
#endif |
||||
} |
||||
} |
||||
|
||||
//===== Helpers to parse a command packet
|
||||
|
||||
// Fill out a CmdRequest struct given a CmdPacket
|
||||
void ICACHE_FLASH_ATTR |
||||
CMD_Request(CmdRequest *req, CmdPacket* cmd) { |
||||
req->cmd = cmd; |
||||
req->arg_num = 0; |
||||
req->arg_ptr = (uint8_t*)&cmd->args; |
||||
} |
||||
|
||||
// Return the number of arguments given a command struct
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
CMD_GetArgc(CmdRequest *req) { |
||||
return req->cmd->argc; |
||||
} |
||||
|
||||
// Copy the next argument from a command structure into the data pointer, returns 0 on success
|
||||
// -1 on error
|
||||
int32_t ICACHE_FLASH_ATTR |
||||
CMD_PopArg(CmdRequest *req, void *data, uint16_t len) { |
||||
uint16_t length; |
||||
|
||||
if (req->arg_num >= req->cmd->argc) |
||||
return -1; |
||||
|
||||
length = *(uint16_t*)req->arg_ptr; |
||||
if (length != len) return -1; // safety check
|
||||
|
||||
req->arg_ptr += 2; |
||||
os_memcpy(data, req->arg_ptr, length); |
||||
req->arg_ptr += length; |
||||
|
||||
req->arg_num ++; |
||||
return 0; |
||||
} |
||||
|
||||
// Skip the next argument
|
||||
void ICACHE_FLASH_ATTR |
||||
CMD_SkipArg(CmdRequest *req) { |
||||
uint16_t length; |
||||
|
||||
if (req->arg_num >= req->cmd->argc) return; |
||||
|
||||
length = *(uint16_t*)req->arg_ptr; |
||||
|
||||
req->arg_ptr += 2; |
||||
req->arg_ptr += length; |
||||
req->arg_num ++; |
||||
} |
||||
|
||||
// Return the length of the next argument
|
||||
uint16_t ICACHE_FLASH_ATTR |
||||
CMD_ArgLen(CmdRequest *req) { |
||||
return *(uint16_t*)req->arg_ptr; |
||||
} |
@ -0,0 +1,106 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
//
|
||||
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
|
||||
|
||||
#ifndef CMD_H |
||||
#define CMD_H |
||||
#include <esp8266.h> |
||||
|
||||
// Escape chars used by tuanpmt, dunno why he didn't use std ones...
|
||||
#define SLIP_START 0x7E |
||||
#define SLIP_END 0x7F |
||||
#define SLIP_REPL 0x7D |
||||
#define SLIP_ESC(x) (x ^ 0x20) |
||||
|
||||
#if 0 |
||||
// Proper SLIP escape chars from RFC
|
||||
#define SLIP_END 0300 // indicates end of packet
|
||||
#define SLIP_ESC 0333 // indicates byte stuffing
|
||||
#define SLIP_ESC_END 0334 // ESC ESC_END means END data byte
|
||||
#define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte
|
||||
#endif |
||||
|
||||
typedef struct __attribute__((__packed__)) { |
||||
uint16_t len; // length of data
|
||||
uint8_t data[0]; // really data[len]
|
||||
} CmdArg; |
||||
|
||||
typedef struct __attribute__((__packed__)) { |
||||
uint16_t cmd; // command to perform, from CmdName enum
|
||||
uint32_t callback; // callback pointer to embed in response
|
||||
uint32_t _return; // return value to embed in response (?)
|
||||
uint16_t argc; // number of arguments to command
|
||||
CmdArg args[0]; // really args[argc]
|
||||
} CmdPacket; |
||||
|
||||
typedef struct { |
||||
CmdPacket *cmd; // command packet header
|
||||
uint32_t arg_num; // number of args parsed
|
||||
uint8_t *arg_ptr; // pointer to ??
|
||||
} CmdRequest; |
||||
|
||||
typedef enum { |
||||
CMD_NULL = 0, |
||||
CMD_RESET, // reset esp (not honored in this implementation)
|
||||
CMD_IS_READY, // health-check
|
||||
CMD_WIFI_CONNECT, // (3) connect to AP (not honored in this implementation)
|
||||
CMD_MQTT_SETUP, |
||||
CMD_MQTT_CONNECT, |
||||
CMD_MQTT_DISCONNECT, |
||||
CMD_MQTT_PUBLISH, |
||||
CMD_MQTT_SUBSCRIBE, |
||||
CMD_MQTT_LWT, |
||||
CMD_MQTT_EVENTS, |
||||
CMD_REST_SETUP, // (11)
|
||||
CMD_REST_REQUEST, |
||||
CMD_REST_SETHEADER, |
||||
CMD_REST_EVENTS, |
||||
CMD_CB_ADD, // 15
|
||||
CMD_CB_EVENTS |
||||
} CmdName; |
||||
|
||||
typedef uint32_t (*cmdfunc_t)(CmdPacket *cmd); |
||||
|
||||
typedef struct { |
||||
CmdName sc_name; |
||||
cmdfunc_t sc_function; |
||||
} CmdList; |
||||
|
||||
#define CMD_CBNLEN 16 |
||||
typedef struct { |
||||
char name[CMD_CBNLEN]; |
||||
uint32_t callback; |
||||
} cmdCallback; |
||||
|
||||
// Used by slip protocol to cause parsing of a received packet
|
||||
void CMD_parse_packet(uint8_t *buf, short len); |
||||
|
||||
// Return the info about a callback to the attached uC by name, these are callbacks that the
|
||||
// attached uC registers using the ADD_SENSOR command
|
||||
cmdCallback* CMD_GetCbByName(char* name); |
||||
|
||||
// Responses
|
||||
|
||||
// Start a response, returns the partial CRC
|
||||
uint16_t CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc); |
||||
// Adds data to a response, returns the partial CRC
|
||||
uint16_t CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len); |
||||
// Ends a response
|
||||
void CMD_ResponseEnd(uint16_t crc); |
||||
|
||||
//void CMD_Response(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc, CmdArg* args[]);
|
||||
|
||||
// Requests
|
||||
|
||||
// Fill out a CmdRequest struct given a CmdPacket
|
||||
void CMD_Request(CmdRequest *req, CmdPacket* cmd); |
||||
// Return the number of arguments given a request
|
||||
uint32_t CMD_GetArgc(CmdRequest *req); |
||||
// Return the length of the next argument
|
||||
uint16_t CMD_ArgLen(CmdRequest *req); |
||||
// Copy next arg from request into the data pointer, returns 0 on success, -1 on error
|
||||
int32_t CMD_PopArg(CmdRequest *req, void *data, uint16_t len); |
||||
// Skip next arg
|
||||
void CMD_SkipArg(CmdRequest *req); |
||||
|
||||
#endif |
@ -0,0 +1,168 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
//
|
||||
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
|
||||
|
||||
#include "esp8266.h" |
||||
#include "cmd.h" |
||||
#include <cgiwifi.h> |
||||
#ifdef MQTT |
||||
#include <mqtt_cmd.h> |
||||
#endif |
||||
#ifdef REST |
||||
#include <rest.h> |
||||
#endif |
||||
|
||||
static uint32_t CMD_Null(CmdPacket *cmd); |
||||
static uint32_t CMD_IsReady(CmdPacket *cmd); |
||||
static uint32_t CMD_Reset(CmdPacket *cmd); |
||||
static uint32_t CMD_WifiConnect(CmdPacket *cmd); |
||||
static uint32_t CMD_AddCallback(CmdPacket *cmd); |
||||
|
||||
// keep track of last status sent to uC so we can notify it when it changes
|
||||
static uint8_t lastWifiStatus = wifiIsDisconnected; |
||||
static bool wifiCbAdded = false; |
||||
|
||||
// Command dispatch table for serial -> ESP commands
|
||||
const CmdList commands[] = { |
||||
{CMD_NULL, CMD_Null}, |
||||
{CMD_RESET, CMD_Reset}, |
||||
{CMD_IS_READY, CMD_IsReady}, |
||||
{CMD_WIFI_CONNECT, CMD_WifiConnect}, |
||||
#ifdef MQTT |
||||
{CMD_MQTT_SETUP, MQTTCMD_Setup}, |
||||
{CMD_MQTT_CONNECT, MQTTCMD_Connect}, |
||||
{CMD_MQTT_DISCONNECT, MQTTCMD_Disconnect}, |
||||
{CMD_MQTT_PUBLISH, MQTTCMD_Publish}, |
||||
{CMD_MQTT_SUBSCRIBE , MQTTCMD_Subscribe}, |
||||
{CMD_MQTT_LWT, MQTTCMD_Lwt}, |
||||
#endif |
||||
#ifdef REST |
||||
{CMD_REST_SETUP, REST_Setup}, |
||||
{CMD_REST_REQUEST, REST_Request}, |
||||
{CMD_REST_SETHEADER, REST_SetHeader}, |
||||
#endif |
||||
{CMD_CB_ADD, CMD_AddCallback}, |
||||
{CMD_NULL, NULL} |
||||
}; |
||||
|
||||
// WifiCb plus 10 for sensors
|
||||
#define MAX_CALLBACKS 12 |
||||
cmdCallback callbacks[MAX_CALLBACKS]; // cleared in CMD_Reset
|
||||
|
||||
// Command handler for IsReady (healthcheck) command
|
||||
static uint32_t ICACHE_FLASH_ATTR |
||||
CMD_IsReady(CmdPacket *cmd) { |
||||
return 1; |
||||
} |
||||
|
||||
// Command handler for Null command
|
||||
static uint32_t ICACHE_FLASH_ATTR |
||||
CMD_Null(CmdPacket *cmd) { |
||||
return 1; |
||||
} |
||||
|
||||
// Command handler for Reset command, this was originally to reset the ESP but we don't want to
|
||||
// do that is esp-link. It is still good to clear any information the ESP has about the attached
|
||||
// uC.
|
||||
static uint32_t ICACHE_FLASH_ATTR |
||||
CMD_Reset(CmdPacket *cmd) { |
||||
// clear callbacks table
|
||||
os_memset(callbacks, 0, sizeof(callbacks)); |
||||
return 1; |
||||
} |
||||
|
||||
static uint32_t ICACHE_FLASH_ATTR |
||||
CMD_AddCb(char* name, uint32_t cb) { |
||||
for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { |
||||
//os_printf("CMD_AddCb: index %d name=%s cb=%p\n", i, callbacks[i].name,
|
||||
// (void *)callbacks[i].callback);
|
||||
// find existing callback or add to the end
|
||||
if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0 || callbacks[i].name[0] == '\0') { |
||||
os_strncpy(callbacks[i].name, name, sizeof(callbacks[i].name)); |
||||
callbacks[i].name[CMD_CBNLEN-1] = 0; // strncpy doesn't null terminate
|
||||
callbacks[i].callback = cb; |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD_AddCb: cb %s added at index %d\n", callbacks[i].name, i); |
||||
#endif |
||||
return 1; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
cmdCallback* ICACHE_FLASH_ATTR |
||||
CMD_GetCbByName(char* name) { |
||||
for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { |
||||
//os_printf("CMD_GetCbByName: index %d name=%s cb=%p\n", i, callbacks[i].name,
|
||||
// (void *)callbacks[i].callback);
|
||||
// if callback doesn't exist or it's null
|
||||
if (os_strncmp(callbacks[i].name, name, CMD_CBNLEN) == 0) { |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD_GetCbByName: cb %s found at index %d\n", name, i); |
||||
#endif |
||||
return &callbacks[i]; |
||||
} |
||||
} |
||||
os_printf("CMD_GetCbByName: cb %s not found\n", name); |
||||
return 0; |
||||
} |
||||
|
||||
// Callback from wifi subsystem to notify us of status changes
|
||||
static void ICACHE_FLASH_ATTR |
||||
CMD_WifiCb(uint8_t wifiStatus) { |
||||
if (wifiStatus != lastWifiStatus){ |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD_WifiCb: wifiStatus=%d\n", wifiStatus); |
||||
#endif |
||||
lastWifiStatus = wifiStatus; |
||||
cmdCallback *wifiCb = CMD_GetCbByName("wifiCb"); |
||||
if ((uint32_t)wifiCb->callback != -1) { |
||||
uint8_t status = wifiStatus == wifiGotIP ? 5 : 1; |
||||
uint16_t crc = CMD_ResponseStart(CMD_WIFI_CONNECT, (uint32_t)wifiCb->callback, 0, 1); |
||||
crc = CMD_ResponseBody(crc, (uint8_t*)&status, 1); |
||||
CMD_ResponseEnd(crc); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Command handler for Wifi connect command
|
||||
static uint32_t ICACHE_FLASH_ATTR |
||||
CMD_WifiConnect(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
if(cmd->argc != 2 || cmd->callback == 0) |
||||
return 0; |
||||
|
||||
if (!wifiCbAdded) { |
||||
wifiAddStateChangeCb(CMD_WifiCb); // register our callback with wifi subsystem
|
||||
wifiCbAdded = true; |
||||
} |
||||
CMD_AddCb("wifiCb", (uint32_t)cmd->callback); // save the MCU's callback
|
||||
lastWifiStatus = 0xff; // set to invalid value so we immediately send status cb in all cases
|
||||
CMD_WifiCb(wifiState); |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
// Command handler to add a callback to the named-callbacks list, this is for a callback to the uC
|
||||
static uint32_t ICACHE_FLASH_ATTR |
||||
CMD_AddCallback(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
if (cmd->argc != 1 || cmd->callback == 0) |
||||
return 0; |
||||
|
||||
char name[16]; |
||||
uint16_t len; |
||||
|
||||
// get the sensor name
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 15) return 0; // max size of name is 15 characters
|
||||
if (CMD_PopArg(&req, (uint8_t *)name, len)) return 0; |
||||
name[len] = 0; |
||||
#ifdef CMD_DBG |
||||
os_printf("CMD_AddCallback: name=%s\n", name); |
||||
#endif |
||||
|
||||
return CMD_AddCb(name, (uint32_t)cmd->callback); // save the sensor callback
|
||||
} |
@ -0,0 +1,163 @@ |
||||
/*
|
||||
Some random cgi routines. |
||||
*/ |
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------------------- |
||||
* "THE BEER-WARE LICENSE" (Revision 42): |
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain |
||||
* this notice you can do whatever you want with this stuff. If we meet some day, |
||||
* and you think this stuff is worth it, you can buy me a beer in return. |
||||
* ---------------------------------------------------------------------------- |
||||
* Heavily modified and enhanced by Thorsten von Eicken in 2015 |
||||
* ---------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
|
||||
#include <esp8266.h> |
||||
#include "cgi.h" |
||||
|
||||
void noCacheHeaders(HttpdConnData *connData, int code) { |
||||
httpdStartResponse(connData, code); |
||||
httpdHeader(connData, "Cache-Control", "no-cache, no-store, must-revalidate"); |
||||
httpdHeader(connData, "Pragma", "no-cache"); |
||||
httpdHeader(connData, "Expires", "0"); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
jsonHeader(HttpdConnData *connData, int code) { |
||||
noCacheHeaders(connData, code); |
||||
httpdHeader(connData, "Content-Type", "application/json"); |
||||
httpdEndHeaders(connData); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
errorResponse(HttpdConnData *connData, int code, char *message) { |
||||
noCacheHeaders(connData, code); |
||||
httpdEndHeaders(connData); |
||||
httpdSend(connData, message, -1); |
||||
#ifdef CGI_DBG |
||||
os_printf("HTTP %d error response: \"%s\"\n", code, message); |
||||
#endif |
||||
} |
||||
|
||||
// look for the HTTP arg 'name' and store it at 'config' with max length 'max_len' (incl
|
||||
// terminating zero), returns -1 on error, 0 if not found, 1 if found and OK
|
||||
int8_t ICACHE_FLASH_ATTR |
||||
getStringArg(HttpdConnData *connData, char *name, char *config, int max_len) { |
||||
char buff[128]; |
||||
int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); |
||||
if (len < 0) return 0; // not found, skip
|
||||
if (len >= max_len) { |
||||
os_sprintf(buff, "Value for %s too long (%d > %d allowed)", name, len, max_len-1); |
||||
errorResponse(connData, 400, buff); |
||||
return -1; |
||||
} |
||||
strcpy(config, buff); |
||||
return 1; |
||||
} |
||||
|
||||
int8_t ICACHE_FLASH_ATTR |
||||
getBoolArg(HttpdConnData *connData, char *name, bool*config) { |
||||
char buff[64]; |
||||
int len = httpdFindArg(connData->getArgs, name, buff, sizeof(buff)); |
||||
if (len < 0) return 0; // not found, skip
|
||||
|
||||
if (strcmp(buff, "1") == 0 || strcmp(buff, "true") == 0) { |
||||
*config = true; |
||||
return 1; |
||||
} |
||||
if (strcmp(buff, "0") == 0 || strcmp(buff, "false") == 0) { |
||||
*config = false; |
||||
return 1; |
||||
} |
||||
os_sprintf(buff, "Invalid value for %s", name); |
||||
errorResponse(connData, 400, buff); |
||||
return -1; |
||||
} |
||||
|
||||
uint8_t ICACHE_FLASH_ATTR |
||||
UTILS_StrToIP(const char* str, void *ip){ |
||||
/* The count of the number of bytes processed. */ |
||||
int i; |
||||
/* A pointer to the next digit to process. */ |
||||
const char * start; |
||||
|
||||
start = str; |
||||
for (i = 0; i < 4; i++) { |
||||
/* The digit being processed. */ |
||||
char c; |
||||
/* The value of this byte. */ |
||||
int n = 0; |
||||
while (1) { |
||||
c = *start; |
||||
start++; |
||||
if (c >= '0' && c <= '9') { |
||||
n *= 10; |
||||
n += c - '0'; |
||||
} |
||||
/* We insist on stopping at "." if we are still parsing
|
||||
the first, second, or third numbers. If we have reached |
||||
the end of the numbers, we will allow any character. */ |
||||
else if ((i < 3 && c == '.') || i == 3) { |
||||
break; |
||||
} |
||||
else { |
||||
return 0; |
||||
} |
||||
} |
||||
if (n >= 256) { |
||||
return 0; |
||||
} |
||||
((uint8_t*)ip)[i] = n; |
||||
} |
||||
return 1; |
||||
} |
||||
|
||||
#define TOKEN(x) (os_strcmp(token, x) == 0) |
||||
#if 0 |
||||
// Handle system information variables and print their value, returns the number of
|
||||
// characters appended to buff
|
||||
int ICACHE_FLASH_ATTR printGlobalInfo(char *buff, int buflen, char *token) { |
||||
if (TOKEN("si_chip_id")) { |
||||
return os_sprintf(buff, "0x%x", system_get_chip_id()); |
||||
} else if (TOKEN("si_freeheap")) { |
||||
return os_sprintf(buff, "%dKB", system_get_free_heap_size()/1024); |
||||
} else if (TOKEN("si_uptime")) { |
||||
uint32 t = system_get_time() / 1000000; // in seconds
|
||||
return os_sprintf(buff, "%dd%dh%dm%ds", t/(24*3600), (t/(3600))%24, (t/60)%60, t%60); |
||||
} else if (TOKEN("si_boot_version")) { |
||||
return os_sprintf(buff, "%d", system_get_boot_version()); |
||||
} else if (TOKEN("si_boot_address")) { |
||||
return os_sprintf(buff, "0x%x", system_get_userbin_addr()); |
||||
} else if (TOKEN("si_cpu_freq")) { |
||||
return os_sprintf(buff, "%dMhz", system_get_cpu_freq()); |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
extern char *esp_link_version; // in user_main.c
|
||||
|
||||
int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
char buff[1024]; |
||||
// don't use jsonHeader so the response does get cached
|
||||
httpdStartResponse(connData, 200); |
||||
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); |
||||
httpdHeader(connData, "Content-Type", "application/json"); |
||||
httpdEndHeaders(connData); |
||||
// construct json response
|
||||
os_sprintf(buff, |
||||
"{\"menu\": [\"Home\", \"/home.html\", " |
||||
"\"Wifi\", \"/wifi/wifi.html\"," |
||||
"\"\xC2\xB5" "C Console\", \"/console.html\", " |
||||
#ifdef MQTT |
||||
"\"REST/MQTT\", \"/mqtt.html\"," |
||||
#endif |
||||
"\"Debug log\", \"/log.html\" ],\n" |
||||
" \"version\": \"%s\" }", esp_link_version); |
||||
httpdSend(connData, buff, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
@ -0,0 +1,22 @@ |
||||
#ifndef CGI_H |
||||
#define CGI_H |
||||
|
||||
#include <esp8266.h> |
||||
#include "httpd.h" |
||||
|
||||
void jsonHeader(HttpdConnData *connData, int code); |
||||
void errorResponse(HttpdConnData *connData, int code, char *message); |
||||
|
||||
// Get the HTTP query-string param 'name' and store it at 'config' with max length
|
||||
// 'max_len' (incl terminating zero), returns -1 on error, 0 if not found, 1 if found
|
||||
int8_t getStringArg(HttpdConnData *connData, char *name, char *config, int max_len); |
||||
|
||||
// Get the HTTP query-string param 'name' and store it boolean value at 'config',
|
||||
// supports 1/true and 0/false, returns -1 on error, 0 if not found, 1 if found
|
||||
int8_t getBoolArg(HttpdConnData *connData, char *name, bool*config); |
||||
|
||||
int cgiMenu(HttpdConnData *connData); |
||||
|
||||
uint8_t UTILS_StrToIP(const char* str, void *ip); |
||||
|
||||
#endif |
@ -0,0 +1,172 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
#ifdef MQTT |
||||
#include <esp8266.h> |
||||
#include "cgi.h" |
||||
#include "config.h" |
||||
#include "status.h" |
||||
#include "mqtt_client.h" |
||||
#include "cgimqtt.h" |
||||
|
||||
static char *mqtt_states[] = { |
||||
"disconnected", "reconnecting", "connecting", "connected", |
||||
}; |
||||
|
||||
// Cgi to return MQTT settings
|
||||
int ICACHE_FLASH_ATTR cgiMqttGet(HttpdConnData *connData) { |
||||
char buff[1024]; |
||||
int len; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||
|
||||
// get the current status topic for display
|
||||
char status_buf1[128], *sb1=status_buf1; |
||||
char status_buf2[128], *sb2=status_buf2; |
||||
mqttStatusMsg(status_buf1); |
||||
// quote all " for the json, sigh...
|
||||
for (int i=0; i<127 && *sb1; i++) { |
||||
if (*sb1 == '"') { |
||||
*sb2++ = '\\'; |
||||
i++; |
||||
} |
||||
*sb2++ = *sb1++; |
||||
} |
||||
*sb2 = 0; |
||||
|
||||
len = os_sprintf(buff, "{ " |
||||
"\"slip-enable\":%d, " |
||||
"\"mqtt-enable\":%d, " |
||||
"\"mqtt-state\":\"%s\", " |
||||
"\"mqtt-status-enable\":%d, " |
||||
"\"mqtt-clean-session\":%d, " |
||||
"\"mqtt-port\":%d, " |
||||
"\"mqtt-timeout\":%d, " |
||||
"\"mqtt-keepalive\":%d, " |
||||
"\"mqtt-host\":\"%s\", " |
||||
"\"mqtt-client-id\":\"%s\", " |
||||
"\"mqtt-username\":\"%s\", " |
||||
"\"mqtt-password\":\"%s\", " |
||||
"\"mqtt-status-topic\":\"%s\", " |
||||
"\"mqtt-status-value\":\"%s\" }", |
||||
flashConfig.slip_enable, flashConfig.mqtt_enable, |
||||
mqtt_states[mqttClient.connState], flashConfig.mqtt_status_enable, |
||||
flashConfig.mqtt_clean_session, flashConfig.mqtt_port, |
||||
flashConfig.mqtt_timeout, flashConfig.mqtt_keepalive, |
||||
flashConfig.mqtt_host, flashConfig.mqtt_clientid, |
||||
flashConfig.mqtt_username, flashConfig.mqtt_password, |
||||
flashConfig.mqtt_status_topic, status_buf2); |
||||
|
||||
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; |
||||
|
||||
// handle MQTT server settings
|
||||
int8_t mqtt_server = 0; // accumulator for changes/errors
|
||||
mqtt_server |= getStringArg(connData, "mqtt-host", |
||||
flashConfig.mqtt_host, sizeof(flashConfig.mqtt_host)); |
||||
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||
mqtt_server |= getStringArg(connData, "mqtt-client-id", |
||||
flashConfig.mqtt_clientid, sizeof(flashConfig.mqtt_clientid)); |
||||
|
||||
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||
mqtt_server |= getStringArg(connData, "mqtt-username", |
||||
flashConfig.mqtt_username, sizeof(flashConfig.mqtt_username)); |
||||
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||
mqtt_server |= getStringArg(connData, "mqtt-password", |
||||
flashConfig.mqtt_password, sizeof(flashConfig.mqtt_password)); |
||||
|
||||
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||
mqtt_server |= getBoolArg(connData, "mqtt-clean-session", |
||||
&flashConfig.mqtt_clean_session); |
||||
|
||||
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||
int8_t mqtt_en_chg = getBoolArg(connData, "mqtt-enable", |
||||
&flashConfig.mqtt_enable); |
||||
|
||||
char buff[16]; |
||||
|
||||
// handle mqtt port
|
||||
if (httpdFindArg(connData->getArgs, "mqtt-port", buff, sizeof(buff)) > 0) { |
||||
int32_t port = atoi(buff); |
||||
if (port > 0 && port < 65536) { |
||||
flashConfig.mqtt_port = port; |
||||
mqtt_server |= 1; |
||||
} else { |
||||
errorResponse(connData, 400, "Invalid MQTT port"); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
} |
||||
|
||||
// handle mqtt timeout
|
||||
if (httpdFindArg(connData->getArgs, "mqtt-timeout", buff, sizeof(buff)) > 0) { |
||||
int32_t timeout = atoi(buff); |
||||
flashConfig.mqtt_timeout = timeout; |
||||
} |
||||
|
||||
// handle mqtt keepalive
|
||||
if (httpdFindArg(connData->getArgs, "mqtt-keepalive", buff, sizeof(buff)) > 0) { |
||||
int32_t keepalive = atoi(buff); |
||||
flashConfig.mqtt_keepalive = keepalive; |
||||
} |
||||
|
||||
// if server setting changed, we need to "make it so"
|
||||
if (mqtt_server) { |
||||
#ifdef CGIMQTT_DBG |
||||
os_printf("MQTT server settings changed, enable=%d\n", flashConfig.mqtt_enable); |
||||
#endif |
||||
MQTT_Free(&mqttClient); // safe even if not connected
|
||||
mqtt_client_init(); |
||||
|
||||
// if just enable changed we just need to bounce the client
|
||||
} else if (mqtt_en_chg > 0) { |
||||
#ifdef CGIMQTT_DBG |
||||
os_printf("MQTT server enable=%d changed\n", flashConfig.mqtt_enable); |
||||
#endif |
||||
if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0) |
||||
MQTT_Reconnect(&mqttClient); |
||||
else |
||||
MQTT_Disconnect(&mqttClient); |
||||
} |
||||
|
||||
// no action required if mqtt status settings change, they just get picked up at the
|
||||
// next status tick
|
||||
if (getBoolArg(connData, "mqtt-status-enable", &flashConfig.mqtt_status_enable) < 0) |
||||
return HTTPD_CGI_DONE; |
||||
if (getStringArg(connData, "mqtt-status-topic", |
||||
flashConfig.mqtt_status_topic, sizeof(flashConfig.mqtt_status_topic)) < 0) |
||||
return HTTPD_CGI_DONE; |
||||
|
||||
// if SLIP-enable is toggled it gets picked-up immediately by the parser
|
||||
int slip_update = getBoolArg(connData, "slip-enable", &flashConfig.slip_enable); |
||||
if (slip_update < 0) return HTTPD_CGI_DONE; |
||||
#ifdef CGIMQTT_DBG |
||||
if (slip_update > 0) os_printf("SLIP-enable changed: %d\n", flashConfig.slip_enable); |
||||
|
||||
os_printf("Saving config\n"); |
||||
#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; |
||||
} |
||||
} |
||||
#endif // MQTT
|
@ -0,0 +1,9 @@ |
||||
#ifdef MQTT |
||||
#ifndef CGIMQTT_H |
||||
#define CGIMQTT_H |
||||
|
||||
#include "httpd.h" |
||||
int cgiMqtt(HttpdConnData *connData); |
||||
|
||||
#endif // CGIMQTT_H
|
||||
#endif // MQTT
|
@ -0,0 +1,74 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
// // TCP Client settings
|
||||
|
||||
#include <esp8266.h> |
||||
#include "cgi.h" |
||||
#include "config.h" |
||||
#include "cgitcp.h" |
||||
|
||||
// Cgi to return TCP client settings
|
||||
int ICACHE_FLASH_ATTR cgiTcpGet(HttpdConnData *connData) { |
||||
char buff[1024]; |
||||
int len; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||
|
||||
len = os_sprintf(buff, "{ \"tcp_enable\":%d, \"rssi_enable\": %d, \"api_key\":\"%s\" }", |
||||
flashConfig.tcp_enable, flashConfig.rssi_enable, flashConfig.api_key); |
||||
|
||||
jsonHeader(connData, 200); |
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
// Cgi to change choice of pin assignments
|
||||
int ICACHE_FLASH_ATTR cgiTcpSet(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||
|
||||
// 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); |
||||
|
||||
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 cgiTcp(HttpdConnData *connData) { |
||||
if (connData->requestType == HTTPD_METHOD_GET) { |
||||
return cgiTcpGet(connData); |
||||
} else if (connData->requestType == HTTPD_METHOD_POST) { |
||||
return cgiTcpSet(connData); |
||||
} else { |
||||
jsonHeader(connData, 404); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
#ifndef CGITCP_H |
||||
#define CGITCP_H |
||||
|
||||
#include "httpd.h" |
||||
|
||||
int cgiTcp(HttpdConnData *connData); |
||||
|
||||
#endif |
@ -0,0 +1,640 @@ |
||||
/*
|
||||
Cgi/template routines for the /wifi url. |
||||
*/ |
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------------------- |
||||
* "THE BEER-WARE LICENSE" (Revision 42): |
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain |
||||
* this notice you can do whatever you want with this stuff. If we meet some day, |
||||
* and you think this stuff is worth it, you can buy me a beer in return. |
||||
* ---------------------------------------------------------------------------- |
||||
* Heavily modified and enhanced by Thorsten von Eicken in 2015 |
||||
* ---------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
|
||||
#include <esp8266.h> |
||||
#include "cgiwifi.h" |
||||
#include "cgi.h" |
||||
#include "status.h" |
||||
#include "config.h" |
||||
#include "log.h" |
||||
|
||||
//#define SLEEP_MODE LIGHT_SLEEP_T
|
||||
#define SLEEP_MODE MODEM_SLEEP_T |
||||
|
||||
// ===== wifi status change callbacks
|
||||
static WifiStateChangeCb wifi_state_change_cb[4]; |
||||
|
||||
uint8_t wifiState = wifiIsDisconnected; |
||||
// reasons for which a connection failed
|
||||
uint8_t wifiReason = 0; |
||||
static char *wifiReasons[] = { |
||||
"", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", |
||||
"not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", |
||||
"ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", |
||||
"ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", |
||||
"unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", |
||||
"beacon_timeout", "no_ap_found" }; |
||||
|
||||
static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; |
||||
static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; |
||||
|
||||
void (*wifiStatusCb)(uint8_t); // callback when wifi status changes
|
||||
|
||||
static char* ICACHE_FLASH_ATTR wifiGetReason(void) { |
||||
if (wifiReason <= 24) return wifiReasons[wifiReason]; |
||||
if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; |
||||
return wifiReasons[1]; |
||||
} |
||||
|
||||
// handler for wifi status change callback coming in from espressif library
|
||||
static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) { |
||||
switch (evt->event) { |
||||
case EVENT_STAMODE_CONNECTED: |
||||
wifiState = wifiIsConnected; |
||||
wifiReason = 0; |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, |
||||
evt->event_info.connected.channel); |
||||
#endif |
||||
statusWifiUpdate(wifiState); |
||||
break; |
||||
case EVENT_STAMODE_DISCONNECTED: |
||||
wifiState = wifiIsDisconnected; |
||||
wifiReason = evt->event_info.disconnected.reason; |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", |
||||
evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); |
||||
#endif |
||||
statusWifiUpdate(wifiState); |
||||
break; |
||||
case EVENT_STAMODE_AUTHMODE_CHANGE: |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi auth mode: %d -> %d\n", |
||||
evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); |
||||
#endif |
||||
break; |
||||
case EVENT_STAMODE_GOT_IP: |
||||
wifiState = wifiGotIP; |
||||
wifiReason = 0; |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi got ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR "\n", |
||||
IP2STR(&evt->event_info.got_ip.ip), IP2STR(&evt->event_info.got_ip.mask), |
||||
IP2STR(&evt->event_info.got_ip.gw)); |
||||
#endif |
||||
statusWifiUpdate(wifiState); |
||||
break; |
||||
case EVENT_SOFTAPMODE_STACONNECTED: |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", |
||||
MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); |
||||
#endif |
||||
break; |
||||
case EVENT_SOFTAPMODE_STADISCONNECTED: |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", |
||||
MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); |
||||
#endif |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
for (int i = 0; i < 4; i++) { |
||||
if (wifi_state_change_cb[i] != NULL) (wifi_state_change_cb[i])(wifiState); |
||||
} |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
wifiAddStateChangeCb(WifiStateChangeCb cb) { |
||||
for (int i = 0; i < 4; i++) { |
||||
if (wifi_state_change_cb[i] == cb) return; |
||||
if (wifi_state_change_cb[i] == NULL) { |
||||
wifi_state_change_cb[i] = cb; |
||||
return; |
||||
} |
||||
} |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("WIFI: max state change cb count exceeded\n"); |
||||
#endif |
||||
} |
||||
|
||||
// ===== wifi scanning
|
||||
|
||||
//WiFi access point data
|
||||
typedef struct { |
||||
char ssid[32]; |
||||
sint8 rssi; |
||||
char enc; |
||||
} ApData; |
||||
|
||||
//Scan result
|
||||
typedef struct { |
||||
char scanInProgress; //if 1, don't access the underlying stuff from the webpage.
|
||||
ApData **apData; |
||||
int noAps; |
||||
} ScanResultData; |
||||
|
||||
//Static scan status storage.
|
||||
static ScanResultData cgiWifiAps; |
||||
|
||||
//Callback the code calls when a wlan ap scan is done. Basically stores the result in
|
||||
//the cgiWifiAps struct.
|
||||
void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { |
||||
int n; |
||||
struct bss_info *bss_link = (struct bss_info *)arg; |
||||
|
||||
if (status!=OK) { |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("wifiScanDoneCb status=%d\n", status); |
||||
#endif |
||||
cgiWifiAps.scanInProgress=0; |
||||
return; |
||||
} |
||||
|
||||
//Clear prev ap data if needed.
|
||||
if (cgiWifiAps.apData!=NULL) { |
||||
for (n=0; n<cgiWifiAps.noAps; n++) os_free(cgiWifiAps.apData[n]); |
||||
os_free(cgiWifiAps.apData); |
||||
} |
||||
|
||||
//Count amount of access points found.
|
||||
n=0; |
||||
while (bss_link != NULL) { |
||||
bss_link = bss_link->next.stqe_next; |
||||
n++; |
||||
} |
||||
//Allocate memory for access point data
|
||||
cgiWifiAps.apData=(ApData **)os_malloc(sizeof(ApData *)*n); |
||||
cgiWifiAps.noAps=n; |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Scan done: found %d APs\n", n); |
||||
#endif |
||||
|
||||
//Copy access point data to the static struct
|
||||
n=0; |
||||
bss_link = (struct bss_info *)arg; |
||||
while (bss_link != NULL) { |
||||
if (n>=cgiWifiAps.noAps) { |
||||
//This means the bss_link changed under our nose. Shouldn't happen!
|
||||
//Break because otherwise we will write in unallocated memory.
|
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); |
||||
#endif |
||||
break; |
||||
} |
||||
//Save the ap data.
|
||||
cgiWifiAps.apData[n]=(ApData *)os_malloc(sizeof(ApData)); |
||||
cgiWifiAps.apData[n]->rssi=bss_link->rssi; |
||||
cgiWifiAps.apData[n]->enc=bss_link->authmode; |
||||
strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("bss%d: %s (%d)\n", n+1, (char*)bss_link->ssid, bss_link->rssi); |
||||
#endif |
||||
|
||||
bss_link = bss_link->next.stqe_next; |
||||
n++; |
||||
} |
||||
//We're done.
|
||||
cgiWifiAps.scanInProgress=0; |
||||
} |
||||
|
||||
static ETSTimer scanTimer; |
||||
static void ICACHE_FLASH_ATTR scanStartCb(void *arg) { |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Starting a scan\n"); |
||||
#endif |
||||
wifi_station_scan(NULL, wifiScanDoneCb); |
||||
} |
||||
|
||||
static int ICACHE_FLASH_ATTR cgiWiFiStartScan(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
jsonHeader(connData, 200); |
||||
if (!cgiWifiAps.scanInProgress) { |
||||
cgiWifiAps.scanInProgress = 1; |
||||
os_timer_disarm(&scanTimer); |
||||
os_timer_setfn(&scanTimer, scanStartCb, NULL); |
||||
os_timer_arm(&scanTimer, 1000, 0); |
||||
} |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
char buff[2048]; |
||||
int len; |
||||
|
||||
jsonHeader(connData, 200); |
||||
|
||||
if (cgiWifiAps.scanInProgress==1) { |
||||
//We're still scanning. Tell Javascript code that.
|
||||
len = os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); |
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
len = os_sprintf(buff, "{\"result\": {\"inProgress\": \"0\", \"APs\": [\n"); |
||||
for (int pos=0; pos<cgiWifiAps.noAps; pos++) { |
||||
len += os_sprintf(buff+len, "{\"essid\": \"%s\", \"rssi\": %d, \"enc\": \"%d\"}%s\n", |
||||
cgiWifiAps.apData[pos]->ssid, cgiWifiAps.apData[pos]->rssi, |
||||
cgiWifiAps.apData[pos]->enc, (pos==cgiWifiAps.noAps-1)?"":","); |
||||
} |
||||
len += os_sprintf(buff+len, "]}}\n"); |
||||
//os_printf("Sending %d bytes: %s\n", len, buff);
|
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { |
||||
if (connData->requestType == HTTPD_METHOD_GET) { |
||||
return cgiWiFiGetScan(connData); |
||||
} else if (connData->requestType == HTTPD_METHOD_POST) { |
||||
return cgiWiFiStartScan(connData); |
||||
} else { |
||||
jsonHeader(connData, 404); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
} |
||||
|
||||
// ===== timers to change state and rescue from failed associations
|
||||
|
||||
// reset timer changes back to STA+AP if we can't associate
|
||||
#define RESET_TIMEOUT (15000) // 15 seconds
|
||||
static ETSTimer resetTimer; |
||||
|
||||
// This routine is ran some time after a connection attempt to an access point. If
|
||||
// the connect succeeds, this gets the module in STA-only mode. If it fails, it ensures
|
||||
// that the module is in STA+AP mode so the user has a chance to recover.
|
||||
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { |
||||
int x = wifi_station_get_connect_status(); |
||||
int m = wifi_get_opmode() & 0x3; |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi check: mode=%s status=%d\n", wifiMode[m], x); |
||||
#endif |
||||
|
||||
if (x == STATION_GOT_IP) { |
||||
if (m != 1) { |
||||
#ifdef CHANGE_TO_STA |
||||
// We're happily connected, go to STA mode
|
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi got IP. Going into STA mode..\n"); |
||||
#endif |
||||
wifi_set_opmode(1); |
||||
wifi_set_sleep_type(SLEEP_MODE); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
#endif |
||||
} |
||||
log_uart(false); |
||||
// no more resetTimer at this point, gotta use physical reset to recover if in trouble
|
||||
} else { |
||||
if (m != 3) { |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi connect failed. Going into STA+AP mode..\n"); |
||||
#endif |
||||
wifi_set_opmode(3); |
||||
} |
||||
log_uart(true); |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Enabling/continuing uart log\n"); |
||||
#endif |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
} |
||||
|
||||
// Temp store for new ap info.
|
||||
static struct station_config stconf; |
||||
// Reassociate timer to delay change of association so the original request can finish
|
||||
static ETSTimer reassTimer; |
||||
|
||||
// Callback actually doing reassociation
|
||||
static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi changing association\n"); |
||||
#endif |
||||
wifi_station_disconnect(); |
||||
stconf.bssid_set = 0; |
||||
wifi_station_set_config(&stconf); |
||||
wifi_station_connect(); |
||||
// Schedule check
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
|
||||
// This cgi uses the routines above to connect to a specific access point with the
|
||||
// given ESSID using the given password.
|
||||
int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { |
||||
char essid[128]; |
||||
char passwd[128]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||
|
||||
int el = httpdFindArg(connData->getArgs, "essid", essid, sizeof(essid)); |
||||
int pl = httpdFindArg(connData->getArgs, "passwd", passwd, sizeof(passwd)); |
||||
|
||||
if (el > 0 && pl >= 0) { |
||||
//Set to 0 if you want to disable the actual reconnecting bit
|
||||
os_strncpy((char*)stconf.ssid, essid, 32); |
||||
os_strncpy((char*)stconf.password, passwd, 64); |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi try to connect to AP %s pw %s\n", essid, passwd); |
||||
#endif |
||||
|
||||
//Schedule disconnect/connect
|
||||
os_timer_disarm(&reassTimer); |
||||
os_timer_setfn(&reassTimer, reassTimerCb, NULL); |
||||
os_timer_arm(&reassTimer, 1000, 0); |
||||
jsonHeader(connData, 200); |
||||
} else { |
||||
jsonHeader(connData, 400); |
||||
httpdSend(connData, "Cannot parse ssid or password", -1); |
||||
} |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static bool parse_ip(char *buff, ip_addr_t *ip_ptr) { |
||||
char *next = buff; // where to start parsing next integer
|
||||
int found = 0; // number of integers parsed
|
||||
uint32_t ip = 0; // the ip addres parsed
|
||||
for (int i=0; i<32; i++) { // 32 is just a safety limit
|
||||
char c = buff[i]; |
||||
if (c == '.' || c == 0) { |
||||
// parse the preceding integer and accumulate into IP address
|
||||
bool last = c == 0; |
||||
buff[i] = 0; |
||||
uint32_t v = atoi(next); |
||||
ip = ip | ((v&0xff)<<(found*8)); |
||||
next = buff+i+1; // next integer starts after the '.'
|
||||
found++; |
||||
if (last) { // if at end of string we better got 4 integers
|
||||
ip_ptr->addr = ip; |
||||
return found == 4; |
||||
} |
||||
continue; |
||||
} |
||||
if (c < '0' || c > '9') return false; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
#ifdef DEBUGIP |
||||
static void ICACHE_FLASH_ATTR debugIP() { |
||||
struct ip_info info; |
||||
if (wifi_get_ip_info(0, &info)) { |
||||
os_printf("\"ip\": \"%d.%d.%d.%d\"\n", IP2STR(&info.ip.addr)); |
||||
os_printf("\"netmask\": \"%d.%d.%d.%d\"\n", IP2STR(&info.netmask.addr)); |
||||
os_printf("\"gateway\": \"%d.%d.%d.%d\"\n", IP2STR(&info.gw.addr)); |
||||
os_printf("\"hostname\": \"%s\"\n", wifi_station_get_hostname()); |
||||
} else { |
||||
os_printf("\"ip\": \"-none-\"\n"); |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
// configure Wifi, specifically DHCP vs static IP address based on flash config
|
||||
static void ICACHE_FLASH_ATTR configWifiIP() { |
||||
if (flashConfig.staticip == 0) { |
||||
// let's DHCP!
|
||||
wifi_station_set_hostname(flashConfig.hostname); |
||||
if (wifi_station_dhcpc_status() == DHCP_STARTED) |
||||
wifi_station_dhcpc_stop(); |
||||
wifi_station_dhcpc_start(); |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi uses DHCP, hostname=%s\n", flashConfig.hostname); |
||||
#endif |
||||
} else { |
||||
// no DHCP, we got static network config!
|
||||
wifi_station_dhcpc_stop(); |
||||
struct ip_info ipi; |
||||
ipi.ip.addr = flashConfig.staticip; |
||||
ipi.netmask.addr = flashConfig.netmask; |
||||
ipi.gw.addr = flashConfig.gateway; |
||||
wifi_set_ip_info(0, &ipi); |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi uses static IP %d.%d.%d.%d\n", IP2STR(&ipi.ip.addr)); |
||||
#endif |
||||
} |
||||
#ifdef DEBUGIP |
||||
debugIP(); |
||||
#endif |
||||
} |
||||
|
||||
// Change special settings
|
||||
int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) { |
||||
char dhcp[8]; |
||||
char hostname[32]; |
||||
char staticip[20]; |
||||
char netmask[20]; |
||||
char gateway[20]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||
|
||||
// get args and their string lengths
|
||||
int dl = httpdFindArg(connData->getArgs, "dhcp", dhcp, sizeof(dhcp)); |
||||
int hl = httpdFindArg(connData->getArgs, "hostname", hostname, sizeof(hostname)); |
||||
int sl = httpdFindArg(connData->getArgs, "staticip", staticip, sizeof(staticip)); |
||||
int nl = httpdFindArg(connData->getArgs, "netmask", netmask, sizeof(netmask)); |
||||
int gl = httpdFindArg(connData->getArgs, "gateway", gateway, sizeof(gateway)); |
||||
|
||||
if (!(dl > 0 && hl >= 0 && sl >= 0 && nl >= 0 && gl >= 0)) { |
||||
jsonHeader(connData, 400); |
||||
httpdSend(connData, "Request is missing fields", -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
char url[64]; // redirect URL
|
||||
if (os_strcmp(dhcp, "off") == 0) { |
||||
// parse static IP params
|
||||
struct ip_info ipi; |
||||
bool ok = parse_ip(staticip, &ipi.ip); |
||||
if (nl > 0) ok = ok && parse_ip(netmask, &ipi.netmask); |
||||
else IP4_ADDR(&ipi.netmask, 255, 255, 255, 0); |
||||
if (gl > 0) ok = ok && parse_ip(gateway, &ipi.gw); |
||||
else ipi.gw.addr = 0; |
||||
if (!ok) { |
||||
jsonHeader(connData, 400); |
||||
httpdSend(connData, "Cannot parse static IP config", -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
// save the params in flash
|
||||
flashConfig.staticip = ipi.ip.addr; |
||||
flashConfig.netmask = ipi.netmask.addr; |
||||
flashConfig.gateway = ipi.gw.addr; |
||||
// construct redirect URL
|
||||
os_sprintf(url, "{\"url\": \"http://%d.%d.%d.%d\"}", IP2STR(&ipi.ip)); |
||||
|
||||
} else { |
||||
// no static IP, set hostname
|
||||
if (hl == 0) os_strcpy(hostname, "esp-link"); |
||||
flashConfig.staticip = 0; |
||||
os_strcpy(flashConfig.hostname, hostname); |
||||
os_sprintf(url, "{\"url\": \"http://%s\"}", hostname); |
||||
} |
||||
|
||||
configSave(); // ignore error...
|
||||
// schedule change-over
|
||||
os_timer_disarm(&reassTimer); |
||||
os_timer_setfn(&reassTimer, configWifiIP, NULL); |
||||
os_timer_arm(&reassTimer, 1000, 0); |
||||
// return redirect info
|
||||
jsonHeader(connData, 200); |
||||
httpdSend(connData, url, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
//This cgi changes the operating mode: STA / AP / STA+AP
|
||||
int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { |
||||
int len; |
||||
char buff[1024]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
|
||||
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); |
||||
if (len!=0) { |
||||
int m = atoi(buff); |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi switching to mode %d\n", m); |
||||
#endif |
||||
wifi_set_opmode(m&3); |
||||
if (m == 1) { |
||||
wifi_set_sleep_type(SLEEP_MODE); |
||||
// STA-only mode, reset into STA+AP after a timeout
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
jsonHeader(connData, 200); |
||||
} else { |
||||
jsonHeader(connData, 400); |
||||
} |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not found", |
||||
"failed", "got IP address" }; |
||||
|
||||
static char *wifiWarn[] = { 0, |
||||
"Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>", |
||||
"<b>Can't scan in this mode!</b> Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>", |
||||
"Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(1)\\\">STA mode</a>", |
||||
}; |
||||
|
||||
#ifdef CHANGE_TO_STA |
||||
#define MODECHANGE "yes" |
||||
#else |
||||
#define MODECHANGE "no" |
||||
#endif |
||||
|
||||
// print various Wifi information into json buffer
|
||||
int ICACHE_FLASH_ATTR printWifiInfo(char *buff) { |
||||
int len; |
||||
|
||||
struct station_config stconf; |
||||
wifi_station_get_config(&stconf); |
||||
|
||||
uint8_t op = wifi_get_opmode() & 0x3; |
||||
char *mode = wifiMode[op]; |
||||
char *status = "unknown"; |
||||
int st = wifi_station_get_connect_status(); |
||||
if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st]; |
||||
int p = wifi_get_phy_mode(); |
||||
char *phy = wifiPhy[p&3]; |
||||
char *warn = wifiWarn[op]; |
||||
sint8 rssi = wifi_station_get_rssi(); |
||||
if (rssi > 0) rssi = 0; |
||||
uint8 mac_addr[6]; |
||||
wifi_get_macaddr(0, mac_addr); |
||||
uint8_t chan = wifi_get_channel(); |
||||
|
||||
len = os_sprintf(buff, |
||||
"\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", " |
||||
"\"rssi\": \"%ddB\", \"warn\": \"%s\", \"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":%d", |
||||
mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn, |
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan); |
||||
|
||||
struct ip_info info; |
||||
if (wifi_get_ip_info(0, &info)) { |
||||
len += os_sprintf(buff+len, ", \"ip\": \"%d.%d.%d.%d\"", IP2STR(&info.ip.addr)); |
||||
len += os_sprintf(buff+len, ", \"netmask\": \"%d.%d.%d.%d\"", IP2STR(&info.netmask.addr)); |
||||
len += os_sprintf(buff+len, ", \"gateway\": \"%d.%d.%d.%d\"", IP2STR(&info.gw.addr)); |
||||
len += os_sprintf(buff+len, ", \"hostname\": \"%s\"", flashConfig.hostname); |
||||
} else { |
||||
len += os_sprintf(buff+len, ", \"ip\": \"-none-\""); |
||||
} |
||||
len += os_sprintf(buff+len, ", \"staticip\": \"%d.%d.%d.%d\"", IP2STR(&flashConfig.staticip)); |
||||
len += os_sprintf(buff+len, ", \"dhcp\": \"%s\"", flashConfig.staticip > 0 ? "off" : "on"); |
||||
|
||||
return len; |
||||
} |
||||
|
||||
int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { |
||||
char buff[1024]; |
||||
int len; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
jsonHeader(connData, 200); |
||||
|
||||
len = os_sprintf(buff, "{"); |
||||
len += printWifiInfo(buff+len); |
||||
len += os_sprintf(buff+len, ", "); |
||||
|
||||
if (wifiReason != 0) { |
||||
len += os_sprintf(buff+len, "\"reason\": \"%s\", ", wifiGetReason()); |
||||
} |
||||
|
||||
#if 0 |
||||
// commented out 'cause often the client that requested the change can't get a request in to
|
||||
// find out that it succeeded. Better to just wait the std 15 seconds...
|
||||
int st=wifi_station_get_connect_status(); |
||||
if (st == STATION_GOT_IP) { |
||||
if (wifi_get_opmode() != 1) { |
||||
// Reset into AP-only mode sooner.
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, 1000, 0); |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
len += os_sprintf(buff+len, "\"x\":0}\n"); |
||||
#ifdef CGIWIFI_DBG |
||||
//os_printf(" -> %s\n", buff);
|
||||
#endif |
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
// Cgi to return various Wifi information
|
||||
int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) { |
||||
char buff[1024]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
|
||||
os_strcpy(buff, "{"); |
||||
printWifiInfo(buff+1); |
||||
os_strcat(buff, "}"); |
||||
|
||||
jsonHeader(connData, 200); |
||||
httpdSend(connData, buff, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
// Init the wireless, which consists of setting a timer if we expect to connect to an AP
|
||||
// so we can revert to STA+AP mode if we can't connect.
|
||||
void ICACHE_FLASH_ATTR wifiInit() { |
||||
wifi_set_phy_mode(2); |
||||
#ifdef CGIWIFI_DBG |
||||
int x = wifi_get_opmode() & 0x3; |
||||
os_printf("Wifi init, mode=%s\n", wifiMode[x]); |
||||
#endif |
||||
configWifiIP(); |
||||
|
||||
wifi_set_event_handler_cb(wifiHandleEventCb); |
||||
// check on the wifi in a few seconds to see whether we need to switch mode
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
|
@ -0,0 +1,173 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
/* Configuration stored in flash */ |
||||
|
||||
#include <esp8266.h> |
||||
#include <osapi.h> |
||||
#include "config.h" |
||||
#include "espfs.h" |
||||
#include "crc16.h" |
||||
|
||||
FlashConfig flashConfig; |
||||
FlashConfig flashDefault = { |
||||
33, 0, 0, |
||||
MCU_RESET_PIN, MCU_ISP_PIN, LED_CONN_PIN, LED_SERIAL_PIN, |
||||
115200, |
||||
"esp-link\0", // hostname
|
||||
0, 0x00ffffff, 0, // static ip, netmask, gateway
|
||||
0, // log mode
|
||||
0, // swap uart (don't by default)
|
||||
1, 0, // tcp_enable, rssi_enable
|
||||
"\0", // api_key
|
||||
0, 0, 0, // slip_enable, mqtt_enable, mqtt_status_enable
|
||||
2, 1, // mqtt_timeout, mqtt_clean_session
|
||||
1883, 60, // mqtt port, mqtt_keepalive
|
||||
"\0", "\0", "\0", "\0", "\0", // mqtt host, client_id, user, password, status-topic
|
||||
}; |
||||
|
||||
typedef union { |
||||
FlashConfig fc; |
||||
uint8_t block[1024]; |
||||
} FlashFull; |
||||
|
||||
// magic number to recognize thet these are our flash settings as opposed to some random stuff
|
||||
#define FLASH_MAGIC (0xaa55) |
||||
|
||||
// size of the setting sector
|
||||
#define FLASH_SECT (4096) |
||||
|
||||
// address where to flash the settings: there are 16KB of reserved space at the end of the first
|
||||
// flash partition, we use the upper 8KB (2 sectors)
|
||||
#define FLASH_ADDR (FLASH_SECT + FIRMWARE_SIZE + 2*FLASH_SECT) |
||||
|
||||
static int flash_pri; // primary flash sector (0 or 1, or -1 for error)
|
||||
|
||||
#if 0 |
||||
static void memDump(void *addr, int len) { |
||||
for (int i=0; i<len; i++) { |
||||
os_printf("0x%02x", ((uint8_t *)addr)[i]); |
||||
} |
||||
os_printf("\n"); |
||||
} |
||||
#endif |
||||
|
||||
bool ICACHE_FLASH_ATTR configSave(void) { |
||||
FlashFull ff; |
||||
os_memset(&ff, 0, sizeof(ff)); |
||||
os_memcpy(&ff, &flashConfig, sizeof(FlashConfig)); |
||||
uint32_t seq = ff.fc.seq+1; |
||||
// erase secondary
|
||||
uint32_t addr = FLASH_ADDR + (1-flash_pri)*FLASH_SECT; |
||||
if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) |
||||
goto fail; // no harm done, give up
|
||||
// calculate CRC
|
||||
ff.fc.seq = seq; |
||||
ff.fc.magic = FLASH_MAGIC; |
||||
ff.fc.crc = 0; |
||||
//os_printf("cksum of: ");
|
||||
//memDump(&ff, sizeof(ff));
|
||||
ff.fc.crc = crc16_data((unsigned char*)&ff, sizeof(ff), 0); |
||||
//os_printf("cksum is %04x\n", ff.fc.crc);
|
||||
// write primary with incorrect seq
|
||||
ff.fc.seq = 0xffffffff; |
||||
if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) |
||||
goto fail; // no harm done, give up
|
||||
// fill in correct seq
|
||||
ff.fc.seq = seq; |
||||
if (spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)) != SPI_FLASH_RESULT_OK) |
||||
goto fail; // most likely failed, but no harm if successful
|
||||
// now that we have safely written the new version, erase old primary
|
||||
addr = FLASH_ADDR + flash_pri*FLASH_SECT; |
||||
flash_pri = 1-flash_pri; |
||||
if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) |
||||
return true; // no back-up but we're OK
|
||||
// write secondary
|
||||
ff.fc.seq = 0xffffffff; |
||||
if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) |
||||
return true; // no back-up but we're OK
|
||||
ff.fc.seq = seq; |
||||
spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)); |
||||
return true; |
||||
fail: |
||||
#ifdef CONFIG_DBG |
||||
os_printf("*** Failed to save config ***\n"); |
||||
#endif |
||||
return false; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR configWipe(void) { |
||||
spi_flash_erase_sector(FLASH_ADDR>>12); |
||||
spi_flash_erase_sector((FLASH_ADDR+FLASH_SECT)>>12); |
||||
} |
||||
|
||||
static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *fc0, FlashFull *fc1); |
||||
|
||||
bool ICACHE_FLASH_ATTR configRestore(void) { |
||||
FlashFull ff0, ff1; |
||||
// read both flash sectors
|
||||
if (spi_flash_read(FLASH_ADDR, (void *)&ff0, sizeof(ff0)) != SPI_FLASH_RESULT_OK) |
||||
os_memset(&ff0, 0, sizeof(ff0)); // clear in case of error
|
||||
if (spi_flash_read(FLASH_ADDR+FLASH_SECT, (void *)&ff1, sizeof(ff1)) != SPI_FLASH_RESULT_OK) |
||||
os_memset(&ff1, 0, sizeof(ff1)); // clear in case of error
|
||||
// figure out which one is good
|
||||
flash_pri = selectPrimary(&ff0, &ff1); |
||||
// if neither is OK, we revert to defaults
|
||||
if (flash_pri < 0) { |
||||
os_memcpy(&flashConfig, &flashDefault, sizeof(FlashConfig)); |
||||
char chipIdStr[6]; |
||||
os_sprintf(chipIdStr, "%06x", system_get_chip_id()); |
||||
#ifdef CHIP_IN_HOSTNAME |
||||
char hostname[16]; |
||||
os_strcpy(hostname, "esp-link-"); |
||||
os_strcat(hostname, chipIdStr); |
||||
os_memcpy(&flashConfig.hostname, hostname, os_strlen(hostname)); |
||||
#endif |
||||
os_memcpy(&flashConfig.mqtt_clientid, &flashConfig.hostname, os_strlen(flashConfig.hostname)); |
||||
os_memcpy(&flashConfig.mqtt_status_topic, &flashConfig.hostname, os_strlen(flashConfig.hostname)); |
||||
flash_pri = 0; |
||||
return false; |
||||
} |
||||
// copy good one into global var and return
|
||||
os_memcpy(&flashConfig, flash_pri == 0 ? &ff0.fc : &ff1.fc, sizeof(FlashConfig)); |
||||
return true; |
||||
} |
||||
|
||||
static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) { |
||||
// check CRC of ff0
|
||||
uint16_t crc = ff0->fc.crc; |
||||
ff0->fc.crc = 0; |
||||
bool ff0_crc_ok = crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0) == crc; |
||||
#ifdef CONFIG_DBG |
||||
os_printf("FLASH chk=0x%04x crc=0x%04x full_sz=%d sz=%d chip_sz=%d\n", |
||||
crc16_data((unsigned char*)ff0, sizeof(FlashFull), 0), |
||||
crc, |
||||
sizeof(FlashFull), |
||||
sizeof(FlashConfig), |
||||
getFlashSize()); |
||||
#endif |
||||
|
||||
// check CRC of ff1
|
||||
crc = ff1->fc.crc; |
||||
ff1->fc.crc = 0; |
||||
bool ff1_crc_ok = crc16_data((unsigned char*)ff1, sizeof(FlashFull), 0) == crc; |
||||
|
||||
// decided which we like better
|
||||
if (ff0_crc_ok) |
||||
if (!ff1_crc_ok || ff0->fc.seq >= ff1->fc.seq) |
||||
return 0; // use first sector as primary
|
||||
else |
||||
return 1; // second sector is newer
|
||||
else |
||||
return ff1_crc_ok ? 1 : -1; |
||||
} |
||||
|
||||
// returns the flash chip's size, in BYTES
|
||||
const size_t ICACHE_FLASH_ATTR |
||||
getFlashSize() { |
||||
uint32_t id = spi_flash_get_id(); |
||||
uint8_t mfgr_id = id & 0xff; |
||||
//uint8_t type_id = (id >> 8) & 0xff; // not relevant for size calculation
|
||||
uint8_t size_id = (id >> 16) & 0xff; // lucky for us, WinBond ID's their chips as a form that lets us calculate the size
|
||||
if (mfgr_id != 0xEF) // 0xEF is WinBond; that's all we care about (for now)
|
||||
return 0; |
||||
return 1 << size_id; |
||||
} |
@ -0,0 +1,34 @@ |
||||
#ifndef CONFIG_H |
||||
#define CONFIG_H |
||||
|
||||
// Flash configuration settings. When adding new items always add them at the end and formulate
|
||||
// them such that a value of zero is an appropriate default or backwards compatible. Existing
|
||||
// modules that are upgraded will have zero in the new fields. This ensures that an upgrade does
|
||||
// not wipe out the old settings.
|
||||
typedef struct { |
||||
uint32_t seq; // flash write sequence number
|
||||
uint16_t magic, crc; |
||||
int8_t reset_pin, isp_pin, conn_led_pin, ser_led_pin; |
||||
int32_t baud_rate; |
||||
char hostname[32]; // if using DHCP
|
||||
uint32_t staticip, netmask, gateway; // using DHCP if staticip==0
|
||||
uint8_t log_mode; // UART log debug mode
|
||||
uint8_t swap_uart; // swap uart0 to gpio 13&15
|
||||
uint8_t tcp_enable, rssi_enable; // TCP client settings
|
||||
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
|
||||
mqtt_timeout, // MQTT send timeout
|
||||
mqtt_clean_session; // MQTT clean session
|
||||
uint16_t mqtt_port, mqtt_keepalive; // MQTT Host port, MQTT Keepalive timer
|
||||
char mqtt_host[32], mqtt_clientid[48], mqtt_username[32], mqtt_password[32]; |
||||
char mqtt_status_topic[32]; |
||||
} FlashConfig; |
||||
extern FlashConfig flashConfig; |
||||
|
||||
bool configSave(void); |
||||
bool configRestore(void); |
||||
void configWipe(void); |
||||
const size_t getFlashSize(); |
||||
|
||||
#endif |
@ -0,0 +1,182 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
|
||||
#include <esp8266.h> |
||||
#include "uart.h" |
||||
#include "cgi.h" |
||||
#include "config.h" |
||||
#include "log.h" |
||||
|
||||
// Web log for the esp8266 to replace outputting to uart1.
|
||||
// The web log has a 1KB circular in-memory buffer which os_printf prints into and
|
||||
// the HTTP handler simply displays the buffer content on a web page.
|
||||
|
||||
// see console.c for invariants (same here)
|
||||
#define BUF_MAX (1400) |
||||
static char log_buf[BUF_MAX]; |
||||
static int log_wr, log_rd; |
||||
static int log_pos; |
||||
static bool log_no_uart; // start out printing to uart
|
||||
static bool log_newline; // at start of a new line
|
||||
|
||||
// called from wifi reset timer to turn UART on when we loose wifi and back off
|
||||
// when we connect to wifi AP. Here this is gated by the flash setting
|
||||
void ICACHE_FLASH_ATTR |
||||
log_uart(bool enable) { |
||||
if (!enable && !log_no_uart && flashConfig.log_mode != LOG_MODE_ON) { |
||||
// we're asked to turn uart off, and uart is on, and the flash setting isn't always-on
|
||||
#if 1 |
||||
#ifdef LOG_DBG |
||||
os_printf("Turning OFF uart log\n"); |
||||
#endif |
||||
os_delay_us(4*1000L); // time for uart to flush
|
||||
log_no_uart = !enable; |
||||
#endif |
||||
} else if (enable && log_no_uart && flashConfig.log_mode != LOG_MODE_OFF) { |
||||
// we're asked to turn uart on, and uart is off, and the flash setting isn't always-off
|
||||
log_no_uart = !enable; |
||||
#ifdef LOG_DBG |
||||
os_printf("Turning ON uart log\n"); |
||||
#endif |
||||
} |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
log_write(char c) { |
||||
log_buf[log_wr] = c; |
||||
log_wr = (log_wr+1) % BUF_MAX; |
||||
if (log_wr == log_rd) { |
||||
log_rd = (log_rd+1) % BUF_MAX; // full, eat first char
|
||||
log_pos++; |
||||
} |
||||
} |
||||
|
||||
#if 0 |
||||
static char ICACHE_FLASH_ATTR |
||||
log_read(void) { |
||||
char c = 0; |
||||
if (log_rd != log_wr) { |
||||
c = log_buf[log_rd]; |
||||
log_rd = (log_rd+1) % BUF_MAX; |
||||
} |
||||
return c; |
||||
} |
||||
#endif |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
log_write_char(char c) { |
||||
// Uart output unless disabled
|
||||
if (!log_no_uart) { |
||||
if (log_newline) { |
||||
char buff[16]; |
||||
int l = os_sprintf(buff, "%6d> ", (system_get_time()/1000)%1000000); |
||||
for (int i=0; i<l; i++) |
||||
uart0_write_char(buff[i]); |
||||
log_newline = false; |
||||
} |
||||
uart0_write_char(c); |
||||
if (c == '\n') { |
||||
log_newline = true; |
||||
uart0_write_char('\r'); |
||||
} |
||||
} |
||||
// Store in log buffer
|
||||
if (c == '\n') log_write('\r'); |
||||
log_write(c); |
||||
} |
||||
|
||||
int ICACHE_FLASH_ATTR |
||||
ajaxLog(HttpdConnData *connData) { |
||||
char buff[2048]; |
||||
int len; // length of text in buff
|
||||
int log_len = (log_wr+BUF_MAX-log_rd) % BUF_MAX; // num chars in log_buf
|
||||
int start = 0; // offset onto log_wr to start sending out chars
|
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
jsonHeader(connData, 200); |
||||
|
||||
// figure out where to start in buffer based on URI param
|
||||
len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); |
||||
if (len > 0) { |
||||
start = atoi(buff); |
||||
if (start < log_pos) { |
||||
start = 0; |
||||
} else if (start >= log_pos+log_len) { |
||||
start = log_len; |
||||
} else { |
||||
start = start - log_pos; |
||||
} |
||||
} |
||||
|
||||
// start outputting
|
||||
len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", |
||||
log_len-start, log_pos+start); |
||||
|
||||
int rd = (log_rd+start) % BUF_MAX; |
||||
while (len < 2040 && rd != log_wr) { |
||||
uint8_t c = log_buf[rd]; |
||||
if (c == '\\' || c == '"') { |
||||
buff[len++] = '\\'; |
||||
buff[len++] = c; |
||||
} else if (c < ' ') { |
||||
len += os_sprintf(buff+len, "\\u%04x", c); |
||||
} else { |
||||
buff[len++] = c; |
||||
} |
||||
rd = (rd + 1) % BUF_MAX; |
||||
} |
||||
os_strcpy(buff+len, "\"}"); len+=2; |
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static char *dbg_mode[] = { "auto", "off", "on" }; |
||||
|
||||
int ICACHE_FLASH_ATTR |
||||
ajaxLogDbg(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
char buff[512]; |
||||
int len, status = 400; |
||||
len = httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); |
||||
if (len > 0) { |
||||
int8_t mode = -1; |
||||
if (os_strcmp(buff, "auto") == 0) mode = LOG_MODE_AUTO; |
||||
if (os_strcmp(buff, "off") == 0) mode = LOG_MODE_OFF; |
||||
if (os_strcmp(buff, "on") == 0) mode = LOG_MODE_ON; |
||||
if (mode >= 0) { |
||||
flashConfig.log_mode = mode; |
||||
if (mode != LOG_MODE_AUTO) log_uart(mode == LOG_MODE_ON); |
||||
status = configSave() ? 200 : 400; |
||||
} |
||||
} else if (connData->requestType == HTTPD_METHOD_GET) { |
||||
status = 200; |
||||
} |
||||
|
||||
jsonHeader(connData, status); |
||||
os_sprintf(buff, "{\"mode\": \"%s\"}", dbg_mode[flashConfig.log_mode]); |
||||
httpdSend(connData, buff, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR dumpMem(void *addr, int len) { |
||||
uint8_t *a = addr; |
||||
int off = 0; |
||||
while (off < len) { |
||||
os_printf("%p ", a); |
||||
for (int i = 0; i < 16 && off + i < len; i++) { |
||||
os_printf(" %02x", a[i]); |
||||
} |
||||
os_printf(" "); |
||||
for (int i=0; i<16 && off<len; i++,off++,a++) |
||||
os_printf("%c", *a > 0x20 && *a < 0x3f ? *a : '.'); |
||||
os_printf("\n"); |
||||
} |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR logInit() { |
||||
log_no_uart = flashConfig.log_mode == LOG_MODE_OFF; // ON unless set to always-off
|
||||
log_wr = 0; |
||||
log_rd = 0; |
||||
os_install_putc1((void *)log_write_char); |
||||
} |
||||
|
||||
|
@ -0,0 +1,179 @@ |
||||
/*
|
||||
* ---------------------------------------------------------------------------- |
||||
* "THE BEER-WARE LICENSE" (Revision 42): |
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain |
||||
* this notice you can do whatever you want with this stuff. If we meet some day, |
||||
* and you think this stuff is worth it, you can buy me a beer in return. |
||||
* ---------------------------------------------------------------------------- |
||||
* Heavily modified and enhanced by Thorsten von Eicken in 2015 |
||||
* ---------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
|
||||
#include <esp8266.h> |
||||
#include "httpd.h" |
||||
#include "httpdespfs.h" |
||||
#include "cgi.h" |
||||
#include "cgiwifi.h" |
||||
#include "cgipins.h" |
||||
#include "cgitcp.h" |
||||
#include "cgimqtt.h" |
||||
#include "cgiflash.h" |
||||
#include "auth.h" |
||||
#include "espfs.h" |
||||
#include "uart.h" |
||||
#include "serbridge.h" |
||||
#include "status.h" |
||||
#include "serled.h" |
||||
#include "console.h" |
||||
#include "config.h" |
||||
#include "log.h" |
||||
#include <gpio.h> |
||||
|
||||
/*
|
||||
This is the main url->function dispatching data struct. |
||||
In short, it's a struct with various URLs plus their handlers. The handlers can |
||||
be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument. |
||||
They can also be auth-functions. An asterisk will match any url starting with |
||||
everything before the asterisks; "*" matches everything. The list will be |
||||
handled top-down, so make sure to put more specific rules above the more |
||||
general ones. Authorization things (like authBasic) act as a 'barrier' and |
||||
should be placed above the URLs they protect. |
||||
*/ |
||||
HttpdBuiltInUrl builtInUrls[] = { |
||||
{ "/", cgiRedirect, "/home.html" }, |
||||
{ "/menu", cgiMenu, NULL }, |
||||
{ "/flash/next", cgiGetFirmwareNext, NULL }, |
||||
{ "/flash/upload", cgiUploadFirmware, NULL }, |
||||
{ "/flash/reboot", cgiRebootFirmware, NULL }, |
||||
{ "/log/text", ajaxLog, NULL }, |
||||
{ "/log/dbg", ajaxLogDbg, NULL }, |
||||
{ "/console/reset", ajaxConsoleReset, NULL }, |
||||
{ "/console/baud", ajaxConsoleBaud, NULL }, |
||||
{ "/console/text", ajaxConsole, NULL }, |
||||
//Enable the line below to protect the WiFi configuration with an username/password combo.
|
||||
// {"/wifi/*", authBasic, myPassFn},
|
||||
{ "/wifi", cgiRedirect, "/wifi/wifi.html" }, |
||||
{ "/wifi/", cgiRedirect, "/wifi/wifi.html" }, |
||||
{ "/wifi/info", cgiWifiInfo, NULL }, |
||||
{ "/wifi/scan", cgiWiFiScan, NULL }, |
||||
{ "/wifi/connect", cgiWiFiConnect, NULL }, |
||||
{ "/wifi/connstatus", cgiWiFiConnStatus, NULL }, |
||||
{ "/wifi/setmode", cgiWiFiSetMode, NULL }, |
||||
{ "/wifi/special", cgiWiFiSpecial, NULL }, |
||||
{ "/pins", cgiPins, NULL }, |
||||
{ "/tcpclient", cgiTcp, NULL }, |
||||
#ifdef MQTT |
||||
{ "/mqtt", cgiMqtt, NULL }, |
||||
#endif |
||||
|
||||
{ "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem
|
||||
{ NULL, NULL, NULL } |
||||
}; |
||||
|
||||
#ifdef SHOW_HEAP_USE |
||||
static ETSTimer prHeapTimer; |
||||
|
||||
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) { |
||||
os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size()); |
||||
} |
||||
#endif |
||||
|
||||
# define VERS_STR_STR(V) #V |
||||
# define VERS_STR(V) VERS_STR_STR(V) |
||||
char* esp_link_version = VERS_STR(VERSION); |
||||
|
||||
// address of espfs binary blob
|
||||
extern uint32_t _binary_espfs_img_start; |
||||
|
||||
static char *rst_codes[] = { |
||||
"normal", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "external", |
||||
}; |
||||
static char *flash_maps[] = { |
||||
"512KB:256/256", "256KB", "1MB:512/512", "2MB:512/512", "4MB:512/512", |
||||
"2MB:1024/1024", "4MB:1024/1024" |
||||
}; |
||||
|
||||
extern void app_init(void); |
||||
extern void mqtt_client_init(void); |
||||
|
||||
void user_rf_pre_init(void) { |
||||
//default is enabled
|
||||
system_set_os_print(DEBUG_SDK); |
||||
} |
||||
|
||||
// Main routine to initialize esp-link.
|
||||
void user_init(void) { |
||||
// get the flash config so we know how to init things
|
||||
//configWipe(); // uncomment to reset the config for testing purposes
|
||||
bool restoreOk = configRestore(); |
||||
// init gpio pin registers
|
||||
gpio_init(); |
||||
gpio_output_set(0, 0, 0, (1<<15)); // some people tie it GND, gotta ensure it's disabled
|
||||
// init UART
|
||||
uart_init(flashConfig.baud_rate, 115200); |
||||
logInit(); // must come after init of uart
|
||||
// say hello (leave some time to cause break in TX after boot loader's msg
|
||||
os_delay_us(10000L); |
||||
os_printf("\n\n** %s\n", esp_link_version); |
||||
os_printf("Flash config restore %s\n", restoreOk ? "ok" : "*FAILED*"); |
||||
|
||||
#if defined(STA_SSID) && defined(STA_PASS) |
||||
int x = wifi_get_opmode() & 0x3; |
||||
if (x == 2) { |
||||
// we only force the STA settings when a full flash of the module has been made, which
|
||||
// resets the wifi settings not to have anything configured
|
||||
struct station_config stconf; |
||||
wifi_station_get_config(&stconf); |
||||
|
||||
if (os_strlen((char*)stconf.ssid) == 0 && os_strlen((char*)stconf.password) == 0) { |
||||
os_strncpy((char*)stconf.ssid, VERS_STR(STA_SSID), 32); |
||||
os_strncpy((char*)stconf.password, VERS_STR(STA_PASS), 64); |
||||
#ifdef CGIWIFI_DBG |
||||
os_printf("Wifi pre-config trying to connect to AP %s pw %s\n", |
||||
(char*)stconf.ssid, (char*)stconf.password); |
||||
#endif |
||||
wifi_set_opmode(3); // sta+ap, will switch to sta-only 15 secs after connecting
|
||||
stconf.bssid_set = 0; |
||||
wifi_station_set_config(&stconf); |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
// Status LEDs
|
||||
statusInit(); |
||||
serledInit(); |
||||
// Wifi
|
||||
wifiInit(); |
||||
|
||||
// init the flash filesystem with the html stuff
|
||||
espFsInit(&_binary_espfs_img_start); |
||||
//EspFsInitResult res = espFsInit(&_binary_espfs_img_start);
|
||||
//os_printf("espFsInit %s\n", res?"ERR":"ok");
|
||||
// mount the http handlers
|
||||
httpdInit(builtInUrls, 80); |
||||
// init the wifi-serial transparent bridge (port 23)
|
||||
serbridgeInit(23, 2323); |
||||
uart_add_recv_cb(&serbridgeUartCb); |
||||
#ifdef SHOW_HEAP_USE |
||||
os_timer_disarm(&prHeapTimer); |
||||
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); |
||||
os_timer_arm(&prHeapTimer, 10000, 1); |
||||
#endif |
||||
|
||||
struct rst_info *rst_info = system_get_rst_info(); |
||||
os_printf("Reset cause: %d=%s\n", rst_info->reason, rst_codes[rst_info->reason]); |
||||
os_printf("exccause=%d epc1=0x%x epc2=0x%x epc3=0x%x excvaddr=0x%x depc=0x%x\n", |
||||
rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3, |
||||
rst_info->excvaddr, rst_info->depc); |
||||
uint32_t fid = spi_flash_get_id(); |
||||
os_printf("Flash map %s, manuf 0x%02lX chip 0x%04lX\n", flash_maps[system_get_flash_size_map()], |
||||
fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); |
||||
|
||||
os_printf("** esp-link ready\n"); |
||||
#ifdef MQTT |
||||
mqtt_client_init(); |
||||
#endif |
||||
|
||||
app_init(); |
||||
} |
@ -0,0 +1,150 @@ |
||||
#ifdef MQTT |
||||
#include <esp8266.h> |
||||
#include "cgiwifi.h" |
||||
#include "config.h" |
||||
#include "mqtt.h" |
||||
|
||||
#ifdef MQTTCLIENT_DBG |
||||
#define DBG_MQTTCLIENT(format, ...) os_printf(format, ## __VA_ARGS__) |
||||
#else |
||||
#define DBG_MQTTCLIENT(format, ...) do { } while(0) |
||||
#endif |
||||
|
||||
MQTT_Client mqttClient; // main mqtt client used by esp-link
|
||||
|
||||
#ifdef BRUNNELS |
||||
char* statusTopicStr; |
||||
static char* onlineMsgStr; |
||||
#endif |
||||
|
||||
static MqttCallback connected_cb; |
||||
static MqttCallback disconnected_cb; |
||||
static MqttCallback published_cb; |
||||
static MqttDataCallback data_cb; |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqttConnectedCb(uint32_t *args) { |
||||
DBG_MQTTCLIENT("MQTT Client: Connected\n"); |
||||
//MQTT_Client* client = (MQTT_Client*)args;
|
||||
//MQTT_Subscribe(client, "system/time", 0); // handy for testing
|
||||
#ifdef BRUNNELS |
||||
MQTT_Publish(client, "announce/all", onlineMsgStr, 0, 0); |
||||
#endif |
||||
if (connected_cb) |
||||
connected_cb(args); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqttDisconnectedCb(uint32_t *args) { |
||||
// MQTT_Client* client = (MQTT_Client*)args;
|
||||
DBG_MQTTCLIENT("MQTT Client: Disconnected\n"); |
||||
if (disconnected_cb) |
||||
disconnected_cb(args); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqttPublishedCb(uint32_t *args) { |
||||
// MQTT_Client* client = (MQTT_Client*)args;
|
||||
DBG_MQTTCLIENT("MQTT Client: Published\n"); |
||||
if (published_cb) |
||||
published_cb(args); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqttDataCb(uint32_t *args, const char* topic, uint32_t topic_len, const char *data, uint32_t data_len) { |
||||
// MQTT_Client* client = (MQTT_Client*)args;
|
||||
|
||||
#ifdef MQTTCLIENT_DBG |
||||
char *topicBuf = (char*)os_zalloc(topic_len + 1); |
||||
char *dataBuf = (char*)os_zalloc(data_len + 1); |
||||
|
||||
os_memcpy(topicBuf, topic, topic_len); |
||||
topicBuf[topic_len] = 0; |
||||
|
||||
os_memcpy(dataBuf, data, data_len); |
||||
dataBuf[data_len] = 0; |
||||
|
||||
os_printf("MQTT Client: Received topic: %s, data: %s\n", topicBuf, dataBuf); |
||||
os_free(topicBuf); |
||||
os_free(dataBuf); |
||||
#endif |
||||
|
||||
if (data_cb) |
||||
data_cb(args, topic, topic_len, data, data_len); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
wifiStateChangeCb(uint8_t status) |
||||
{ |
||||
if (flashConfig.mqtt_enable) { |
||||
if (status == wifiGotIP && mqttClient.connState != TCP_CONNECTING) { |
||||
MQTT_Connect(&mqttClient); |
||||
} |
||||
else if (status == wifiIsDisconnected && mqttClient.connState == TCP_CONNECTING) { |
||||
MQTT_Disconnect(&mqttClient); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqtt_client_init() |
||||
{ |
||||
MQTT_Init(&mqttClient, flashConfig.mqtt_host, flashConfig.mqtt_port, 0, flashConfig.mqtt_timeout, |
||||
flashConfig.mqtt_clientid, flashConfig.mqtt_username, flashConfig.mqtt_password, |
||||
flashConfig.mqtt_keepalive); |
||||
|
||||
// removed client_id concat for now until a better solution is devised
|
||||
// statusTopicStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(flashConfig.mqtt_status_topic) + 2);
|
||||
// os_strcpy(statusTopicStr, flashConfig.mqtt_clientid);
|
||||
// os_strcat(statusTopicStr, "/");
|
||||
|
||||
#ifdef BRUNNELS |
||||
char* onlineMsg = " is online"; |
||||
onlineMsgStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(onlineMsg) + 1); |
||||
os_strcpy(onlineMsgStr, flashConfig.mqtt_clientid); |
||||
os_strcat(onlineMsgStr, onlineMsg); |
||||
|
||||
char* offlineMsg = " is offline"; |
||||
char* offlineMsgStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(offlineMsg) + 1); |
||||
os_strcpy(offlineMsgStr, flashConfig.mqtt_clientid); |
||||
os_strcat(offlineMsgStr, offlineMsg); |
||||
|
||||
char* lwt = "/lwt"; |
||||
char *lwtMsgStr = (char*)os_zalloc(strlen(flashConfig.mqtt_clientid) + strlen(lwt) + 1); |
||||
os_strcpy(lwtMsgStr, flashConfig.mqtt_clientid); |
||||
os_strcat(lwtMsgStr, lwt); |
||||
MQTT_InitLWT(&mqttClient, lwtMsgStr, offlineMsg, 0, 0); |
||||
#endif |
||||
|
||||
MQTT_OnConnected(&mqttClient, mqttConnectedCb); |
||||
MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb); |
||||
MQTT_OnPublished(&mqttClient, mqttPublishedCb); |
||||
MQTT_OnData(&mqttClient, mqttDataCb); |
||||
|
||||
if (flashConfig.mqtt_enable && strlen(flashConfig.mqtt_host) > 0) |
||||
MQTT_Connect(&mqttClient); |
||||
|
||||
wifiAddStateChangeCb(wifiStateChangeCb); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqtt_client_on_connected(MqttCallback connectedCb) { |
||||
connected_cb = connectedCb; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqtt_client_on_disconnected(MqttCallback disconnectedCb) { |
||||
disconnected_cb = disconnectedCb; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqtt_client_on_published(MqttCallback publishedCb) { |
||||
published_cb = publishedCb; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqtt_client_on_data(MqttDataCallback dataCb) { |
||||
data_cb = dataCb; |
||||
} |
||||
|
||||
#endif // MQTT
|
@ -0,0 +1,15 @@ |
||||
#ifdef MQTT |
||||
#ifndef MQTT_CLIENT_H |
||||
#define MQTT_CLIENT_H |
||||
#include "mqtt.h" |
||||
|
||||
extern MQTT_Client mqttClient; |
||||
extern char* statusTopicStr; |
||||
void mqtt_client_init(); |
||||
void mqtt_client_on_connected(MqttCallback connectedCb); |
||||
void mqtt_client_on_disconnected(MqttCallback disconnectedCb); |
||||
void mqtt_client_on_published(MqttCallback publishedCb); |
||||
void mqtt_client_on_data(MqttDataCallback dataCb); |
||||
|
||||
#endif //MQTT_CLIENT_H
|
||||
#endif // MQTT
|
@ -0,0 +1,129 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
|
||||
#include <esp8266.h> |
||||
#include "config.h" |
||||
#include "serled.h" |
||||
#include "cgiwifi.h" |
||||
|
||||
#ifdef MQTT |
||||
#include "mqtt.h" |
||||
#include "mqtt_client.h" |
||||
extern MQTT_Client mqttClient; |
||||
|
||||
//===== MQTT Status update
|
||||
|
||||
// Every minute...
|
||||
#define MQTT_STATUS_INTERVAL (60*1000) |
||||
|
||||
static ETSTimer mqttStatusTimer; |
||||
|
||||
int ICACHE_FLASH_ATTR |
||||
mqttStatusMsg(char *buf) { |
||||
sint8 rssi = wifi_station_get_rssi(); |
||||
if (rssi > 0) rssi = 0; // not connected or other error
|
||||
//os_printf("timer rssi=%d\n", rssi);
|
||||
|
||||
// compose MQTT message
|
||||
return os_sprintf(buf, |
||||
"{\"rssi\":%d, \"heap_free\":%ld}", |
||||
rssi, (unsigned long)system_get_free_heap_size()); |
||||
} |
||||
|
||||
// Timer callback to send an RSSI update to a monitoring system
|
||||
static void ICACHE_FLASH_ATTR mqttStatusCb(void *v) { |
||||
if (!flashConfig.mqtt_status_enable || os_strlen(flashConfig.mqtt_status_topic) == 0 || |
||||
mqttClient.connState != MQTT_CONNECTED) |
||||
return; |
||||
|
||||
char buf[128]; |
||||
mqttStatusMsg(buf); |
||||
MQTT_Publish(&mqttClient, flashConfig.mqtt_status_topic, buf, 1, 0); |
||||
} |
||||
|
||||
|
||||
|
||||
#endif // MQTT
|
||||
|
||||
//===== "CONN" LED status indication
|
||||
|
||||
static ETSTimer ledTimer; |
||||
|
||||
// Set the LED on or off, respecting the defined polarity
|
||||
static void ICACHE_FLASH_ATTR setLed(int on) { |
||||
int8_t pin = flashConfig.conn_led_pin; |
||||
if (pin < 0) return; // disabled
|
||||
// LED is active-low
|
||||
if (on) { |
||||
gpio_output_set(0, (1<<pin), (1<<pin), 0); |
||||
} else { |
||||
gpio_output_set((1<<pin), 0, (1<<pin), 0); |
||||
} |
||||
} |
||||
|
||||
static uint8_t ledState = 0; |
||||
|
||||
// Timer callback to update the LED
|
||||
static void ICACHE_FLASH_ATTR ledTimerCb(void *v) { |
||||
int time = 1000; |
||||
|
||||
if (wifiState == wifiGotIP) { |
||||
// connected, all is good, solid light with a short dark blip every 3 seconds
|
||||
ledState = 1-ledState; |
||||
time = ledState ? 2900 : 100; |
||||
} else if (wifiState == wifiIsConnected) { |
||||
// waiting for DHCP, go on/off every second
|
||||
ledState = 1 - ledState; |
||||
time = 1000; |
||||
} else { |
||||
// not connected
|
||||
switch (wifi_get_opmode()) { |
||||
case 1: // STA
|
||||
ledState = 0; |
||||
break; |
||||
case 2: // AP
|
||||
ledState = 1-ledState; |
||||
time = ledState ? 50 : 1950; |
||||
break; |
||||
case 3: // STA+AP
|
||||
ledState = 1-ledState; |
||||
time = ledState ? 50 : 950; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
setLed(ledState); |
||||
os_timer_arm(&ledTimer, time, 0); |
||||
} |
||||
|
||||
// change the wifi state indication
|
||||
void ICACHE_FLASH_ATTR statusWifiUpdate(uint8_t state) { |
||||
wifiState = state; |
||||
// schedule an update (don't want to run into concurrency issues)
|
||||
os_timer_disarm(&ledTimer); |
||||
os_timer_setfn(&ledTimer, ledTimerCb, NULL); |
||||
os_timer_arm(&ledTimer, 500, 0); |
||||
} |
||||
|
||||
//===== Init status stuff
|
||||
|
||||
void ICACHE_FLASH_ATTR statusInit(void) { |
||||
if (flashConfig.conn_led_pin >= 0) { |
||||
makeGpio(flashConfig.conn_led_pin); |
||||
setLed(1); |
||||
} |
||||
#ifdef STATUS_DBG |
||||
os_printf("CONN led=%d\n", flashConfig.conn_led_pin); |
||||
#endif |
||||
|
||||
os_timer_disarm(&ledTimer); |
||||
os_timer_setfn(&ledTimer, ledTimerCb, NULL); |
||||
os_timer_arm(&ledTimer, 2000, 0); |
||||
|
||||
#ifdef MQTT |
||||
os_timer_disarm(&mqttStatusTimer); |
||||
os_timer_setfn(&mqttStatusTimer, mqttStatusCb, NULL); |
||||
os_timer_arm(&mqttStatusTimer, MQTT_STATUS_INTERVAL, 1); // recurring timer
|
||||
#endif // MQTT
|
||||
} |
||||
|
||||
|
@ -1,6 +1,7 @@ |
||||
#ifndef STATUS_H |
||||
#define STATUS_H |
||||
|
||||
int mqttStatusMsg(char *buf); |
||||
void statusWifiUpdate(uint8_t state); |
||||
void statusInit(void); |
||||
|
@ -1,13 +0,0 @@ |
||||
CFLAGS=-I../../lib/heatshrink -I.. -std=gnu99 -DESPFS_HEATSHRINK
|
||||
|
||||
espfstest: main.o espfs.o heatshrink_decoder.o |
||||
$(CC) -o $@ $^
|
||||
|
||||
espfs.o: ../espfs.c |
||||
$(CC) $(CFLAGS) -c $^ -o $@
|
||||
|
||||
heatshrink_decoder.o: ../heatshrink_decoder.c |
||||
$(CC) $(CFLAGS) -c $^ -o $@
|
||||
|
||||
clean: |
||||
rm -f *.o espfstest
|
@ -1,67 +0,0 @@ |
||||
/*
|
||||
Simple and stupid file decompressor for an espfs image. Mostly used as a testbed for espfs.c and
|
||||
the decompressors: code compiled natively is way easier to debug using gdb et all :) |
||||
*/ |
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <sys/mman.h> |
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <fcntl.h> |
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
|
||||
|
||||
#include "espfs.h" |
||||
|
||||
char *espFsData; |
||||
|
||||
int main(int argc, char **argv) { |
||||
int f, out; |
||||
int len; |
||||
char buff[128]; |
||||
EspFsFile *ef; |
||||
off_t size; |
||||
EspFsInitResult ir; |
||||
|
||||
if (argc!=3) { |
||||
printf("Usage: %s espfs-image file\nExpands file from the espfs-image archive.\n", argv[0]); |
||||
exit(0); |
||||
} |
||||
|
||||
f=open(argv[1], O_RDONLY); |
||||
if (f<=0) { |
||||
perror(argv[1]); |
||||
exit(1); |
||||
} |
||||
size=lseek(f, 0, SEEK_END); |
||||
espFsData=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); |
||||
if (espFsData==MAP_FAILED) { |
||||
perror("mmap"); |
||||
exit(1); |
||||
} |
||||
|
||||
ir=espFsInit(espFsData); |
||||
if (ir != ESPFS_INIT_RESULT_OK) { |
||||
printf("Couldn't init espfs filesystem (code %d)\n", ir); |
||||
exit(1); |
||||
} |
||||
|
||||
ef=espFsOpen(argv[2]); |
||||
if (ef==NULL) { |
||||
printf("Couldn't find %s in image.\n", argv[2]); |
||||
exit(1); |
||||
} |
||||
|
||||
out=open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644); |
||||
if (out<=0) { |
||||
perror(argv[2]); |
||||
exit(1); |
||||
} |
||||
|
||||
while ((len=espFsRead(ef, buff, 128))!=0) { |
||||
write(out, buff, len); |
||||
} |
||||
espFsClose(ef); |
||||
//munmap, close, ... I can't be bothered.
|
||||
} |
@ -1,30 +0,0 @@ |
||||
//Heatshrink config for the decompressor.
|
||||
#ifndef HEATSHRINK_CONFIG_H |
||||
#define HEATSHRINK_CONFIG_H |
||||
|
||||
/* Should functionality assuming dynamic allocation be used? */ |
||||
#define HEATSHRINK_DYNAMIC_ALLOC 1 |
||||
|
||||
#if HEATSHRINK_DYNAMIC_ALLOC |
||||
/* Optional replacement of malloc/free */ |
||||
#ifdef __ets__ |
||||
#define HEATSHRINK_MALLOC(SZ) os_malloc(SZ) |
||||
#define HEATSHRINK_FREE(P, SZ) os_free(P) |
||||
#else |
||||
#define HEATSHRINK_MALLOC(SZ) malloc(SZ) |
||||
#define HEATSHRINK_FREE(P, SZ) free(P) |
||||
#endif |
||||
#else |
||||
/* Required parameters for static configuration */ |
||||
#define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 |
||||
#define HEATSHRINK_STATIC_WINDOW_BITS 8 |
||||
#define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 |
||||
#endif |
||||
|
||||
/* Turn on logging for debugging. */ |
||||
#define HEATSHRINK_DEBUGGING_LOGS 0 |
||||
|
||||
/* Use indexing for faster compression. (This requires additional space.) */ |
||||
#define HEATSHRINK_USE_INDEX 1 |
||||
|
||||
#endif |
@ -1,19 +0,0 @@ |
||||
#include "espfs.h" |
||||
#ifdef ESPFS_HEATSHRINK |
||||
//Stupid wrapper so we don't have to move c-files around
|
||||
//Also loads httpd-specific config.
|
||||
|
||||
#ifdef __ets__ |
||||
//esp build
|
||||
|
||||
#include <esp8266.h> |
||||
|
||||
#define memset(x,y,z) os_memset(x,y,z) |
||||
#define memcpy(x,y,z) os_memcpy(x,y,z) |
||||
#endif |
||||
|
||||
#include "heatshrink_config_custom.h" |
||||
#include "../lib/heatshrink/heatshrink_decoder.c" |
||||
|
||||
|
||||
#endif |
@ -1,4 +0,0 @@ |
||||
//Stupid wraparound include to make sure object file doesn't end up in heatshrink dir
|
||||
#ifdef ESPFS_HEATSHRINK |
||||
#include "../lib/heatshrink/heatshrink_encoder.c" |
||||
#endif |
@ -0,0 +1,48 @@ |
||||
#
|
||||
# mman-win32 (mingw32) Makefile
|
||||
#
|
||||
include config.mak |
||||
|
||||
ifeq ($(BUILD_STATIC),yes) |
||||
TARGETS+=libmman.a
|
||||
INSTALL+=static-install
|
||||
endif |
||||
ifeq ($(BUILD_MSVC),yes) |
||||
SHFLAGS+=-Wl,--output-def,libmman.def
|
||||
INSTALL+=lib-install
|
||||
endif |
||||
|
||||
all: $(TARGETS) |
||||
|
||||
mman.o: mman.c mman.h |
||||
$(CC) -o mman.o -c mman.c -Wall -O3 -fomit-frame-pointer
|
||||
|
||||
libmman.a: mman.o |
||||
$(AR) cru libmman.a mman.o
|
||||
$(RANLIB) libmman.a
|
||||
|
||||
static-install: |
||||
mkdir -p $(DESTDIR)$(libdir)
|
||||
cp libmman.a $(DESTDIR)$(libdir)
|
||||
mkdir -p $(DESTDIR)$(incdir)
|
||||
cp mman.h $(DESTDIR)$(incdir)
|
||||
|
||||
lib-install: |
||||
mkdir -p $(DESTDIR)$(libdir)
|
||||
cp libmman.lib $(DESTDIR)$(libdir)
|
||||
|
||||
install: $(INSTALL) |
||||
|
||||
test.exe: test.c mman.c mman.h |
||||
$(CC) -o test.exe test.c -L. -lmman
|
||||
|
||||
test: $(TARGETS) test.exe |
||||
test.exe
|
||||
|
||||
clean:: |
||||
rm -f mman.o libmman.a libmman.def libmman.lib test.exe *.dat
|
||||
|
||||
distclean: clean |
||||
rm -f config.mak
|
||||
|
||||
.PHONY: clean distclean install test |
@ -0,0 +1,11 @@ |
||||
# Automatically generated by configure
|
||||
PREFIX=/mingw
|
||||
libdir=/mingw/lib
|
||||
incdir=/mingw/include/sys
|
||||
AR=ar
|
||||
CC=gcc
|
||||
RANLIB=ranlib
|
||||
STRIP=strip
|
||||
BUILD_STATIC=yes
|
||||
BUILD_MSVC=
|
||||
LIBCMD=echo ignoring lib
|
@ -0,0 +1,157 @@ |
||||
#!/bin/sh |
||||
# mmap-win32 configure script |
||||
# |
||||
# Parts copied from FFmpeg's configure |
||||
# |
||||
|
||||
set_all(){ |
||||
value=$1 |
||||
shift |
||||
for var in $*; do |
||||
eval $var=$value |
||||
done |
||||
} |
||||
|
||||
enable(){ |
||||
set_all yes $* |
||||
} |
||||
|
||||
disable(){ |
||||
set_all no $* |
||||
} |
||||
|
||||
enabled(){ |
||||
eval test "x\$$1" = "xyes" |
||||
} |
||||
|
||||
disabled(){ |
||||
eval test "x\$$1" = "xno" |
||||
} |
||||
|
||||
show_help(){ |
||||
echo "Usage: configure [options]" |
||||
echo "Options: [defaults in brackets after descriptions]" |
||||
echo "All \"enable\" options have \"disable\" counterparts" |
||||
echo |
||||
echo " --help print this message" |
||||
echo " --prefix=PREFIX install in PREFIX [$PREFIX]" |
||||
echo " --libdir=DIR install libs in DIR [$PREFIX/lib]" |
||||
echo " --incdir=DIR install includes in DIR [$PREFIX/include]" |
||||
echo " --enable-static build static libraries [yes]" |
||||
echo " --enable-msvc create msvc-compatible import lib [auto]" |
||||
echo |
||||
echo " --cc=CC use C compiler CC [$cc_default]" |
||||
echo " --cross-prefix=PREFIX use PREFIX for compilation tools [$cross_prefix]" |
||||
exit 1 |
||||
} |
||||
|
||||
die_unknown(){ |
||||
echo "Unknown option \"$1\"." |
||||
echo "See $0 --help for available options." |
||||
exit 1 |
||||
} |
||||
|
||||
PREFIX="/mingw" |
||||
libdir="${PREFIX}/lib" |
||||
incdir="${PREFIX}/include/sys" |
||||
ar="ar" |
||||
cc_default="gcc" |
||||
ranlib="ranlib" |
||||
strip="strip" |
||||
|
||||
DEFAULT="msvc |
||||
" |
||||
|
||||
DEFAULT_YES="static |
||||
stripping |
||||
" |
||||
|
||||
CMDLINE_SELECT="$DEFAULT |
||||
$DEFAULT_NO |
||||
$DEFAULT_YES |
||||
" |
||||
|
||||
enable $DEFAULT_YES |
||||
disable $DEFAULT_NO |
||||
|
||||
for opt do |
||||
optval="${opt#*=}" |
||||
case "$opt" in |
||||
--help) |
||||
show_help |
||||
;; |
||||
--prefix=*) |
||||
PREFIX="$optval" |
||||
;; |
||||
--libdir=*) |
||||
libdir="$optval" |
||||
;; |
||||
--incdir=*) |
||||
incdir="$optval" |
||||
;; |
||||
--cc=*) |
||||
cc="$optval" |
||||
;; |
||||
--cross-prefix=*) |
||||
cross_prefix="$optval" |
||||
;; |
||||
--enable-?*|--disable-?*) |
||||
eval `echo "$opt" | sed 's/--/action=/;s/-/ option=/;s/-/_/g'` |
||||
echo "$CMDLINE_SELECT" | grep -q "^ *$option\$" || die_unknown $opt |
||||
$action $option |
||||
;; |
||||
*) |
||||
die_unknown $opt |
||||
;; |
||||
esac |
||||
done |
||||
|
||||
ar="${cross_prefix}${ar}" |
||||
cc_default="${cross_prefix}${cc_default}" |
||||
ranlib="${cross_prefix}${ranlib}" |
||||
strip="${cross_prefix}${strip}" |
||||
|
||||
if ! test -z $cc; then |
||||
cc_default="${cc}" |
||||
fi |
||||
cc="${cc_default}" |
||||
|
||||
disabled static && { |
||||
echo "At least one library type must be set."; |
||||
exit 1; |
||||
} |
||||
|
||||
if enabled msvc; then |
||||
lib /? > /dev/null 2>&1 /dev/null || { |
||||
echo "MSVC's lib command not found." |
||||
echo "Make sure MSVC is installed and its bin folder is in your \$PATH." |
||||
exit 1 |
||||
} |
||||
fi |
||||
|
||||
if ! enabled stripping; then |
||||
strip="echo ignoring strip" |
||||
fi |
||||
|
||||
enabled msvc && libcmd="lib" || libcmd="echo ignoring lib" |
||||
|
||||
echo "# Automatically generated by configure" > config.mak |
||||
echo "PREFIX=$PREFIX" >> config.mak |
||||
echo "libdir=$libdir" >> config.mak |
||||
echo "incdir=$incdir" >> config.mak |
||||
echo "AR=$ar" >> config.mak |
||||
echo "CC=$cc" >> config.mak |
||||
echo "RANLIB=$ranlib" >> config.mak |
||||
echo "STRIP=$strip" >> config.mak |
||||
echo "BUILD_STATIC=$static" >> config.mak |
||||
echo "BUILD_MSVC=$msvc" >> config.mak |
||||
echo "LIBCMD=$libcmd" >> config.mak |
||||
|
||||
echo "prefix: $PREFIX" |
||||
echo "libdir: $libdir" |
||||
echo "incdir: $incdir" |
||||
echo "ar: $ar" |
||||
echo "cc: $cc" |
||||
echo "ranlib: $ranlib" |
||||
echo "strip: $strip" |
||||
echo "static: $static" |
@ -0,0 +1,180 @@ |
||||
|
||||
#include <windows.h> |
||||
#include <errno.h> |
||||
#include <io.h> |
||||
|
||||
#include "mman.h" |
||||
|
||||
#ifndef FILE_MAP_EXECUTE |
||||
#define FILE_MAP_EXECUTE 0x0020 |
||||
#endif /* FILE_MAP_EXECUTE */ |
||||
|
||||
static int __map_mman_error(const DWORD err, const int deferr) |
||||
{ |
||||
if (err == 0) |
||||
return 0; |
||||
//TODO: implement
|
||||
return err; |
||||
} |
||||
|
||||
static DWORD __map_mmap_prot_page(const int prot) |
||||
{ |
||||
DWORD protect = 0; |
||||
|
||||
if (prot == PROT_NONE) |
||||
return protect; |
||||
|
||||
if ((prot & PROT_EXEC) != 0) |
||||
{ |
||||
protect = ((prot & PROT_WRITE) != 0) ?
|
||||
PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; |
||||
} |
||||
else |
||||
{ |
||||
protect = ((prot & PROT_WRITE) != 0) ? |
||||
PAGE_READWRITE : PAGE_READONLY; |
||||
} |
||||
|
||||
return protect; |
||||
} |
||||
|
||||
static DWORD __map_mmap_prot_file(const int prot) |
||||
{ |
||||
DWORD desiredAccess = 0; |
||||
|
||||
if (prot == PROT_NONE) |
||||
return desiredAccess; |
||||
|
||||
if ((prot & PROT_READ) != 0) |
||||
desiredAccess |= FILE_MAP_READ; |
||||
if ((prot & PROT_WRITE) != 0) |
||||
desiredAccess |= FILE_MAP_WRITE; |
||||
if ((prot & PROT_EXEC) != 0) |
||||
desiredAccess |= FILE_MAP_EXECUTE; |
||||
|
||||
return desiredAccess; |
||||
} |
||||
|
||||
void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) |
||||
{ |
||||
HANDLE fm, h; |
||||
|
||||
void * map = MAP_FAILED; |
||||
|
||||
#ifdef _MSC_VER |
||||
#pragma warning(push) |
||||
#pragma warning(disable: 4293) |
||||
#endif |
||||
|
||||
const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ?
|
||||
(DWORD)off : (DWORD)(off & 0xFFFFFFFFL); |
||||
const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ? |
||||
(DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL); |
||||
const DWORD protect = __map_mmap_prot_page(prot); |
||||
const DWORD desiredAccess = __map_mmap_prot_file(prot); |
||||
|
||||
const off_t maxSize = off + (off_t)len; |
||||
|
||||
const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ?
|
||||
(DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL); |
||||
const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ? |
||||
(DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL); |
||||
|
||||
#ifdef _MSC_VER |
||||
#pragma warning(pop) |
||||
#endif |
||||
|
||||
errno = 0; |
||||
|
||||
if (len == 0
|
||||
/* Unsupported flag combinations */ |
||||
|| (flags & MAP_FIXED) != 0 |
||||
/* Usupported protection combinations */ |
||||
|| prot == PROT_EXEC) |
||||
{ |
||||
errno = EINVAL; |
||||
return MAP_FAILED; |
||||
} |
||||
|
||||
h = ((flags & MAP_ANONYMOUS) == 0) ?
|
||||
(HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE; |
||||
|
||||
if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) |
||||
{ |
||||
errno = EBADF; |
||||
return MAP_FAILED; |
||||
} |
||||
|
||||
fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL); |
||||
|
||||
if (fm == NULL) |
||||
{ |
||||
errno = __map_mman_error(GetLastError(), EPERM); |
||||
return MAP_FAILED; |
||||
} |
||||
|
||||
map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len); |
||||
|
||||
CloseHandle(fm); |
||||
|
||||
if (map == NULL) |
||||
{ |
||||
errno = __map_mman_error(GetLastError(), EPERM); |
||||
return MAP_FAILED; |
||||
} |
||||
|
||||
return map; |
||||
} |
||||
|
||||
int munmap(void *addr, size_t len) |
||||
{ |
||||
if (UnmapViewOfFile(addr)) |
||||
return 0; |
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM); |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
int mprotect(void *addr, size_t len, int prot) |
||||
{ |
||||
DWORD newProtect = __map_mmap_prot_page(prot); |
||||
DWORD oldProtect = 0; |
||||
|
||||
if (VirtualProtect(addr, len, newProtect, &oldProtect)) |
||||
return 0; |
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM); |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
int msync(void *addr, size_t len, int flags) |
||||
{ |
||||
if (FlushViewOfFile(addr, len)) |
||||
return 0; |
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM); |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
int mlock(const void *addr, size_t len) |
||||
{ |
||||
if (VirtualLock((LPVOID)addr, len)) |
||||
return 0; |
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM); |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
int munlock(const void *addr, size_t len) |
||||
{ |
||||
if (VirtualUnlock((LPVOID)addr, len)) |
||||
return 0; |
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM); |
||||
|
||||
return -1; |
||||
} |
@ -0,0 +1,55 @@ |
||||
/*
|
||||
* sys/mman.h |
||||
* mman-win32 |
||||
*/ |
||||
|
||||
#ifndef _SYS_MMAN_H_ |
||||
#define _SYS_MMAN_H_ |
||||
|
||||
#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
|
||||
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows.
|
||||
#endif |
||||
|
||||
/* All the headers include this file. */ |
||||
#ifndef _MSC_VER |
||||
#include <_mingw.h> |
||||
#endif |
||||
|
||||
#include <sys/types.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#define PROT_NONE 0 |
||||
#define PROT_READ 1 |
||||
#define PROT_WRITE 2 |
||||
#define PROT_EXEC 4 |
||||
|
||||
#define MAP_FILE 0 |
||||
#define MAP_SHARED 1 |
||||
#define MAP_PRIVATE 2 |
||||
#define MAP_TYPE 0xf |
||||
#define MAP_FIXED 0x10 |
||||
#define MAP_ANONYMOUS 0x20 |
||||
#define MAP_ANON MAP_ANONYMOUS |
||||
|
||||
#define MAP_FAILED ((void *)-1) |
||||
|
||||
/* Flags for msync. */ |
||||
#define MS_ASYNC 1 |
||||
#define MS_SYNC 2 |
||||
#define MS_INVALIDATE 4 |
||||
|
||||
void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); |
||||
int munmap(void *addr, size_t len); |
||||
int mprotect(void *addr, size_t len, int prot); |
||||
int msync(void *addr, size_t len, int flags); |
||||
int mlock(const void *addr, size_t len); |
||||
int munlock(const void *addr, size_t len); |
||||
|
||||
#ifdef __cplusplus |
||||
}; |
||||
#endif |
||||
|
||||
#endif /* _SYS_MMAN_H_ */ |
@ -0,0 +1,235 @@ |
||||
|
||||
#include "mman.h" |
||||
|
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <sys/stat.h> |
||||
|
||||
#ifndef NULL |
||||
#define NULL (void*)0 |
||||
#endif |
||||
|
||||
const char* map_file_name = "map_file.dat"; |
||||
|
||||
int test_anon_map_readwrite() |
||||
{
|
||||
void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE, |
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
||||
if (map == MAP_FAILED) |
||||
{ |
||||
printf("mmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno); |
||||
return -1; |
||||
} |
||||
|
||||
*((unsigned char*)map) = 1; |
||||
|
||||
int result = munmap(map, 1024); |
||||
|
||||
if (result != 0) |
||||
printf("munmap (MAP_ANONYMOUS, PROT_READ | PROT_WRITE) returned unexpected error: %d\n", errno); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
int test_anon_map_readonly() |
||||
{
|
||||
void* map = mmap(NULL, 1024, PROT_READ, |
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
||||
if (map == MAP_FAILED) |
||||
{ |
||||
printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); |
||||
return -1; |
||||
} |
||||
|
||||
*((unsigned char*)map) = 1; |
||||
|
||||
int result = munmap(map, 1024); |
||||
|
||||
if (result != 0) |
||||
printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
int test_anon_map_writeonly() |
||||
{
|
||||
void* map = mmap(NULL, 1024, PROT_WRITE, |
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
||||
if (map == MAP_FAILED) |
||||
{ |
||||
printf("mmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno); |
||||
return -1; |
||||
} |
||||
|
||||
*((unsigned char*)map) = 1; |
||||
|
||||
int result = munmap(map, 1024); |
||||
|
||||
if (result != 0) |
||||
printf("munmap (MAP_ANONYMOUS, PROT_WRITE) returned unexpected error: %d\n", errno); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
int test_anon_map_readonly_nowrite() |
||||
{
|
||||
void* map = mmap(NULL, 1024, PROT_READ, |
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
||||
if (map == MAP_FAILED) |
||||
{ |
||||
printf("mmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); |
||||
return -1; |
||||
} |
||||
|
||||
if (*((unsigned char*)map) != 0) |
||||
printf("test_anon_map_readonly_nowrite (MAP_ANONYMOUS, PROT_READ) returned unexpected value: %d\n",
|
||||
(int)*((unsigned char*)map)); |
||||
|
||||
int result = munmap(map, 1024); |
||||
|
||||
if (result != 0) |
||||
printf("munmap (MAP_ANONYMOUS, PROT_READ) returned unexpected error: %d\n", errno); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
int test_file_map_readwrite() |
||||
{ |
||||
mode_t mode = S_IRUSR | S_IWUSR; |
||||
int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); |
||||
|
||||
void* map = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); |
||||
if (map == MAP_FAILED) |
||||
{ |
||||
printf("mmap returned unexpected error: %d\n", errno); |
||||
return -1; |
||||
} |
||||
|
||||
*((unsigned char*)map) = 1; |
||||
|
||||
int result = munmap(map, 1024); |
||||
|
||||
if (result != 0) |
||||
printf("munmap returned unexpected error: %d\n", errno); |
||||
|
||||
close(o); |
||||
|
||||
/*TODO: get file info and content and compare it with the sources conditions */ |
||||
unlink(map_file_name); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
int test_file_map_mlock_munlock() |
||||
{ |
||||
const size_t map_size = 1024; |
||||
|
||||
int result = 0; |
||||
mode_t mode = S_IRUSR | S_IWUSR; |
||||
int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); |
||||
if (o == -1) |
||||
{ |
||||
printf("unable to create file %s: %d\n", map_file_name, errno); |
||||
return -1; |
||||
} |
||||
|
||||
void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); |
||||
if (map == MAP_FAILED) |
||||
{ |
||||
printf("mmap returned unexpected error: %d\n", errno); |
||||
result = -1; |
||||
goto done_close; |
||||
} |
||||
|
||||
if (mlock(map, map_size) != 0) |
||||
{ |
||||
printf("mlock returned unexpected error: %d\n", errno); |
||||
result = -1; |
||||
goto done_munmap;
|
||||
} |
||||
|
||||
*((unsigned char*)map) = 1; |
||||
|
||||
if (munlock(map, map_size) != 0) |
||||
{ |
||||
printf("munlock returned unexpected error: %d\n", errno); |
||||
result = -1; |
||||
} |
||||
|
||||
done_munmap: |
||||
result = munmap(map, map_size); |
||||
|
||||
if (result != 0) |
||||
printf("munmap returned unexpected error: %d\n", errno); |
||||
|
||||
done_close: |
||||
close(o); |
||||
|
||||
unlink(map_file_name); |
||||
done: |
||||
return result; |
||||
} |
||||
|
||||
int test_file_map_msync() |
||||
{ |
||||
const size_t map_size = 1024; |
||||
|
||||
int result = 0; |
||||
mode_t mode = S_IRUSR | S_IWUSR; |
||||
int o = open(map_file_name, O_TRUNC | O_BINARY | O_RDWR | O_CREAT, mode); |
||||
if (o == -1) |
||||
{ |
||||
printf("unable to create file %s: %d\n", map_file_name, errno); |
||||
return -1; |
||||
} |
||||
|
||||
void* map = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, o, 0); |
||||
if (map == MAP_FAILED) |
||||
{ |
||||
printf("mmap returned unexpected error: %d\n", errno); |
||||
result = -1; |
||||
goto done_close; |
||||
} |
||||
|
||||
*((unsigned char*)map) = 1; |
||||
|
||||
if (msync(map, map_size, MS_SYNC) != 0) |
||||
{ |
||||
printf("msync returned unexpected error: %d\n", errno); |
||||
result = -1; |
||||
} |
||||
|
||||
result = munmap(map, map_size); |
||||
|
||||
if (result != 0) |
||||
printf("munmap returned unexpected error: %d\n", errno); |
||||
|
||||
done_close: |
||||
close(o); |
||||
|
||||
unlink(map_file_name); |
||||
done: |
||||
return result; |
||||
} |
||||
|
||||
#define EXEC_TEST(name) \ |
||||
if (name() != 0) { result = -1; printf( #name ": fail\n"); } \
|
||||
else { printf(#name ": pass\n"); } |
||||
|
||||
int main() |
||||
{ |
||||
int result = 0; |
||||
|
||||
EXEC_TEST(test_anon_map_readwrite); |
||||
//NOTE: this test must cause an access violation exception
|
||||
//EXEC_TEST(test_anon_map_readonly);
|
||||
EXEC_TEST(test_anon_map_readonly_nowrite); |
||||
EXEC_TEST(test_anon_map_writeonly); |
||||
|
||||
EXEC_TEST(test_file_map_readwrite); |
||||
EXEC_TEST(test_file_map_mlock_munlock); |
||||
EXEC_TEST(test_file_map_msync); |
||||
//TODO: EXEC_TEST(test_file_map_mprotect);
|
||||
|
||||
return result; |
||||
} |
@ -0,0 +1,7 @@ |
||||
@echo off |
||||
|
||||
REM remove automatic created obj folder |
||||
rd obj /S /Q >nul 2>&1 |
||||
|
||||
PATH=%PATH%;C:\Espressif\xtensa-lx106-elf\bin;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\espressif\git-bin;C:\espressif\java-bin;C:\Python27 |
||||
make -f Makefile %1 %2 %3 %4 %5 |
@ -0,0 +1,101 @@ |
||||
<div id="main"> |
||||
<div class="header"> |
||||
<h1>REST & MQTT</h1> |
||||
</div> |
||||
|
||||
<div class="content"> |
||||
<div class="pure-g"> |
||||
<div class="pure-u-1"><div class="card"> |
||||
<p>The REST & 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> |
||||
<input type="checkbox" name="mqtt-enable"/> |
||||
<label>Enable MQTT client</label> |
||||
<br> |
||||
<label>MQTT client state: </label> |
||||
<b id="mqtt-state"></b> |
||||
<br> |
||||
<legend>MQTT server settings</legend> |
||||
<div class="pure-form-stacked"> |
||||
<label>Server hostname or IP</label> |
||||
<input type="text" name="mqtt-host"/> |
||||
<label>Server port</label> |
||||
<input type="text" name="mqtt-port"/> |
||||
<label>Client ID</label> |
||||
<input type="text" name="mqtt-client-id"/> |
||||
<label>Client Timeout (seconds)</label> |
||||
<input type="text" name="mqtt-timeout" /> |
||||
<label>Keep Alive Interval (seconds)</label> |
||||
<input type="text" name="mqtt-keepalive" /> |
||||
<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</label> |
||||
<input type="text" name="mqtt-status-topic"/> |
||||
Message: <tt id="mqtt-status-value"></tt> |
||||
</div> |
||||
<button id="mqtt-status-button" type="submit" class="pure-button button-primary"> |
||||
Update status settings! |
||||
</button> |
||||
</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> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<script src="mqtt.js"></script> |
||||
<script type="text/javascript"> |
||||
onLoad(function() { |
||||
fetchMqtt(); |
||||
bnd($("#mqtt-form"), "submit", changeMqtt); |
||||
bnd($("#mqtt-status-form"), "submit", changeMqttStatus); |
||||
}); |
||||
</script> |
||||
</body></html> |
@ -0,0 +1,76 @@ |
||||
//===== MQTT cards
|
||||
|
||||
function changeMqtt(e) { |
||||
e.preventDefault(); |
||||
var url = "mqtt?1=1"; |
||||
var i, inputs = document.querySelectorAll('#mqtt-form 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) { |
||||
showNotification("MQTT updated"); |
||||
removeClass(cb, 'pure-button-disabled'); |
||||
}, function (s, st) { |
||||
showWarning("Error: " + st); |
||||
removeClass(cb, 'pure-button-disabled'); |
||||
window.setTimeout(fetchMqtt, 100); |
||||
}); |
||||
} |
||||
|
||||
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 changeMqttStatus(e) { |
||||
e.preventDefault(); |
||||
var v = document.querySelector('input[name="mqtt-status-topic"]').value; |
||||
ajaxSpin("POST", "/mqtt?mqtt-status-topic=" + v, function () { |
||||
showNotification("MQTT status settings updated"); |
||||
}, function (s, st) { |
||||
showWarning("Error: " + st); |
||||
window.setTimeout(fetchMqtt, 100); |
||||
}); |
||||
} |
||||
|
||||
function setMqtt(name, v) { |
||||
ajaxSpin("POST", "/mqtt?" + name + "=" + (v ? 1 : 0), function () { |
||||
var n = name.replace("-enable", ""); |
||||
showNotification(n + " is now " + (v ? "enabled" : "disabled")); |
||||
}, function () { |
||||
showWarning("Enable/disable failed"); |
||||
window.setTimeout(fetchMqtt, 100); |
||||
}); |
||||
} |
Before Width: | Height: | Size: 914 B After Width: | Height: | Size: 425 B |
@ -1,10 +1,14 @@ |
||||
#ifndef HTTPDESPFS_H |
||||
#define HTTPDESPFS_H |
||||
|
||||
#include <esp8266.h> |
||||
#include "espfs.h" |
||||
#include "espfsformat.h" |
||||
#include "cgi.h" |
||||
#include "httpd.h" |
||||
|
||||
int cgiEspFsHook(HttpdConnData *connData); |
||||
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData); |
||||
//int cgiEspFsTemplate(HttpdConnData *connData);
|
||||
//int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData);
|
||||
|
||||
#endif |
||||
|
@ -1 +1,39 @@ |
||||
#ifndef _USER_CONFIG_H_ |
||||
#define _USER_CONFIG_H_ |
||||
#include <c_types.h> |
||||
#ifdef __WIN32__ |
||||
#include <_mingw.h> |
||||
#endif |
||||
|
||||
#undef SHOW_HEAP_USE |
||||
#define DEBUGIP |
||||
#define SDK_DBG |
||||
|
||||
#define CMD_DBG |
||||
#undef ESPFS_DBG |
||||
#undef CGI_DBG |
||||
#define CGIFLASH_DBG |
||||
#define CGIMQTT_DBG |
||||
#define CGIPINS_DBG |
||||
#define CGIWIFI_DBG |
||||
#define CONFIG_DBG |
||||
#define LOG_DBG |
||||
#define STATUS_DBG |
||||
#define HTTPD_DBG |
||||
#define MQTT_DBG |
||||
#define MQTTCMD_DBG |
||||
#undef PKTBUF_DBG |
||||
#define REST_DBG |
||||
#define RESTCMD_DBG |
||||
#define SERBR_DBG |
||||
#define SERLED_DBG |
||||
#undef SLIP_DBG |
||||
#define UART_DBG |
||||
|
||||
// If defined, the default hostname for DHCP will include the chip ID to make it unique
|
||||
#undef CHIP_IN_HOSTNAME |
||||
|
||||
extern char* esp_link_version; |
||||
extern uint8_t UTILS_StrToIP(const char* str, void *ip); |
||||
|
||||
#endif |
||||
|
@ -0,0 +1,817 @@ |
||||
/* mqtt.c
|
||||
* Protocol: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
|
||||
* |
||||
* Copyright (c) 2014-2015, Tuan PM <tuanpm at live dot com> |
||||
* All rights reserved. |
||||
* |
||||
* Modified by Thorsten von Eicken to make it fully callback based |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
// TODO:
|
||||
// Handle SessionPresent=0 in CONNACK and rexmit subscriptions
|
||||
// Improve timeout for CONNACK, currently only has keep-alive timeout (maybe send artificial ping?)
|
||||
// Allow messages that don't require ACK to be sent even when pending_buffer is != NULL
|
||||
// Set dup flag in retransmissions
|
||||
|
||||
#include <esp8266.h> |
||||
#include "pktbuf.h" |
||||
#include "mqtt.h" |
||||
|
||||
#ifdef MQTT_DBG |
||||
#define DBG_MQTT(format, ...) os_printf(format, ## __VA_ARGS__) |
||||
#else |
||||
#define DBG_MQTT(format, ...) do { } while(0) |
||||
#endif |
||||
|
||||
extern void dumpMem(void *buf, int len); |
||||
|
||||
// HACK
|
||||
sint8 espconn_secure_connect(struct espconn *espconn) { |
||||
return espconn_connect(espconn); |
||||
} |
||||
sint8 espconn_secure_disconnect(struct espconn *espconn) { |
||||
return espconn_disconnect(espconn); |
||||
} |
||||
sint8 espconn_secure_sent(struct espconn *espconn, uint8 *psent, uint16 length) { |
||||
return espconn_sent(espconn, psent, length); |
||||
} |
||||
|
||||
// max message size supported for receive
|
||||
#define MQTT_MAX_RCV_MESSAGE 2048 |
||||
// max message size for sending (except publish)
|
||||
#define MQTT_MAX_SHORT_MESSAGE 128 |
||||
|
||||
#ifdef MQTT_DBG |
||||
static char* mqtt_msg_type[] = { |
||||
"NULL", "TYPE_CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL", "PUBCOMP", |
||||
"SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT", "RESV", |
||||
}; |
||||
#endif |
||||
|
||||
// forward declarations
|
||||
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_doAbort(MQTT_Client* client); |
||||
|
||||
// Deliver a publish message to the client
|
||||
static void ICACHE_FLASH_ATTR |
||||
deliver_publish(MQTT_Client* client, uint8_t* message, uint16_t length) { |
||||
|
||||
// parse the message into topic and data
|
||||
uint16_t topic_length = length; |
||||
const char *topic = mqtt_get_publish_topic(message, &topic_length); |
||||
uint16_t data_length = length; |
||||
const char *data = mqtt_get_publish_data(message, &data_length); |
||||
|
||||
// callback to client
|
||||
if (client->dataCb) |
||||
client->dataCb((uint32_t*)client, topic, topic_length, data, data_length); |
||||
if (client->cmdDataCb) |
||||
client->cmdDataCb((uint32_t*)client, topic, topic_length, data, data_length); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Client received callback function. |
||||
* @param arg: contain the ip link information |
||||
* @param pdata: received data |
||||
* @param len: the length of received data |
||||
* @retval None |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_tcpclient_recv(void* arg, char* pdata, unsigned short len) { |
||||
//os_printf("MQTT: recv CB\n");
|
||||
uint8_t msg_type; |
||||
uint16_t msg_id; |
||||
uint16_t msg_len; |
||||
|
||||
struct espconn* pCon = (struct espconn*)arg; |
||||
MQTT_Client* client = (MQTT_Client *)pCon->reverse; |
||||
if (client == NULL) return; // aborted connection
|
||||
|
||||
//os_printf("MQTT: Data received %d bytes\n", len);
|
||||
|
||||
do { |
||||
// append data to our buffer
|
||||
int avail = client->in_buffer_size - client->in_buffer_filled; |
||||
if (len <= avail) { |
||||
os_memcpy(client->in_buffer + client->in_buffer_filled, pdata, len); |
||||
client->in_buffer_filled += len; |
||||
len = 0; |
||||
} else { |
||||
os_memcpy(client->in_buffer + client->in_buffer_filled, pdata, avail); |
||||
client->in_buffer_filled += avail; |
||||
len -= avail; |
||||
pdata += avail; |
||||
} |
||||
|
||||
// check out what's at the head of the buffer
|
||||
msg_type = mqtt_get_type(client->in_buffer); |
||||
msg_id = mqtt_get_id(client->in_buffer, client->in_buffer_size); |
||||
msg_len = mqtt_get_total_length(client->in_buffer, client->in_buffer_size); |
||||
|
||||
if (msg_len > client->in_buffer_size) { |
||||
// oops, too long a message for us to digest, disconnect and hope for a miracle
|
||||
os_printf("MQTT: Too long a message (%d bytes)\n", msg_len); |
||||
mqtt_doAbort(client); |
||||
return; |
||||
} |
||||
|
||||
// check whether what's left in the buffer is a complete message
|
||||
if (msg_len > client->in_buffer_filled) break; |
||||
|
||||
if (client->connState != MQTT_CONNECTED) { |
||||
// why are we receiving something??
|
||||
DBG_MQTT("MQTT ERROR: recv in invalid state %d\n", client->connState); |
||||
mqtt_doAbort(client); |
||||
return; |
||||
} |
||||
|
||||
// we are connected and are sending/receiving data messages
|
||||
uint8_t pending_msg_type = 0; |
||||
uint16_t pending_msg_id = 0; |
||||
if (client->pending_buffer != NULL) { |
||||
pending_msg_type = mqtt_get_type(client->pending_buffer->data); |
||||
pending_msg_id = mqtt_get_id(client->pending_buffer->data, client->pending_buffer->filled); |
||||
} |
||||
DBG_MQTT("MQTT: Recv type=%s id=%04X len=%d; Pend type=%s id=%02X\n", |
||||
mqtt_msg_type[msg_type], msg_id, msg_len, mqtt_msg_type[pending_msg_type],pending_msg_id); |
||||
|
||||
switch (msg_type) { |
||||
case MQTT_MSG_TYPE_CONNACK: |
||||
//DBG_MQTT("MQTT: Connect successful\n");
|
||||
// callbacks for internal and external clients
|
||||
if (client->connectedCb) client->connectedCb((uint32_t*)client); |
||||
if (client->cmdConnectedCb) client->cmdConnectedCb((uint32_t*)client); |
||||
client->reconTimeout = 1; // reset the reconnect backoff
|
||||
break; |
||||
|
||||
case MQTT_MSG_TYPE_SUBACK: |
||||
if (pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE && pending_msg_id == msg_id) { |
||||
//DBG_MQTT("MQTT: Subscribe successful\n");
|
||||
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); |
||||
} |
||||
break; |
||||
|
||||
case MQTT_MSG_TYPE_UNSUBACK: |
||||
if (pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE && pending_msg_id == msg_id) { |
||||
//DBG_MQTT("MQTT: Unsubscribe successful\n");
|
||||
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); |
||||
} |
||||
break; |
||||
|
||||
case MQTT_MSG_TYPE_PUBACK: // ack for a publish we sent
|
||||
if (pending_msg_type == MQTT_MSG_TYPE_PUBLISH && pending_msg_id == msg_id) { |
||||
//DBG_MQTT("MQTT: QoS1 Publish successful\n");
|
||||
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); |
||||
} |
||||
break; |
||||
|
||||
case MQTT_MSG_TYPE_PUBREC: // rec for a publish we sent
|
||||
if (pending_msg_type == MQTT_MSG_TYPE_PUBLISH && pending_msg_id == msg_id) { |
||||
//DBG_MQTT("MQTT: QoS2 publish cont\n");
|
||||
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); |
||||
// we need to send PUBREL
|
||||
mqtt_msg_pubrel(&client->mqtt_connection, msg_id); |
||||
mqtt_enq_message(client, client->mqtt_connection.message.data, |
||||
client->mqtt_connection.message.length); |
||||
} |
||||
break; |
||||
|
||||
case MQTT_MSG_TYPE_PUBCOMP: // comp for a pubrel we sent (originally publish we sent)
|
||||
if (pending_msg_type == MQTT_MSG_TYPE_PUBREL && pending_msg_id == msg_id) { |
||||
//DBG_MQTT("MQTT: QoS2 Publish successful\n");
|
||||
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); |
||||
} |
||||
break; |
||||
|
||||
case MQTT_MSG_TYPE_PUBLISH: { // incoming publish
|
||||
// we may need to ACK the publish
|
||||
uint8_t msg_qos = mqtt_get_qos(client->in_buffer); |
||||
#ifdef MQTT_DBG |
||||
uint16_t topic_length = msg_len; |
||||
os_printf("MQTT: Recv PUBLISH qos=%d %s\n", msg_qos, |
||||
mqtt_get_publish_topic(client->in_buffer, &topic_length)); |
||||
#endif |
||||
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 == 1 || msg_qos == 2) { |
||||
mqtt_enq_message(client, client->mqtt_connection.message.data, |
||||
client->mqtt_connection.message.length); |
||||
} |
||||
// send the publish message to clients
|
||||
deliver_publish(client, client->in_buffer, msg_len); |
||||
} |
||||
break; |
||||
|
||||
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) { |
||||
//DBG_MQTT("MQTT: Cont QoS2 recv\n");
|
||||
client->pending_buffer = PktBuf_ShiftFree(client->pending_buffer); |
||||
// we need to send PUBCOMP
|
||||
mqtt_msg_pubcomp(&client->mqtt_connection, msg_id); |
||||
mqtt_enq_message(client, client->mqtt_connection.message.data, |
||||
client->mqtt_connection.message.length); |
||||
} |
||||
break; |
||||
|
||||
case MQTT_MSG_TYPE_PINGRESP: |
||||
client->keepAliveAckTick = 0; |
||||
break; |
||||
} |
||||
|
||||
// Shift out the message and see whether we have another one
|
||||
if (msg_len < client->in_buffer_filled) |
||||
os_memcpy(client->in_buffer, client->in_buffer+msg_len, client->in_buffer_filled-msg_len); |
||||
client->in_buffer_filled -= msg_len; |
||||
} while(client->in_buffer_filled > 0 || len > 0); |
||||
|
||||
// Send next packet out, if possible
|
||||
if (!client->sending && client->pending_buffer == NULL && client->msgQueue != NULL) { |
||||
mqtt_send_message(client); |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief Callback from TCP that previous send completed |
||||
* @param arg: contain the ip link information |
||||
* @retval None |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_tcpclient_sent_cb(void* arg) { |
||||
//DBG_MQTT("MQTT: sent CB\n");
|
||||
struct espconn* pCon = (struct espconn *)arg; |
||||
MQTT_Client* client = (MQTT_Client *)pCon->reverse; |
||||
if (client == NULL) return; // aborted connection ?
|
||||
//DBG_MQTT("MQTT: Sent\n");
|
||||
|
||||
// if the message we sent is not a "pending" one, we need to free the buffer
|
||||
if (client->sending_buffer != NULL) { |
||||
PktBuf *buf = client->sending_buffer; |
||||
//DBG_MQTT("PktBuf free %p l=%d\n", buf, buf->filled);
|
||||
os_free(buf); |
||||
client->sending_buffer = NULL; |
||||
} |
||||
client->sending = false; |
||||
|
||||
// send next message if one is queued and we're not expecting an ACK
|
||||
if (client->connState == MQTT_CONNECTED && client->pending_buffer == NULL && |
||||
client->msgQueue != NULL) { |
||||
mqtt_send_message(client); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* @brief: Timer function to handle timeouts |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_timer(void* arg) { |
||||
MQTT_Client* client = (MQTT_Client*)arg; |
||||
//DBG_MQTT("MQTT: timer CB\n");
|
||||
|
||||
switch (client->connState) { |
||||
default: break; |
||||
|
||||
case MQTT_CONNECTED: |
||||
// first check whether we're timing out for an ACK
|
||||
if (client->pending_buffer != NULL && --client->timeoutTick == 0) { |
||||
// looks like we're not getting a response in time, abort the connection
|
||||
mqtt_doAbort(client); |
||||
client->timeoutTick = 0; // trick to make reconnect happen in 1 second
|
||||
return; |
||||
} |
||||
|
||||
// check whether our last keep-alive timed out
|
||||
if (client->keepAliveAckTick > 0 && --client->keepAliveAckTick == 0) { |
||||
os_printf("\nMQTT ERROR: Keep-alive timed out\n"); |
||||
mqtt_doAbort(client); |
||||
return; |
||||
} |
||||
|
||||
// check whether we need to send a keep-alive message
|
||||
if (client->keepAliveTick > 0 && --client->keepAliveTick == 0) { |
||||
// timeout: we need to send a ping message
|
||||
//DBG_MQTT("MQTT: Send keepalive\n");
|
||||
mqtt_msg_pingreq(&client->mqtt_connection); |
||||
PktBuf *buf = PktBuf_New(client->mqtt_connection.message.length); |
||||
os_memcpy(buf->data, client->mqtt_connection.message.data, |
||||
client->mqtt_connection.message.length); |
||||
buf->filled = client->mqtt_connection.message.length; |
||||
client->msgQueue = PktBuf_Unshift(client->msgQueue, buf); |
||||
mqtt_send_message(client); |
||||
client->keepAliveTick = client->connect_info.keepalive; |
||||
client->keepAliveAckTick = client->sendTimeout; |
||||
} |
||||
|
||||
break; |
||||
|
||||
case TCP_RECONNECT_REQ: |
||||
if (client->timeoutTick == 0 || --client->timeoutTick == 0) { |
||||
// it's time to reconnect! start by re-enqueueing anything pending
|
||||
if (client->pending_buffer != NULL) { |
||||
client->msgQueue = PktBuf_Unshift(client->msgQueue, client->pending_buffer); |
||||
client->pending_buffer = NULL; |
||||
} |
||||
client->connect_info.clean_session = 0; // ask server to keep state
|
||||
MQTT_Connect(client); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief Callback from SDK that socket is disconnected |
||||
* @param arg: contain the ip link information |
||||
* @retval None |
||||
*/ |
||||
void ICACHE_FLASH_ATTR |
||||
mqtt_tcpclient_discon_cb(void* arg) { |
||||
struct espconn* pespconn = (struct espconn *)arg; |
||||
MQTT_Client* client = (MQTT_Client *)pespconn->reverse; |
||||
DBG_MQTT("MQTT: Disconnect CB, freeing espconn %p\n", arg); |
||||
if (pespconn->proto.tcp) os_free(pespconn->proto.tcp); |
||||
os_free(pespconn); |
||||
|
||||
// if this is an aborted connection we're done
|
||||
if (client == NULL) return; |
||||
DBG_MQTT("MQTT: Disconnected from %s:%d\n", client->host, client->port); |
||||
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client); |
||||
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client); |
||||
|
||||
// reconnect unless we're in a permanently disconnected state
|
||||
if (client->connState == MQTT_DISCONNECTED) return; |
||||
client->timeoutTick = client->reconTimeout; |
||||
if (client->reconTimeout < 128) client->reconTimeout <<= 1; |
||||
client->connState = TCP_RECONNECT_REQ; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Callback from SDK that socket got reset, note that no discon_cb will follow |
||||
* @param arg: contain the ip link information |
||||
* @retval None |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_tcpclient_recon_cb(void* arg, int8_t err) { |
||||
struct espconn* pespconn = (struct espconn *)arg; |
||||
MQTT_Client* client = (MQTT_Client *)pespconn->reverse; |
||||
//DBG_MQTT("MQTT: Reset CB, freeing espconn %p (err=%d)\n", arg, err);
|
||||
if (pespconn->proto.tcp) os_free(pespconn->proto.tcp); |
||||
os_free(pespconn); |
||||
os_printf("MQTT: Connection reset from %s:%d\n", client->host, client->port); |
||||
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client); |
||||
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client); |
||||
|
||||
// reconnect unless we're in a permanently disconnected state
|
||||
if (client->connState == MQTT_DISCONNECTED) return; |
||||
client->timeoutTick = client->reconTimeout; |
||||
if (client->reconTimeout < 128) client->reconTimeout <<= 1; |
||||
client->connState = TCP_RECONNECT_REQ; |
||||
os_printf("timeoutTick=%d reconTimeout=%d\n", client->timeoutTick, client->reconTimeout); |
||||
} |
||||
|
||||
|
||||
/**
|
||||
* @brief Callback from SDK that socket is connected |
||||
* @param arg: contain the ip link information |
||||
* @retval None |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_tcpclient_connect_cb(void* arg) { |
||||
struct espconn* pCon = (struct espconn *)arg; |
||||
MQTT_Client* client = (MQTT_Client *)pCon->reverse; |
||||
if (client == NULL) return; // aborted connection
|
||||
|
||||
espconn_regist_disconcb(client->pCon, mqtt_tcpclient_discon_cb); |
||||
espconn_regist_recvcb(client->pCon, mqtt_tcpclient_recv); |
||||
espconn_regist_sentcb(client->pCon, mqtt_tcpclient_sent_cb); |
||||
os_printf("MQTT: TCP connected to %s:%d\n", client->host, client->port); |
||||
|
||||
// send MQTT connect message to broker
|
||||
mqtt_msg_connect(&client->mqtt_connection, &client->connect_info); |
||||
PktBuf *buf = PktBuf_New(client->mqtt_connection.message.length); |
||||
os_memcpy(buf->data, client->mqtt_connection.message.data, |
||||
client->mqtt_connection.message.length); |
||||
buf->filled = client->mqtt_connection.message.length; |
||||
client->msgQueue = PktBuf_Unshift(client->msgQueue, buf); // prepend to send (rexmit) queue
|
||||
mqtt_send_message(client); |
||||
client->connState = MQTT_CONNECTED; // v3.1.1 allows publishing while still connecting
|
||||
} |
||||
|
||||
/**
|
||||
* @brief Allocate and enqueue mqtt message, kick sending, if appropriate |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_enq_message(MQTT_Client *client, const uint8_t *data, uint16_t len) { |
||||
PktBuf *buf = PktBuf_New(len); |
||||
os_memcpy(buf->data, data, len); |
||||
buf->filled = len; |
||||
client->msgQueue = PktBuf_Push(client->msgQueue, buf); |
||||
|
||||
if (client->connState == MQTT_CONNECTED && !client->sending && client->pending_buffer == NULL) { |
||||
mqtt_send_message(client); |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief Send out top message in queue onto socket |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_send_message(MQTT_Client* client) { |
||||
//DBG_MQTT("MQTT: Send_message\n");
|
||||
PktBuf *buf = client->msgQueue; |
||||
if (buf == NULL || client->sending) return; // ahem...
|
||||
client->msgQueue = PktBuf_Shift(client->msgQueue); |
||||
|
||||
// get some details about the message
|
||||
uint16_t msg_type = mqtt_get_type(buf->data); |
||||
uint8_t msg_id = mqtt_get_id(buf->data, buf->filled); |
||||
msg_id = msg_id; |
||||
#ifdef MQTT_DBG |
||||
os_printf("MQTT: Send type=%s id=%04X len=%d\n", mqtt_msg_type[msg_type], msg_id, buf->filled); |
||||
#if 0 |
||||
for (int i=0; i<buf->filled; i++) { |
||||
if (buf->data[i] >= ' ' && buf->data[i] <= '~') os_printf("%c", buf->data[i]); |
||||
else os_printf("\\x%02X", buf->data[i]); |
||||
} |
||||
os_printf("\n"); |
||||
#endif |
||||
#endif |
||||
|
||||
// send the message out
|
||||
if (client->security) |
||||
espconn_secure_sent(client->pCon, buf->data, buf->filled); |
||||
else |
||||
espconn_sent(client->pCon, buf->data, buf->filled); |
||||
client->sending = true; |
||||
|
||||
// depending on whether it needs an ack we need to hold on to the message
|
||||
bool needsAck = |
||||
(msg_type == MQTT_MSG_TYPE_PUBLISH && mqtt_get_qos(buf->data) > 0) || |
||||
msg_type == MQTT_MSG_TYPE_PUBREL || msg_type == MQTT_MSG_TYPE_PUBREC || |
||||
msg_type == MQTT_MSG_TYPE_SUBSCRIBE || msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE || |
||||
msg_type == MQTT_MSG_TYPE_PINGREQ; |
||||
if (msg_type == MQTT_MSG_TYPE_PINGREQ) { |
||||
client->pending_buffer = NULL; // we don't need to rexmit this one
|
||||
client->sending_buffer = buf; |
||||
} else if (needsAck) { |
||||
client->pending_buffer = buf; // remeber for rexmit on disconnect/reconnect
|
||||
client->sending_buffer = NULL; |
||||
client->timeoutTick = client->sendTimeout+1; // +1 to ensure full sendTireout seconds
|
||||
} else { |
||||
client->pending_buffer = NULL; |
||||
client->sending_buffer = buf; |
||||
client->timeoutTick = 0; |
||||
} |
||||
client->keepAliveTick = client->connect_info.keepalive > 0 ? client->connect_info.keepalive+1 : 0; |
||||
} |
||||
|
||||
/**
|
||||
* @brief DNS lookup for broker hostname completed, move to next phase |
||||
*/ |
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_dns_found(const char* name, ip_addr_t* ipaddr, void* arg) { |
||||
struct espconn* pConn = (struct espconn *)arg; |
||||
MQTT_Client* client = (MQTT_Client *)pConn->reverse; |
||||
|
||||
if (ipaddr == NULL) { |
||||
os_printf("MQTT: DNS lookup failed\n"); |
||||
client->timeoutTick = client->reconTimeout; |
||||
if (client->reconTimeout < 128) client->reconTimeout <<= 1; |
||||
client->connState = TCP_RECONNECT_REQ; // the timer will kick-off a reconnection
|
||||
return; |
||||
} |
||||
DBG_MQTT("MQTT: ip %d.%d.%d.%d\n", |
||||
*((uint8 *)&ipaddr->addr), |
||||
*((uint8 *)&ipaddr->addr + 1), |
||||
*((uint8 *)&ipaddr->addr + 2), |
||||
*((uint8 *)&ipaddr->addr + 3)); |
||||
|
||||
if (client->ip.addr == 0 && ipaddr->addr != 0) { |
||||
os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); |
||||
uint8_t err; |
||||
if (client->security) |
||||
err = espconn_secure_connect(client->pCon); |
||||
else |
||||
err = espconn_connect(client->pCon); |
||||
if (err != 0) { |
||||
os_printf("MQTT ERROR: Failed to connect\n"); |
||||
client->timeoutTick = client->reconTimeout; |
||||
if (client->reconTimeout < 128) client->reconTimeout <<= 1; |
||||
client->connState = TCP_RECONNECT_REQ; |
||||
} else { |
||||
DBG_MQTT("MQTT: connecting...\n"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
//===== publish / subscribe
|
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
msg_conn_init(mqtt_connection_t *new_msg, mqtt_connection_t *old_msg, |
||||
uint8_t *buf, uint16_t buflen) { |
||||
new_msg->message_id = old_msg->message_id; |
||||
new_msg->buffer = buf; |
||||
new_msg->buffer_length = buflen; |
||||
} |
||||
|
||||
/**
|
||||
* @brief MQTT publish function. |
||||
* @param client: MQTT_Client reference |
||||
* @param topic: string topic will publish to |
||||
* @param data: buffer data send point to |
||||
* @param data_length: length of data |
||||
* @param qos: qos |
||||
* @param retain: retain |
||||
* @retval TRUE if success queue |
||||
*/ |
||||
bool ICACHE_FLASH_ATTR |
||||
MQTT_Publish(MQTT_Client* client, const char* topic, const char* data, uint8_t qos, uint8_t retain) { |
||||
// estimate the packet size to allocate a buffer
|
||||
uint16_t topic_length = os_strlen(topic); |
||||
uint16_t data_length = os_strlen(data); |
||||
// estimate: fixed hdr, pkt-id, topic length, topic, data, fudge
|
||||
uint16_t buf_len = 3 + 2 + 2 + topic_length + data_length + 16; |
||||
PktBuf *buf = PktBuf_New(buf_len); |
||||
if (buf == NULL) { |
||||
os_printf("MQTT ERROR: Cannot allocate buffer for %d byte publish\n", buf_len); |
||||
return FALSE; |
||||
} |
||||
// use a temporary mqtt_message_t pointing to our buffer, this is a bit of a mess because we
|
||||
// need to keep track of the message_id that is embedded in it
|
||||
mqtt_connection_t msg; |
||||
msg_conn_init(&msg, &client->mqtt_connection, buf->data, buf_len); |
||||
uint16_t msg_id; |
||||
if (!mqtt_msg_publish(&msg, topic, data, data_length, qos, retain, &msg_id)){ |
||||
os_printf("MQTT ERROR: Queuing Publish failed\n"); |
||||
os_free(buf); |
||||
return FALSE; |
||||
} |
||||
client->mqtt_connection.message_id = msg.message_id; |
||||
if (msg.message.data != buf->data) |
||||
os_memcpy(buf->data, msg.message.data, msg.message.length); |
||||
buf->filled = msg.message.length; |
||||
|
||||
DBG_MQTT("MQTT: Publish, topic: \"%s\", length: %d\n", topic, msg.message.length); |
||||
//dumpMem(buf, buf_len);
|
||||
client->msgQueue = PktBuf_Push(client->msgQueue, buf); |
||||
|
||||
if (!client->sending && client->pending_buffer == NULL) { |
||||
mqtt_send_message(client); |
||||
} |
||||
return TRUE; |
||||
} |
||||
|
||||
/**
|
||||
* @brief MQTT subscribe function. |
||||
* @param client: MQTT_Client reference |
||||
* @param topic: string topic will subscribe |
||||
* @param qos: qos |
||||
* @retval TRUE if success queue |
||||
*/ |
||||
bool ICACHE_FLASH_ATTR |
||||
MQTT_Subscribe(MQTT_Client* client, char* topic, uint8_t qos) { |
||||
uint16_t msg_id; |
||||
if (!mqtt_msg_subscribe(&client->mqtt_connection, topic, 0, &msg_id)) { |
||||
os_printf("MQTT ERROR: Queuing Subscribe failed (too long)\n"); |
||||
return FALSE; |
||||
} |
||||
DBG_MQTT("MQTT: Subscribe, topic: \"%s\"\n", topic); |
||||
mqtt_enq_message(client, client->mqtt_connection.message.data, |
||||
client->mqtt_connection.message.length); |
||||
return TRUE; |
||||
} |
||||
|
||||
//===== Initialization and connect/disconnect
|
||||
|
||||
/**
|
||||
* @brief MQTT initialization mqtt client function |
||||
* @param client: MQTT_Client reference |
||||
* @param host: Domain or IP string |
||||
* @param port: Port to connect |
||||
* @param security: 1 for ssl, 0 for none |
||||
* @param clientid: MQTT client id |
||||
* @param client_user: MQTT client user |
||||
* @param client_pass: MQTT client password |
||||
* @param keepAliveTime: MQTT keep alive timer, in second |
||||
* @param cleanSession: On connection, a client sets the "clean session" flag, which is sometimes also known as the "clean start" flag. |
||||
* If clean session is set to false, then the connection is treated as durable. This means that when the client |
||||
* disconnects, any subscriptions it has will remain and any subsequent QoS 1 or 2 messages will be stored until |
||||
* it connects again in the future. If clean session is true, then all subscriptions will be removed for the client |
||||
* when it disconnects. |
||||
* @retval None |
||||
*/ |
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_Init(MQTT_Client* client, char* host, uint32 port, uint8_t security, uint8_t sendTimeout, |
||||
char* client_id, char* client_user, char* client_pass, |
||||
uint8_t keepAliveTime) { |
||||
DBG_MQTT("MQTT_Init\n"); |
||||
|
||||
os_memset(client, 0, sizeof(MQTT_Client)); |
||||
|
||||
client->host = (char*)os_zalloc(os_strlen(host) + 1); |
||||
os_strcpy(client->host, host); |
||||
|
||||
client->port = port; |
||||
client->security = !!security; |
||||
|
||||
// timeouts with sanity checks
|
||||
client->sendTimeout = sendTimeout == 0 ? 1 : sendTimeout; |
||||
client->reconTimeout = 1; // reset reconnect back-off
|
||||
|
||||
os_memset(&client->connect_info, 0, sizeof(mqtt_connect_info_t)); |
||||
|
||||
client->connect_info.client_id = (char*)os_zalloc(os_strlen(client_id) + 1); |
||||
os_strcpy(client->connect_info.client_id, client_id); |
||||
|
||||
client->connect_info.username = (char*)os_zalloc(os_strlen(client_user) + 1); |
||||
os_strcpy(client->connect_info.username, client_user); |
||||
|
||||
client->connect_info.password = (char*)os_zalloc(os_strlen(client_pass) + 1); |
||||
os_strcpy(client->connect_info.password, client_pass); |
||||
|
||||
client->connect_info.keepalive = keepAliveTime; |
||||
client->connect_info.clean_session = 1; |
||||
|
||||
client->in_buffer = (uint8_t *)os_zalloc(MQTT_MAX_RCV_MESSAGE); |
||||
client->in_buffer_size = MQTT_MAX_RCV_MESSAGE; |
||||
|
||||
uint8_t *out_buffer = (uint8_t *)os_zalloc(MQTT_MAX_SHORT_MESSAGE); |
||||
mqtt_msg_init(&client->mqtt_connection, out_buffer, MQTT_MAX_SHORT_MESSAGE); |
||||
} |
||||
|
||||
/**
|
||||
* @brief MQTT Set Last Will Topic, must be called before MQTT_Connect |
||||
*/ |
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_InitLWT(MQTT_Client* client, char* will_topic, char* will_msg, |
||||
uint8_t will_qos, uint8_t will_retain) { |
||||
|
||||
client->connect_info.will_topic = (char*)os_zalloc(os_strlen(will_topic) + 1); |
||||
os_strcpy((char*)client->connect_info.will_topic, will_topic); |
||||
|
||||
client->connect_info.will_message = (char*)os_zalloc(os_strlen(will_msg) + 1); |
||||
os_strcpy((char*)client->connect_info.will_message, will_msg); |
||||
|
||||
client->connect_info.will_qos = will_qos; |
||||
client->connect_info.will_retain = will_retain; |
||||
|
||||
// TODO: if we're connected we should disconnect and reconnect to establish the new LWT
|
||||
} |
||||
|
||||
/**
|
||||
* @brief Begin connect to MQTT broker |
||||
* @param client: MQTT_Client reference |
||||
* @retval None |
||||
*/ |
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_Connect(MQTT_Client* client) { |
||||
//MQTT_Disconnect(client);
|
||||
client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); |
||||
client->pCon->type = ESPCONN_TCP; |
||||
client->pCon->state = ESPCONN_NONE; |
||||
client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); |
||||
client->pCon->proto.tcp->local_port = espconn_port(); |
||||
client->pCon->proto.tcp->remote_port = client->port; |
||||
client->pCon->reverse = client; |
||||
espconn_regist_connectcb(client->pCon, mqtt_tcpclient_connect_cb); |
||||
espconn_regist_reconcb(client->pCon, mqtt_tcpclient_recon_cb); |
||||
|
||||
// start timer function to tick every second
|
||||
os_timer_disarm(&client->mqttTimer); |
||||
os_timer_setfn(&client->mqttTimer, (os_timer_func_t *)mqtt_timer, client); |
||||
os_timer_arm(&client->mqttTimer, 1000, 1); |
||||
|
||||
// initiate the TCP connection or DNS lookup
|
||||
os_printf("MQTT: Connect to %s:%d %p\n", client->host, client->port, client->pCon); |
||||
if (UTILS_StrToIP((const char *)client->host, |
||||
(void*)&client->pCon->proto.tcp->remote_ip)) { |
||||
uint8_t err; |
||||
if (client->security) |
||||
err = espconn_secure_connect(client->pCon); |
||||
else |
||||
err = espconn_connect(client->pCon); |
||||
if (err != 0) { |
||||
os_printf("MQTT ERROR: Failed to connect\n"); |
||||
os_free(client->pCon->proto.tcp); |
||||
os_free(client->pCon); |
||||
client->pCon = NULL; |
||||
return; |
||||
} |
||||
} else { |
||||
espconn_gethostbyname(client->pCon, (const char *)client->host, &client->ip, |
||||
mqtt_dns_found); |
||||
} |
||||
|
||||
client->connState = TCP_CONNECTING; |
||||
client->timeoutTick = 20; // generous timeout to allow for DNS, etc
|
||||
client->sending = FALSE; |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
mqtt_doAbort(MQTT_Client* client) { |
||||
os_printf("MQTT: Disconnecting from %s:%d (%p)\n", client->host, client->port, client->pCon); |
||||
client->pCon->reverse = NULL; // ensure we jettison this pCon...
|
||||
if (client->security) |
||||
espconn_secure_disconnect(client->pCon); |
||||
else |
||||
espconn_disconnect(client->pCon); |
||||
|
||||
if (client->disconnectedCb) client->disconnectedCb((uint32_t*)client); |
||||
if (client->cmdDisconnectedCb) client->cmdDisconnectedCb((uint32_t*)client); |
||||
|
||||
if (client->sending_buffer != NULL) { |
||||
os_free(client->sending_buffer); |
||||
client->sending_buffer = NULL; |
||||
} |
||||
client->pCon = NULL; // it will be freed in disconnect callback
|
||||
client->connState = TCP_RECONNECT_REQ; |
||||
client->timeoutTick = client->reconTimeout; // reconnect in a few seconds
|
||||
if (client->reconTimeout < 128) client->reconTimeout <<= 1; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_Reconnect(MQTT_Client* client) { |
||||
DBG_MQTT("MQTT: Reconnect requested\n"); |
||||
if (client->connState == MQTT_DISCONNECTED) |
||||
MQTT_Connect(client); |
||||
else if (client->connState == MQTT_CONNECTED) |
||||
mqtt_doAbort(client); |
||||
// in other cases we're already in the reconnecting process
|
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_Disconnect(MQTT_Client* client) { |
||||
DBG_MQTT("MQTT: Disconnect requested\n"); |
||||
os_timer_disarm(&client->mqttTimer); |
||||
if (client->connState == MQTT_DISCONNECTED) return; |
||||
if (client->connState == TCP_RECONNECT_REQ) { |
||||
client->connState = MQTT_DISCONNECTED; |
||||
return; |
||||
} |
||||
mqtt_doAbort(client); |
||||
//void *out_buffer = client->mqtt_connection.buffer;
|
||||
//if (out_buffer != NULL) os_free(out_buffer);
|
||||
client->connState = MQTT_DISCONNECTED; // ensure we don't automatically reconnect
|
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_Free(MQTT_Client* client) { |
||||
DBG_MQTT("MQTT: Free requested\n"); |
||||
MQTT_Disconnect(client); |
||||
|
||||
if (client->host) os_free(client->host); |
||||
client->host = NULL; |
||||
|
||||
if (client->connect_info.client_id) os_free(client->connect_info.client_id); |
||||
if (client->connect_info.username) os_free(client->connect_info.username); |
||||
if (client->connect_info.password) os_free(client->connect_info.password); |
||||
os_memset(&client->connect_info, 0, sizeof(mqtt_connect_info_t)); |
||||
|
||||
if (client->in_buffer) os_free(client->in_buffer); |
||||
client->in_buffer = NULL; |
||||
|
||||
if (client->mqtt_connection.buffer) os_free(client->mqtt_connection.buffer); |
||||
os_memset(&client->mqtt_connection, 0, sizeof(client->mqtt_connection)); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_OnConnected(MQTT_Client* client, MqttCallback connectedCb) { |
||||
client->connectedCb = connectedCb; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_OnDisconnected(MQTT_Client* client, MqttCallback disconnectedCb) { |
||||
client->disconnectedCb = disconnectedCb; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_OnData(MQTT_Client* client, MqttDataCallback dataCb) { |
||||
client->dataCb = dataCb; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
MQTT_OnPublished(MQTT_Client* client, MqttCallback publishedCb) { |
||||
client->publishedCb = publishedCb; |
||||
} |
@ -0,0 +1,134 @@ |
||||
/* mqtt.h
|
||||
* |
||||
* Copyright (c) 2014-2015, Tuan PM <tuanpm at live dot com> |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
#ifndef MQTT_H_ |
||||
#define MQTT_H_ |
||||
|
||||
#include "mqtt_msg.h" |
||||
#include "pktbuf.h" |
||||
|
||||
// in rest.c
|
||||
uint8_t UTILS_StrToIP(const char* str, void *ip); |
||||
|
||||
// State of MQTT connection
|
||||
typedef enum { |
||||
MQTT_DISCONNECTED, // we're in disconnected state
|
||||
TCP_RECONNECT_REQ, // connect failed, needs reconnecting
|
||||
TCP_CONNECTING, // in TCP connection process
|
||||
MQTT_CONNECTED, // conneted (or connecting)
|
||||
} tConnState; |
||||
|
||||
// Simple notification callback
|
||||
typedef void (*MqttCallback)(uint32_t* args); |
||||
// Callback with data messge
|
||||
typedef void (*MqttDataCallback)(uint32_t* args, const char* topic, uint32_t topic_len, |
||||
const char* data, uint32_t data_len); |
||||
|
||||
// MQTTY client data structure
|
||||
typedef struct { |
||||
struct espconn* pCon; // socket
|
||||
// connection information
|
||||
char* host; // MQTT server
|
||||
uint16_t port; |
||||
uint8_t security; // 0=tcp, 1=ssl
|
||||
ip_addr_t ip; // MQTT server IP address
|
||||
mqtt_connect_info_t connect_info; // info to connect/reconnect
|
||||
// protocol state and message assembly
|
||||
tConnState connState; // connection state
|
||||
bool sending; // espconn_send is pending
|
||||
mqtt_connection_t mqtt_connection; // message assembly descriptor
|
||||
PktBuf* msgQueue; // queued outbound messages
|
||||
// TCP input buffer
|
||||
uint8_t* in_buffer; |
||||
int in_buffer_size; // length allocated
|
||||
int in_buffer_filled; // number of bytes held
|
||||
// outstanding message when we expect an ACK
|
||||
PktBuf* pending_buffer; // buffer sent and awaiting ACK
|
||||
PktBuf* sending_buffer; // buffer sent not awaiting ACK
|
||||
// timer and associated timeout counters
|
||||
ETSTimer mqttTimer; // timer for this connection
|
||||
uint8_t keepAliveTick; // seconds 'til keep-alive is required (0=no k-a)
|
||||
uint8_t keepAliveAckTick; // seconds 'til keep-alive ack is overdue (0=no k-a)
|
||||
uint8_t timeoutTick; // seconds 'til other timeout
|
||||
uint8_t sendTimeout; // value of send timeout setting
|
||||
uint8_t reconTimeout; // timeout to reconnect (back-off)
|
||||
// callbacks
|
||||
MqttCallback connectedCb; |
||||
MqttCallback cmdConnectedCb; |
||||
MqttCallback disconnectedCb; |
||||
MqttCallback cmdDisconnectedCb; |
||||
MqttCallback publishedCb; |
||||
MqttCallback cmdPublishedCb; |
||||
MqttDataCallback dataCb; |
||||
MqttDataCallback cmdDataCb; |
||||
// misc
|
||||
void* user_data; |
||||
} MQTT_Client; |
||||
|
||||
// Initialize client data structure
|
||||
void MQTT_Init(MQTT_Client* mqttClient, char* host, uint32 port, |
||||
uint8_t security, uint8_t sendTimeout, |
||||
char* client_id, char* client_user, char* client_pass, |
||||
uint8_t keepAliveTime); |
||||
|
||||
// Completely free buffers associated with client data structure
|
||||
// This does not free the mqttClient struct itself, it just readies the struct so
|
||||
// it can be freed or MQTT_Init can be called on it again
|
||||
void MQTT_Free(MQTT_Client* mqttClient); |
||||
|
||||
// Set Last Will Topic on client, must be called before MQTT_InitConnection
|
||||
void MQTT_InitLWT(MQTT_Client* mqttClient, char* will_topic, char* will_msg, |
||||
uint8_t will_qos, uint8_t will_retain); |
||||
|
||||
// Disconnect and reconnect in order to change params (such as LWT)
|
||||
void MQTT_Reconnect(MQTT_Client* mqttClient); |
||||
|
||||
// Kick of a persistent connection to the broker, will reconnect anytime conn breaks
|
||||
void MQTT_Connect(MQTT_Client* mqttClient); |
||||
|
||||
// Kill persistent connection
|
||||
void MQTT_Disconnect(MQTT_Client* mqttClient); |
||||
|
||||
// Subscribe to a topic
|
||||
bool MQTT_Subscribe(MQTT_Client* client, char* topic, uint8_t qos); |
||||
|
||||
// Publish a message
|
||||
bool MQTT_Publish(MQTT_Client* client, const char* topic, const char* data, |
||||
uint8_t qos, uint8_t retain); |
||||
|
||||
// Callback when connected
|
||||
void MQTT_OnConnected(MQTT_Client* mqttClient, MqttCallback connectedCb); |
||||
// Callback when disconnected
|
||||
void MQTT_OnDisconnected(MQTT_Client* mqttClient, MqttCallback disconnectedCb); |
||||
// Callback when publish succeeded
|
||||
void MQTT_OnPublished(MQTT_Client* mqttClient, MqttCallback publishedCb); |
||||
// Callback when data arrives for subscription
|
||||
void MQTT_OnData(MQTT_Client* mqttClient, MqttDataCallback dataCb); |
||||
|
||||
#endif /* USER_AT_MQTT_H_ */ |
@ -0,0 +1,377 @@ |
||||
//
|
||||
// MQTT Commands coming in from the attache microcontrollver over the serial port
|
||||
//
|
||||
|
||||
#include <esp8266.h> |
||||
#include "mqtt.h" |
||||
#include "mqtt_client.h" |
||||
#include "mqtt_cmd.h" |
||||
|
||||
#ifdef MQTTCMD_DBG |
||||
#define DBG_MQTTCMD(format, ...) os_printf(format, ## __VA_ARGS__) |
||||
#else |
||||
#define DBG_MQTTCMD(format, ...) do { } while(0) |
||||
#endif |
||||
|
||||
// if MQTT_1_CLIENT is defined we only support the one client that is built into esp-link.
|
||||
// this keeps everything simpler. Undefining it brings back old code that supports creating
|
||||
// a new client and setting all its params. Most likely that old code no longer works...
|
||||
#define MQTT_1_CLIENT |
||||
|
||||
// callbacks to the attached uC
|
||||
uint32_t connectedCb = 0, disconnectCb = 0, publishedCb = 0, dataCb = 0; |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
cmdMqttConnectedCb(uint32_t* args) { |
||||
MQTT_Client* client = (MQTT_Client*)args; |
||||
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; |
||||
DBG_MQTTCMD("MQTT: Connected connectedCb=%p, disconnectedCb=%p, publishedCb=%p, dataCb=%p\n", |
||||
(void*)cb->connectedCb, |
||||
(void*)cb->disconnectedCb, |
||||
(void*)cb->publishedCb, |
||||
(void*)cb->dataCb); |
||||
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->connectedCb, 0, 0); |
||||
CMD_ResponseEnd(crc); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
cmdMqttDisconnectedCb(uint32_t* args) { |
||||
MQTT_Client* client = (MQTT_Client*)args; |
||||
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; |
||||
DBG_MQTTCMD("MQTT: Disconnected\n"); |
||||
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->disconnectedCb, 0, 0); |
||||
CMD_ResponseEnd(crc); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
cmdMqttPublishedCb(uint32_t* args) { |
||||
MQTT_Client* client = (MQTT_Client*)args; |
||||
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; |
||||
DBG_MQTTCMD("MQTT: Published\n"); |
||||
uint16_t crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->publishedCb, 0, 0); |
||||
CMD_ResponseEnd(crc); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
cmdMqttDataCb(uint32_t* args, const char* topic, uint32_t topic_len, const char* data, uint32_t data_len) { |
||||
uint16_t crc = 0; |
||||
MQTT_Client* client = (MQTT_Client*)args; |
||||
MqttCmdCb* cb = (MqttCmdCb*)client->user_data; |
||||
|
||||
crc = CMD_ResponseStart(CMD_MQTT_EVENTS, cb->dataCb, 0, 2); |
||||
crc = CMD_ResponseBody(crc, (uint8_t*)topic, topic_len); |
||||
crc = CMD_ResponseBody(crc, (uint8_t*)data, data_len); |
||||
CMD_ResponseEnd(crc); |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
MQTTCMD_Lwt(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
|
||||
if (CMD_GetArgc(&req) != 5) |
||||
return 0; |
||||
|
||||
// get mqtt client
|
||||
uint32_t client_ptr; |
||||
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); |
||||
#ifdef MQTT_1_CLIENT |
||||
MQTT_Client* client = &mqttClient; |
||||
#else |
||||
MQTT_Client* client = (MQTT_Client*)client_ptr; |
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Lwt client ptr=%p\n", (void*)client_ptr); |
||||
#endif |
||||
|
||||
// free old topic & message
|
||||
if (client->connect_info.will_topic) |
||||
os_free(client->connect_info.will_topic); |
||||
if (client->connect_info.will_message) |
||||
os_free(client->connect_info.will_message); |
||||
|
||||
uint16_t len; |
||||
|
||||
// get topic
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 128) return 0; // safety check
|
||||
client->connect_info.will_topic = (char*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, client->connect_info.will_topic, len); |
||||
client->connect_info.will_topic[len] = 0; |
||||
|
||||
// get message
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 128) return 0; // safety check
|
||||
client->connect_info.will_message = (char*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, client->connect_info.will_message, len); |
||||
client->connect_info.will_message[len] = 0; |
||||
|
||||
// get qos
|
||||
CMD_PopArg(&req, (uint8_t*)&client->connect_info.will_qos, 4); |
||||
|
||||
// get retain
|
||||
CMD_PopArg(&req, (uint8_t*)&client->connect_info.will_retain, 4); |
||||
|
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Lwt topic=%s, message=%s, qos=%d, retain=%d\n", |
||||
client->connect_info.will_topic, |
||||
client->connect_info.will_message, |
||||
client->connect_info.will_qos, |
||||
client->connect_info.will_retain); |
||||
|
||||
// trigger a reconnect to set the LWT
|
||||
MQTT_Reconnect(client); |
||||
return 1; |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
MQTTCMD_Publish(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
|
||||
if (CMD_GetArgc(&req) != 6) |
||||
return 0; |
||||
|
||||
// get mqtt client
|
||||
uint32_t client_ptr; |
||||
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); |
||||
#ifdef MQTT_1_CLIENT |
||||
MQTT_Client* client = &mqttClient; |
||||
#else |
||||
MQTT_Client* client = (MQTT_Client*)client_ptr; |
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Publish client ptr=%p\n", (void*)client_ptr); |
||||
#endif |
||||
|
||||
uint16_t len; |
||||
|
||||
// get topic
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 128) return 0; // safety check
|
||||
uint8_t *topic = (uint8_t*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, topic, len); |
||||
topic[len] = 0; |
||||
|
||||
// get data
|
||||
len = CMD_ArgLen(&req); |
||||
uint8_t *data = (uint8_t*)os_zalloc(len+1); |
||||
if (!data) { // safety check
|
||||
os_free(topic); |
||||
return 0; |
||||
} |
||||
CMD_PopArg(&req, data, len); |
||||
data[len] = 0; |
||||
|
||||
uint32_t qos, retain, data_len; |
||||
|
||||
// get data length
|
||||
// this isn't used but we have to pull it off the stack
|
||||
CMD_PopArg(&req, (uint8_t*)&data_len, 4); |
||||
|
||||
// get qos
|
||||
CMD_PopArg(&req, (uint8_t*)&qos, 4); |
||||
|
||||
// get retain
|
||||
CMD_PopArg(&req, (uint8_t*)&retain, 4); |
||||
|
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Publish topic=%s, data_len=%d, qos=%ld, retain=%ld\n", |
||||
topic, |
||||
os_strlen((char*)data), |
||||
qos, |
||||
retain); |
||||
|
||||
MQTT_Publish(client, (char*)topic, (char*)data, (uint8_t)qos, (uint8_t)retain); |
||||
os_free(topic); |
||||
os_free(data); |
||||
return 1; |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
MQTTCMD_Subscribe(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
|
||||
if (CMD_GetArgc(&req) != 3) |
||||
return 0; |
||||
|
||||
// get mqtt client
|
||||
uint32_t client_ptr; |
||||
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); |
||||
#ifdef MQTT_1_CLIENT |
||||
MQTT_Client* client = &mqttClient; |
||||
#else |
||||
MQTT_Client* client = (MQTT_Client*)client_ptr; |
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Subscribe client ptr=%p\n", (void*)client_ptr); |
||||
#endif |
||||
|
||||
uint16_t len; |
||||
|
||||
// get topic
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 128) return 0; // safety check
|
||||
uint8_t* topic = (uint8_t*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, topic, len); |
||||
topic[len] = 0; |
||||
|
||||
// get qos
|
||||
uint32_t qos = 0; |
||||
CMD_PopArg(&req, (uint8_t*)&qos, 4); |
||||
|
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Subscribe topic=%s, qos=%ld\n", topic, qos); |
||||
|
||||
MQTT_Subscribe(client, (char*)topic, (uint8_t)qos); |
||||
os_free(topic); |
||||
return 1; |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
MQTTCMD_Setup(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
|
||||
#ifdef MQTT_1_CLIENT |
||||
MQTT_Client* client = &mqttClient; |
||||
CMD_SkipArg(&req); |
||||
CMD_SkipArg(&req); |
||||
CMD_SkipArg(&req); |
||||
CMD_SkipArg(&req); |
||||
CMD_SkipArg(&req); |
||||
#else |
||||
if (CMD_GetArgc(&req) != 9) |
||||
return 0; |
||||
|
||||
// create mqtt client
|
||||
uint8_t clientLen = sizeof(MQTT_Client); |
||||
MQTT_Client* client = (MQTT_Client*)os_zalloc(clientLen); |
||||
if (client == NULL) return 0; |
||||
os_memset(client, 0, clientLen); |
||||
|
||||
uint16_t len; |
||||
uint8_t *client_id, *user_data, *pass_data; |
||||
uint32_t keepalive, clean_session; |
||||
|
||||
// get client id
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 32) return 0; // safety check
|
||||
client_id = (uint8_t*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, client_id, len); |
||||
client_id[len] = 0; |
||||
|
||||
// get username
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 32) return 0; // safety check
|
||||
user_data = (uint8_t*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, user_data, len); |
||||
user_data[len] = 0; |
||||
|
||||
// get password
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 32) return 0; // safety check
|
||||
pass_data = (uint8_t*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, pass_data, len); |
||||
pass_data[len] = 0; |
||||
|
||||
// get keepalive
|
||||
CMD_PopArg(&req, (uint8_t*)&keepalive, 4); |
||||
|
||||
// get clean session
|
||||
CMD_PopArg(&req, (uint8_t*)&clean_session, 4); |
||||
#ifdef MQTTCMD_DBG |
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Setup clientid=%s, user=%s, pw=%s, keepalive=%ld, clean_session=%ld\n", client_id, user_data, pass_data, keepalive, clean_session); |
||||
#endif |
||||
|
||||
// init client
|
||||
// TODO: why malloc these all here, pass to MQTT_InitClient to be malloc'd again?
|
||||
MQTT_InitClient(client, (char*)client_id, (char*)user_data, (char*)pass_data, keepalive, clean_session); |
||||
|
||||
os_free(client_id); |
||||
os_free(user_data); |
||||
os_free(pass_data); |
||||
#endif |
||||
|
||||
// create callback
|
||||
MqttCmdCb* callback = (MqttCmdCb*)os_zalloc(sizeof(MqttCmdCb)); |
||||
uint32_t cb_data; |
||||
|
||||
CMD_PopArg(&req, (uint8_t*)&cb_data, 4); |
||||
callback->connectedCb = cb_data; |
||||
CMD_PopArg(&req, (uint8_t*)&cb_data, 4); |
||||
callback->disconnectedCb = cb_data; |
||||
CMD_PopArg(&req, (uint8_t*)&cb_data, 4); |
||||
callback->publishedCb = cb_data; |
||||
CMD_PopArg(&req, (uint8_t*)&cb_data, 4); |
||||
callback->dataCb = cb_data; |
||||
|
||||
client->user_data = callback; |
||||
|
||||
client->cmdConnectedCb = cmdMqttConnectedCb; |
||||
client->cmdDisconnectedCb = cmdMqttDisconnectedCb; |
||||
client->cmdPublishedCb = cmdMqttPublishedCb; |
||||
client->cmdDataCb = cmdMqttDataCb; |
||||
|
||||
return 0xf00df00d; //(uint32_t)client;
|
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
MQTTCMD_Connect(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
|
||||
#ifdef MQTT_1_CLIENT |
||||
return 1; |
||||
|
||||
#else |
||||
if (CMD_GetArgc(&req) != 4) |
||||
return 0; |
||||
|
||||
// get mqtt client
|
||||
uint32_t client_ptr; |
||||
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); |
||||
MQTT_Client* client = (MQTT_Client*)client_ptr; |
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Connect client ptr=%p\n", (void*)client_ptr); |
||||
|
||||
uint16_t len; |
||||
|
||||
// get host
|
||||
if (client->host) |
||||
os_free(client->host); |
||||
len = CMD_ArgLen(&req); |
||||
if (len > 128) return 0; // safety check
|
||||
client->host = (char*)os_zalloc(len + 1); |
||||
CMD_PopArg(&req, client->host, len); |
||||
client->host[len] = 0; |
||||
|
||||
// get port
|
||||
CMD_PopArg(&req, (uint8_t*)&client->port, 4); |
||||
|
||||
// get security
|
||||
CMD_PopArg(&req, (uint8_t*)&client->security, 4); |
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Connect host=%s, port=%d, security=%d\n", |
||||
client->host, |
||||
client->port, |
||||
client->security); |
||||
|
||||
MQTT_Connect(client); |
||||
return 1; |
||||
#endif |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
MQTTCMD_Disconnect(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
|
||||
#ifdef MQTT_1_CLIENT |
||||
return 1; |
||||
|
||||
#else |
||||
if (CMD_GetArgc(&req) != 1) |
||||
return 0; |
||||
|
||||
// get mqtt client
|
||||
uint32_t client_ptr; |
||||
CMD_PopArg(&req, (uint8_t*)&client_ptr, 4); |
||||
MQTT_Client* client = (MQTT_Client*)client_ptr; |
||||
DBG_MQTTCMD("MQTT: MQTTCMD_Disconnect client ptr=%p\n", (void*)client_ptr); |
||||
|
||||
// disconnect
|
||||
MQTT_Disconnect(client); |
||||
return 1; |
||||
#endif |
||||
} |
@ -0,0 +1,20 @@ |
||||
#ifndef MODULES_MQTT_CMD_H_ |
||||
#define MODULES_MQTT_CMD_H_ |
||||
|
||||
#include "cmd.h" |
||||
|
||||
typedef struct { |
||||
uint32_t connectedCb; |
||||
uint32_t disconnectedCb; |
||||
uint32_t publishedCb; |
||||
uint32_t dataCb; |
||||
} MqttCmdCb; |
||||
|
||||
uint32_t MQTTCMD_Connect(CmdPacket *cmd); |
||||
uint32_t MQTTCMD_Disconnect(CmdPacket *cmd); |
||||
uint32_t MQTTCMD_Setup(CmdPacket *cmd); |
||||
uint32_t MQTTCMD_Publish(CmdPacket *cmd); |
||||
uint32_t MQTTCMD_Subscribe(CmdPacket *cmd); |
||||
uint32_t MQTTCMD_Lwt(CmdPacket *cmd); |
||||
|
||||
#endif /* MODULES_MQTT_CMD_H_ */ |
@ -0,0 +1,452 @@ |
||||
/*
|
||||
* Copyright (c) 2014, Stephen Robinson |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* 3. Neither the name of the copyright holder nor the names of its |
||||
* contributors may be used to endorse or promote products derived |
||||
* from this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#include <esp8266.h> |
||||
#include "mqtt_msg.h" |
||||
#define MQTT_MAX_FIXED_HEADER_SIZE 3 |
||||
|
||||
enum mqtt_connect_flag { |
||||
MQTT_CONNECT_FLAG_USERNAME = 1 << 7, |
||||
MQTT_CONNECT_FLAG_PASSWORD = 1 << 6, |
||||
MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5, |
||||
MQTT_CONNECT_FLAG_WILL = 1 << 2, |
||||
MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 |
||||
}; |
||||
|
||||
struct |
||||
__attribute__((__packed__)) mqtt_connect_variable_header { |
||||
uint8_t lengthMsb; |
||||
uint8_t lengthLsb; |
||||
#if defined(PROTOCOL_NAMEv31) |
||||
uint8_t magic[6]; |
||||
#elif defined(PROTOCOL_NAMEv311) |
||||
uint8_t magic[4]; |
||||
#else |
||||
#error "Please define protocol name" |
||||
#endif |
||||
uint8_t version; |
||||
uint8_t flags; |
||||
uint8_t keepaliveMsb; |
||||
uint8_t keepaliveLsb; |
||||
}; |
||||
|
||||
static int ICACHE_FLASH_ATTR |
||||
append_string(mqtt_connection_t* connection, const char* string, int len) { |
||||
if (connection->message.length + len + 2 > connection->buffer_length) |
||||
return -1; |
||||
|
||||
connection->buffer[connection->message.length++] = len >> 8; |
||||
connection->buffer[connection->message.length++] = len & 0xff; |
||||
memcpy(connection->buffer + connection->message.length, string, len); |
||||
connection->message.length += len; |
||||
|
||||
return len + 2; |
||||
} |
||||
|
||||
static uint16_t ICACHE_FLASH_ATTR |
||||
append_message_id(mqtt_connection_t* connection, uint16_t message_id) { |
||||
// If message_id is zero then we should assign one, otherwise
|
||||
// we'll use the one supplied by the caller
|
||||
while (message_id == 0) |
||||
message_id = ++connection->message_id; |
||||
|
||||
if (connection->message.length + 2 > connection->buffer_length) |
||||
return 0; |
||||
|
||||
connection->buffer[connection->message.length++] = message_id >> 8; |
||||
connection->buffer[connection->message.length++] = message_id & 0xff; |
||||
|
||||
return message_id; |
||||
} |
||||
|
||||
static int ICACHE_FLASH_ATTR |
||||
init_message(mqtt_connection_t* connection) { |
||||
connection->message.length = MQTT_MAX_FIXED_HEADER_SIZE; |
||||
return MQTT_MAX_FIXED_HEADER_SIZE; |
||||
} |
||||
|
||||
static mqtt_message_t* ICACHE_FLASH_ATTR |
||||
fail_message(mqtt_connection_t* connection) { |
||||
connection->message.data = connection->buffer; |
||||
connection->message.length = 0; |
||||
return &connection->message; |
||||
} |
||||
|
||||
static mqtt_message_t* ICACHE_FLASH_ATTR |
||||
fini_message(mqtt_connection_t* connection, int type, int dup, int qos, int retain) { |
||||
int remaining_length = connection->message.length - MQTT_MAX_FIXED_HEADER_SIZE; |
||||
|
||||
if (remaining_length > 127) { |
||||
connection->buffer[0] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); |
||||
connection->buffer[1] = 0x80 | (remaining_length % 128); |
||||
connection->buffer[2] = remaining_length / 128; |
||||
connection->message.length = remaining_length + 3; |
||||
connection->message.data = connection->buffer; |
||||
} |
||||
else { |
||||
connection->buffer[1] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); |
||||
connection->buffer[2] = remaining_length; |
||||
connection->message.length = remaining_length + 2; |
||||
connection->message.data = connection->buffer + 1; |
||||
} |
||||
|
||||
return &connection->message; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length) { |
||||
uint8_t len = sizeof(connection); |
||||
memset(connection, '\0', len); |
||||
connection->buffer = buffer; |
||||
connection->buffer_length = buffer_length; |
||||
} |
||||
|
||||
int ICACHE_FLASH_ATTR |
||||
mqtt_get_total_length(const uint8_t* buffer, uint16_t length) { |
||||
int i; |
||||
int totlen = 0; |
||||
|
||||
for (i = 1; i < length; ++i) { |
||||
totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); |
||||
if ((buffer[i] & 0x80) == 0) { |
||||
++i; |
||||
break; |
||||
} |
||||
} |
||||
totlen += i; |
||||
|
||||
return totlen; |
||||
} |
||||
|
||||
const char* ICACHE_FLASH_ATTR |
||||
mqtt_get_publish_topic(const uint8_t* buffer, uint16_t* length) { |
||||
int i; |
||||
int totlen = 0; |
||||
int topiclen; |
||||
|
||||
for (i = 1; i < *length; ++i) { |
||||
totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); |
||||
if ((buffer[i] & 0x80) == 0) { |
||||
++i; |
||||
break; |
||||
} |
||||
} |
||||
totlen += i; |
||||
|
||||
if (i + 2 >= *length) |
||||
return NULL; |
||||
topiclen = buffer[i++] << 8; |
||||
topiclen |= buffer[i++]; |
||||
|
||||
if (i + topiclen > *length) |
||||
return NULL; |
||||
|
||||
*length = topiclen; |
||||
return (const char*)(buffer + i); |
||||
} |
||||
|
||||
const char* ICACHE_FLASH_ATTR |
||||
mqtt_get_publish_data(const uint8_t* buffer, uint16_t* length) { |
||||
int i; |
||||
int totlen = 0; |
||||
int topiclen; |
||||
|
||||
for (i = 1; i < *length; ++i) { |
||||
totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); |
||||
if ((buffer[i] & 0x80) == 0) { |
||||
++i; |
||||
break; |
||||
} |
||||
} |
||||
totlen += i; |
||||
|
||||
if (i + 2 >= *length) |
||||
return NULL; |
||||
topiclen = buffer[i++] << 8; |
||||
topiclen |= buffer[i++]; |
||||
|
||||
if (i + topiclen >= *length) { |
||||
*length = 0; |
||||
return NULL; |
||||
} |
||||
i += topiclen; |
||||
|
||||
if (mqtt_get_qos(buffer) > 0) { |
||||
if (i + 2 >= *length) |
||||
return NULL; |
||||
i += 2; |
||||
} |
||||
|
||||
if (totlen < i) |
||||
return NULL; |
||||
|
||||
if (totlen <= *length) |
||||
*length = totlen - i; |
||||
else |
||||
*length = *length - i; |
||||
return (const char*)(buffer + i); |
||||
} |
||||
|
||||
uint16_t ICACHE_FLASH_ATTR |
||||
mqtt_get_id(const uint8_t* buffer, uint16_t length) { |
||||
if (length < 1) |
||||
return 0; |
||||
|
||||
switch (mqtt_get_type(buffer)) { |
||||
case MQTT_MSG_TYPE_PUBLISH: { |
||||
int i; |
||||
int topiclen; |
||||
|
||||
for (i = 1; i < length; ++i) { |
||||
if ((buffer[i] & 0x80) == 0) { |
||||
++i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (i + 2 >= length) |
||||
return 0; |
||||
topiclen = buffer[i++] << 8; |
||||
topiclen |= buffer[i++]; |
||||
|
||||
if (i + topiclen >= length) |
||||
return 0; |
||||
i += topiclen; |
||||
|
||||
if (mqtt_get_qos(buffer) > 0) { |
||||
if (i + 2 >= length) |
||||
return 0; |
||||
//i += 2;
|
||||
} |
||||
else { |
||||
return 0; |
||||
} |
||||
|
||||
return (buffer[i] << 8) | buffer[i + 1]; |
||||
} |
||||
case MQTT_MSG_TYPE_PUBACK: |
||||
case MQTT_MSG_TYPE_PUBREC: |
||||
case MQTT_MSG_TYPE_PUBREL: |
||||
case MQTT_MSG_TYPE_PUBCOMP: |
||||
case MQTT_MSG_TYPE_SUBACK: |
||||
case MQTT_MSG_TYPE_UNSUBACK: |
||||
case MQTT_MSG_TYPE_SUBSCRIBE: { |
||||
// This requires the remaining length to be encoded in 1 byte,
|
||||
// which it should be.
|
||||
if (length >= 4 && (buffer[1] & 0x80) == 0) |
||||
return (buffer[2] << 8) | buffer[3]; |
||||
else |
||||
return 0; |
||||
} |
||||
|
||||
default: |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info) { |
||||
struct mqtt_connect_variable_header* variable_header; |
||||
|
||||
init_message(connection); |
||||
|
||||
if (connection->message.length + sizeof(*variable_header) > connection->buffer_length) |
||||
return fail_message(connection); |
||||
variable_header = (void*)(connection->buffer + connection->message.length); |
||||
connection->message.length += sizeof(*variable_header); |
||||
|
||||
variable_header->lengthMsb = 0; |
||||
#if defined(PROTOCOL_NAMEv31) |
||||
variable_header->lengthLsb = 6; |
||||
memcpy(variable_header->magic, "MQIsdp", 6); |
||||
variable_header->version = 3; |
||||
#elif defined(PROTOCOL_NAMEv311) |
||||
variable_header->lengthLsb = 4; |
||||
memcpy(variable_header->magic, "MQTT", 4); |
||||
variable_header->version = 4; |
||||
#else |
||||
#error "Please define protocol name" |
||||
#endif |
||||
|
||||
variable_header->flags = 0; |
||||
variable_header->keepaliveMsb = info->keepalive >> 8; |
||||
variable_header->keepaliveLsb = info->keepalive & 0xff; |
||||
|
||||
if (info->clean_session) |
||||
variable_header->flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; |
||||
|
||||
if (info->client_id != NULL && info->client_id[0] != '\0') { |
||||
if (append_string(connection, info->client_id, strlen(info->client_id)) < 0) |
||||
return fail_message(connection); |
||||
} |
||||
else |
||||
return fail_message(connection); |
||||
|
||||
if (info->will_topic != NULL && info->will_topic[0] != '\0') { |
||||
if (append_string(connection, info->will_topic, strlen(info->will_topic)) < 0) |
||||
return fail_message(connection); |
||||
|
||||
if (append_string(connection, info->will_message, strlen(info->will_message)) < 0) |
||||
return fail_message(connection); |
||||
|
||||
variable_header->flags |= MQTT_CONNECT_FLAG_WILL; |
||||
if (info->will_retain) |
||||
variable_header->flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; |
||||
variable_header->flags |= (info->will_qos & 3) << 3; |
||||
} |
||||
|
||||
if (info->username != NULL && info->username[0] != '\0') { |
||||
if (append_string(connection, info->username, strlen(info->username)) < 0) |
||||
return fail_message(connection); |
||||
|
||||
variable_header->flags |= MQTT_CONNECT_FLAG_USERNAME; |
||||
} |
||||
|
||||
if (info->password != NULL && info->password[0] != '\0') { |
||||
if (append_string(connection, info->password, strlen(info->password)) < 0) |
||||
return fail_message(connection); |
||||
|
||||
variable_header->flags |= MQTT_CONNECT_FLAG_PASSWORD; |
||||
} |
||||
|
||||
return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id) { |
||||
init_message(connection); |
||||
|
||||
if (topic == NULL || topic[0] == '\0') |
||||
return fail_message(connection); |
||||
|
||||
if (append_string(connection, topic, strlen(topic)) < 0) |
||||
return fail_message(connection); |
||||
|
||||
if (qos > 0) { |
||||
if ((*message_id = append_message_id(connection, 0)) == 0) |
||||
return fail_message(connection); |
||||
} |
||||
else |
||||
*message_id = 0; |
||||
|
||||
if (connection->message.length + data_length > connection->buffer_length) |
||||
return fail_message(connection); |
||||
memcpy(connection->buffer + connection->message.length, data, data_length); |
||||
connection->message.length += data_length; |
||||
|
||||
return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id) { |
||||
init_message(connection); |
||||
if (append_message_id(connection, message_id) == 0) |
||||
return fail_message(connection); |
||||
return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id) { |
||||
init_message(connection); |
||||
if (append_message_id(connection, message_id) == 0) |
||||
return fail_message(connection); |
||||
return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id) { |
||||
init_message(connection); |
||||
if (append_message_id(connection, message_id) == 0) |
||||
return fail_message(connection); |
||||
return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id) { |
||||
init_message(connection); |
||||
if (append_message_id(connection, message_id) == 0) |
||||
return fail_message(connection); |
||||
return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id) { |
||||
init_message(connection); |
||||
|
||||
if (topic == NULL || topic[0] == '\0') |
||||
return fail_message(connection); |
||||
|
||||
if ((*message_id = append_message_id(connection, 0)) == 0) |
||||
return fail_message(connection); |
||||
|
||||
if (append_string(connection, topic, strlen(topic)) < 0) |
||||
return fail_message(connection); |
||||
|
||||
if (connection->message.length + 1 > connection->buffer_length) |
||||
return fail_message(connection); |
||||
connection->buffer[connection->message.length++] = qos; |
||||
|
||||
return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id) { |
||||
init_message(connection); |
||||
|
||||
if (topic == NULL || topic[0] == '\0') |
||||
return fail_message(connection); |
||||
|
||||
if ((*message_id = append_message_id(connection, 0)) == 0) |
||||
return fail_message(connection); |
||||
|
||||
if (append_string(connection, topic, strlen(topic)) < 0) |
||||
return fail_message(connection); |
||||
|
||||
return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_pingreq(mqtt_connection_t* connection) { |
||||
init_message(connection); |
||||
return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_pingresp(mqtt_connection_t* connection) { |
||||
init_message(connection); |
||||
return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); |
||||
} |
||||
|
||||
mqtt_message_t* ICACHE_FLASH_ATTR |
||||
mqtt_msg_disconnect(mqtt_connection_t* connection) { |
||||
init_message(connection); |
||||
return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); |
||||
} |
@ -0,0 +1,127 @@ |
||||
/*
|
||||
* Copyright (c) 2014, Stephen Robinson |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* 3. Neither the name of the copyright holder nor the names of its |
||||
* contributors may be used to endorse or promote products derived |
||||
* from this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef MQTT_MSG_H |
||||
#define MQTT_MSG_H |
||||
|
||||
#define PROTOCOL_NAMEv311 |
||||
|
||||
enum mqtt_message_type { |
||||
MQTT_MSG_TYPE_CONNECT = 1, |
||||
MQTT_MSG_TYPE_CONNACK = 2, |
||||
MQTT_MSG_TYPE_PUBLISH = 3, |
||||
MQTT_MSG_TYPE_PUBACK = 4, |
||||
MQTT_MSG_TYPE_PUBREC = 5, |
||||
MQTT_MSG_TYPE_PUBREL = 6, |
||||
MQTT_MSG_TYPE_PUBCOMP = 7, |
||||
MQTT_MSG_TYPE_SUBSCRIBE = 8, |
||||
MQTT_MSG_TYPE_SUBACK = 9, |
||||
MQTT_MSG_TYPE_UNSUBSCRIBE = 10, |
||||
MQTT_MSG_TYPE_UNSUBACK = 11, |
||||
MQTT_MSG_TYPE_PINGREQ = 12, |
||||
MQTT_MSG_TYPE_PINGRESP = 13, |
||||
MQTT_MSG_TYPE_DISCONNECT = 14 |
||||
}; |
||||
|
||||
// Descriptor for a serialized MQTT message, this is returned by functions that compose a message
|
||||
// (It's really an MQTT packet in v3.1.1 terminology)
|
||||
typedef struct mqtt_message { |
||||
uint8_t* data; |
||||
uint16_t length; |
||||
} mqtt_message_t; |
||||
|
||||
// Descriptor for a connection with message assembly storage
|
||||
typedef struct mqtt_connection { |
||||
mqtt_message_t message; // resulting message
|
||||
uint16_t message_id; // id of assembled message and memo to calculate next message id
|
||||
uint8_t* buffer; // buffer for assembling messages
|
||||
uint16_t buffer_length; // buffer length
|
||||
} mqtt_connection_t; |
||||
|
||||
// Descriptor for a connect request
|
||||
typedef struct mqtt_connect_info { |
||||
char* client_id; |
||||
char* username; |
||||
char* password; |
||||
char* will_topic; |
||||
char* will_message; |
||||
uint8_t keepalive; |
||||
uint8_t will_qos; |
||||
uint8_t will_retain; |
||||
uint8_t clean_session; |
||||
} mqtt_connect_info_t; |
||||
|
||||
static inline int ICACHE_FLASH_ATTR mqtt_get_type(const uint8_t* buffer) { |
||||
return (buffer[0] & 0xf0) >> 4; |
||||
} |
||||
|
||||
static inline int ICACHE_FLASH_ATTR mqtt_get_dup(const uint8_t* buffer) { |
||||
return (buffer[0] & 0x08) >> 3; |
||||
} |
||||
|
||||
static inline int ICACHE_FLASH_ATTR mqtt_get_qos(const uint8_t* buffer) { |
||||
return (buffer[0] & 0x06) >> 1; |
||||
} |
||||
|
||||
static inline int ICACHE_FLASH_ATTR mqtt_get_retain(const uint8_t* buffer) { |
||||
return (buffer[0] & 0x01); |
||||
} |
||||
|
||||
// Init a connection descriptor
|
||||
void mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length); |
||||
|
||||
// Returns the total length of a message including MQTT fixed header
|
||||
int mqtt_get_total_length(const uint8_t* buffer, uint16_t length); |
||||
|
||||
// Return pointer to topic, length in in/out param: in=length of buffer, out=length of topic
|
||||
const char* mqtt_get_publish_topic(const uint8_t* buffer, uint16_t* length); |
||||
|
||||
// Return pointer to data, length in in/out param: in=length of buffer, out=length of data
|
||||
const char* mqtt_get_publish_data(const uint8_t* buffer, uint16_t* length); |
||||
|
||||
// Return message id
|
||||
uint16_t mqtt_get_id(const uint8_t* buffer, uint16_t length); |
||||
|
||||
// The following functions construct an outgoing message
|
||||
mqtt_message_t* mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info); |
||||
mqtt_message_t* mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id); |
||||
mqtt_message_t* mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id); |
||||
mqtt_message_t* mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id); |
||||
mqtt_message_t* mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id); |
||||
mqtt_message_t* mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id); |
||||
mqtt_message_t* mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id); |
||||
mqtt_message_t* mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id); |
||||
mqtt_message_t* mqtt_msg_pingreq(mqtt_connection_t* connection); |
||||
mqtt_message_t* mqtt_msg_pingresp(mqtt_connection_t* connection); |
||||
mqtt_message_t* mqtt_msg_disconnect(mqtt_connection_t* connection); |
||||
|
||||
#endif // MQTT_MSG_H
|
||||
|
@ -0,0 +1,66 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
|
||||
#include <esp8266.h> |
||||
#include "pktbuf.h" |
||||
|
||||
#ifdef PKTBUF_DBG |
||||
static void ICACHE_FLASH_ATTR |
||||
PktBuf_Print(PktBuf *buf) { |
||||
os_printf("PktBuf:"); |
||||
for (int i=-16; i<0; i++) |
||||
os_printf(" %02X", ((uint8_t*)buf)[i]); |
||||
os_printf(" %p", buf); |
||||
for (int i=0; i<16; i++) |
||||
os_printf(" %02X", ((uint8_t*)buf)[i]); |
||||
os_printf("\n"); |
||||
os_printf("PktBuf: next=%p len=0x%04x\n", |
||||
((void**)buf)[-4], ((uint16_t*)buf)[-6]); |
||||
} |
||||
#endif |
||||
|
||||
|
||||
PktBuf * ICACHE_FLASH_ATTR |
||||
PktBuf_New(uint16_t length) { |
||||
PktBuf *buf = os_zalloc(length+sizeof(PktBuf)); |
||||
buf->next = NULL; |
||||
buf->filled = 0; |
||||
//os_printf("PktBuf_New: %p l=%d->%d d=%p\n",
|
||||
// buf, length, length+sizeof(PktBuf), buf->data);
|
||||
return buf; |
||||
} |
||||
|
||||
PktBuf * ICACHE_FLASH_ATTR |
||||
PktBuf_Push(PktBuf *headBuf, PktBuf *buf) { |
||||
if (headBuf == NULL) { |
||||
//os_printf("PktBuf_Push: %p\n", buf);
|
||||
return buf; |
||||
} |
||||
PktBuf *h = headBuf; |
||||
while (h->next != NULL) h = h->next; |
||||
h->next = buf; |
||||
//os_printf("PktBuf_Push: %p->..->%p\n", headBuf, buf);
|
||||
return headBuf; |
||||
} |
||||
|
||||
PktBuf * ICACHE_FLASH_ATTR |
||||
PktBuf_Unshift(PktBuf *headBuf, PktBuf *buf) { |
||||
buf->next = headBuf; |
||||
//os_printf("PktBuf_Unshift: %p->%p\n", buf, buf->next);
|
||||
return buf; |
||||
} |
||||
|
||||
PktBuf * ICACHE_FLASH_ATTR |
||||
PktBuf_Shift(PktBuf *headBuf) { |
||||
PktBuf *buf = headBuf->next; |
||||
headBuf->next = NULL; |
||||
//os_printf("PktBuf_Shift: (%p)->%p\n", headBuf, buf);
|
||||
return buf; |
||||
} |
||||
|
||||
PktBuf * ICACHE_FLASH_ATTR |
||||
PktBuf_ShiftFree(PktBuf *headBuf) { |
||||
PktBuf *buf = headBuf->next; |
||||
//os_printf("PktBuf_ShiftFree: (%p)->%p\n", headBuf, buf);
|
||||
os_free(headBuf); |
||||
return buf; |
||||
} |
@ -0,0 +1,27 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
|
||||
#ifndef PKTBUF_H |
||||
#define PKTBUF_H |
||||
|
||||
typedef struct PktBuf { |
||||
struct PktBuf *next; // next buffer in chain
|
||||
uint16_t filled; // number of bytes filled in buffer
|
||||
uint8_t data[0]; // data in buffer
|
||||
} PktBuf; |
||||
|
||||
// Allocate a new packet buffer of given length
|
||||
PktBuf *PktBuf_New(uint16_t length); |
||||
|
||||
// Append a buffer to the end of a packet buffer queue, returns new head
|
||||
PktBuf *PktBuf_Push(PktBuf *headBuf, PktBuf *buf); |
||||
|
||||
// Prepend a buffer to the beginning of a packet buffer queue, return new head
|
||||
PktBuf * PktBuf_Unshift(PktBuf *headBuf, PktBuf *buf); |
||||
|
||||
// Shift first buffer off queue, returns new head (not shifted buffer!)
|
||||
PktBuf *PktBuf_Shift(PktBuf *headBuf); |
||||
|
||||
// Shift first buffer off queue, free it, return new head
|
||||
PktBuf *PktBuf_ShiftFree(PktBuf *headBuf); |
||||
|
||||
#endif |
@ -0,0 +1,400 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
//
|
||||
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh
|
||||
|
||||
#include "esp8266.h" |
||||
#include "rest.h" |
||||
#include "cmd.h" |
||||
|
||||
#ifdef REST_DBG |
||||
#define DBG_REST(format, ...) os_printf(format, ## __VA_ARGS__) |
||||
#else |
||||
#define DBG_REST(format, ...) do { } while(0) |
||||
#endif |
||||
|
||||
|
||||
// Connection pool for REST clients. Attached MCU's just call REST_setup and this allocates
|
||||
// a connection, They never call any 'free' and given that the attached MCU could restart at
|
||||
// any time, we cannot really rely on the attached MCU to call 'free' ever, so better do without.
|
||||
// Instead, we allocate a fixed pool of connections an round-robin. What this means is that the
|
||||
// attached MCU should really use at most as many REST connections as there are slots in the pool.
|
||||
#define MAX_REST 4 |
||||
static RestClient restClient[MAX_REST]; |
||||
static uint8_t restNum = 0xff; // index into restClient for next slot to allocate
|
||||
#define REST_CB 0xbeef0000 // fudge added to callback for arduino so we can detect problems
|
||||
|
||||
// Receive HTTP response - this hacky function assumes that the full response is received in
|
||||
// one go. Sigh...
|
||||
static void ICACHE_FLASH_ATTR |
||||
tcpclient_recv(void *arg, char *pdata, unsigned short len) { |
||||
struct espconn *pCon = (struct espconn*)arg; |
||||
RestClient *client = (RestClient *)pCon->reverse; |
||||
|
||||
// parse status line
|
||||
int pi = 0; |
||||
int32_t code = -1; |
||||
char statusCode[4] = "\0\0\0\0"; |
||||
int statusLen = 0; |
||||
bool inStatus = false; |
||||
while (pi < len) { |
||||
if (pdata[pi] == '\n') { |
||||
// end of status line
|
||||
if (code == -1) code = 502; // BAD GATEWAY
|
||||
break; |
||||
} else if (pdata[pi] == ' ') { |
||||
if (inStatus) code = atoi(statusCode); |
||||
inStatus = !inStatus; |
||||
} else if (inStatus) { |
||||
if (statusLen < 3) statusCode[statusLen] = pdata[pi]; |
||||
statusLen++; |
||||
} |
||||
pi++; |
||||
} |
||||
|
||||
// parse header, all this does is look for the end of the header
|
||||
bool currentLineIsBlank = false; |
||||
while (pi < len) { |
||||
if (pdata[pi] == '\n') { |
||||
if (currentLineIsBlank) { |
||||
// body is starting
|
||||
pi++; |
||||
break; |
||||
} |
||||
currentLineIsBlank = true; |
||||
} else if (pdata[pi] != '\r') { |
||||
currentLineIsBlank = false; |
||||
} |
||||
pi++; |
||||
} |
||||
//if (pi < len && pdata[pi] == '\r') pi++; // hacky!
|
||||
|
||||
// collect body and send it
|
||||
uint16_t crc; |
||||
int body_len = len-pi; |
||||
DBG_REST("REST: status=%ld, body=%d\n", code, body_len); |
||||
if (pi == len) { |
||||
crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 0); |
||||
} else { |
||||
crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 1); |
||||
crc = CMD_ResponseBody(crc, (uint8_t*)(pdata+pi), body_len); |
||||
CMD_ResponseEnd(crc); |
||||
#if 0 |
||||
os_printf("REST: body="); |
||||
for (int j=pi; j<len; j++) os_printf(" %02x", pdata[j]); |
||||
os_printf("\n"); |
||||
#endif |
||||
} |
||||
|
||||
//if(client->security)
|
||||
// espconn_secure_disconnect(client->pCon);
|
||||
//else
|
||||
espconn_disconnect(client->pCon); |
||||
|
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
tcpclient_sent_cb(void *arg) { |
||||
struct espconn *pCon = (struct espconn *)arg; |
||||
RestClient* client = (RestClient *)pCon->reverse; |
||||
DBG_REST("REST: Sent\n"); |
||||
if (client->data_sent != client->data_len) { |
||||
// we only sent part of the buffer, send the rest
|
||||
espconn_sent(client->pCon, (uint8_t*)(client->data+client->data_sent), |
||||
client->data_len-client->data_sent); |
||||
client->data_sent = client->data_len; |
||||
} else { |
||||
// we're done sending, free the memory
|
||||
if (client->data) os_free(client->data); |
||||
client->data = 0; |
||||
} |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
tcpclient_discon_cb(void *arg) { |
||||
struct espconn *pespconn = (struct espconn *)arg; |
||||
RestClient* client = (RestClient *)pespconn->reverse; |
||||
// free the data buffer, if we have one
|
||||
if (client->data) os_free(client->data); |
||||
client->data = 0; |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
tcpclient_recon_cb(void *arg, sint8 errType) { |
||||
struct espconn *pCon = (struct espconn *)arg; |
||||
RestClient* client = (RestClient *)pCon->reverse; |
||||
os_printf("REST #%d: conn reset, err=%d\n", client-restClient, errType); |
||||
// free the data buffer, if we have one
|
||||
if (client->data) os_free(client->data); |
||||
client->data = 0; |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
tcpclient_connect_cb(void *arg) { |
||||
struct espconn *pCon = (struct espconn *)arg; |
||||
RestClient* client = (RestClient *)pCon->reverse; |
||||
DBG_REST("REST #%d: connected\n", client-restClient); |
||||
espconn_regist_disconcb(client->pCon, tcpclient_discon_cb); |
||||
espconn_regist_recvcb(client->pCon, tcpclient_recv); |
||||
espconn_regist_sentcb(client->pCon, tcpclient_sent_cb); |
||||
|
||||
client->data_sent = client->data_len <= 1400 ? client->data_len : 1400; |
||||
DBG_REST("REST #%d: sending %d\n", client-restClient, client->data_sent); |
||||
//if(client->security){
|
||||
// espconn_secure_sent(client->pCon, client->data, client->data_sent);
|
||||
//}
|
||||
//else{
|
||||
espconn_sent(client->pCon, (uint8_t*)client->data, client->data_sent); |
||||
//}
|
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
rest_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) { |
||||
struct espconn *pConn = (struct espconn *)arg; |
||||
RestClient* client = (RestClient *)pConn->reverse; |
||||
|
||||
if(ipaddr == NULL) { |
||||
os_printf("REST DNS: Got no ip, try to reconnect\n"); |
||||
return; |
||||
} |
||||
DBG_REST("REST DNS: found ip %d.%d.%d.%d\n", |
||||
*((uint8 *) &ipaddr->addr), |
||||
*((uint8 *) &ipaddr->addr + 1), |
||||
*((uint8 *) &ipaddr->addr + 2), |
||||
*((uint8 *) &ipaddr->addr + 3)); |
||||
if(client->ip.addr == 0 && ipaddr->addr != 0) { |
||||
os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); |
||||
#ifdef CLIENT_SSL_ENABLE |
||||
if(client->security) { |
||||
espconn_secure_connect(client->pCon); |
||||
} else |
||||
#endif |
||||
espconn_connect(client->pCon); |
||||
DBG_REST("REST: connecting...\n"); |
||||
} |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
REST_Setup(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
uint32_t port, security; |
||||
|
||||
// start parsing the command
|
||||
CMD_Request(&req, cmd); |
||||
if(CMD_GetArgc(&req) != 3) return 0; |
||||
|
||||
// get the hostname
|
||||
uint16_t len = CMD_ArgLen(&req); |
||||
if (len > 128) return 0; // safety check
|
||||
uint8_t *rest_host = (uint8_t*)os_zalloc(len + 1); |
||||
if (CMD_PopArg(&req, rest_host, len)) return 0; |
||||
rest_host[len] = 0; |
||||
|
||||
// get the port
|
||||
if (CMD_PopArg(&req, (uint8_t*)&port, 4)) { |
||||
os_free(rest_host); |
||||
return 0; |
||||
} |
||||
|
||||
// get the security mode
|
||||
if (CMD_PopArg(&req, (uint8_t*)&security, 4)) { |
||||
os_free(rest_host); |
||||
return 0; |
||||
} |
||||
|
||||
// clear connection structures the first time
|
||||
if (restNum == 0xff) { |
||||
os_memset(restClient, 0, MAX_REST * sizeof(RestClient)); |
||||
restNum = 0; |
||||
} |
||||
|
||||
// allocate a connection structure
|
||||
RestClient *client = restClient + restNum; |
||||
uint8_t clientNum = restNum; |
||||
restNum = (restNum+1)%MAX_REST; |
||||
|
||||
// free any data structure that may be left from a previous connection
|
||||
if (client->header) os_free(client->header); |
||||
if (client->content_type) os_free(client->content_type); |
||||
if (client->user_agent) os_free(client->user_agent); |
||||
if (client->data) os_free(client->data); |
||||
if (client->pCon) { |
||||
if (client->pCon->proto.tcp) os_free(client->pCon->proto.tcp); |
||||
os_free(client->pCon); |
||||
} |
||||
os_memset(client, 0, sizeof(RestClient)); |
||||
DBG_REST("REST: setup #%d host=%s port=%ld security=%ld\n", clientNum, rest_host, port, security); |
||||
|
||||
client->resp_cb = cmd->callback; |
||||
|
||||
client->host = (char *)rest_host; |
||||
client->port = port; |
||||
client->security = security; |
||||
|
||||
client->header = (char*)os_zalloc(4); |
||||
client->header[0] = 0; |
||||
|
||||
client->content_type = (char*)os_zalloc(22); |
||||
os_sprintf((char *)client->content_type, "x-www-form-urlencoded"); |
||||
|
||||
client->user_agent = (char*)os_zalloc(9); |
||||
os_sprintf((char *)client->user_agent, "esp-link"); |
||||
|
||||
client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); |
||||
client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); |
||||
|
||||
client->pCon->type = ESPCONN_TCP; |
||||
client->pCon->state = ESPCONN_NONE; |
||||
client->pCon->proto.tcp->local_port = espconn_port(); |
||||
client->pCon->proto.tcp->remote_port = client->port; |
||||
|
||||
client->pCon->reverse = client; |
||||
|
||||
return REST_CB | (uint32_t)clientNum; |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
REST_SetHeader(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
|
||||
if(CMD_GetArgc(&req) != 3) |
||||
return 0; |
||||
|
||||
// Get client
|
||||
uint32_t clientNum; |
||||
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) return 0; |
||||
if ((clientNum & 0xffff0000) != REST_CB) return 0; |
||||
RestClient *client = restClient + ((clientNum & 0xffff) % MAX_REST); |
||||
|
||||
// Get header selector
|
||||
uint32_t header_index; |
||||
if (CMD_PopArg(&req, (uint8_t*)&header_index, 4)) return 0; |
||||
|
||||
// Get header value
|
||||
uint16_t len = CMD_ArgLen(&req); |
||||
if (len > 256) return 0; //safety check
|
||||
switch(header_index) { |
||||
case HEADER_GENERIC: |
||||
if(client->header) os_free(client->header); |
||||
client->header = (char*)os_zalloc(len + 3); |
||||
CMD_PopArg(&req, (uint8_t*)client->header, len); |
||||
client->header[len] = '\r'; |
||||
client->header[len+1] = '\n'; |
||||
client->header[len+2] = 0; |
||||
DBG_REST("REST: Set header: %s\r\n", client->header); |
||||
break; |
||||
case HEADER_CONTENT_TYPE: |
||||
if(client->content_type) os_free(client->content_type); |
||||
client->content_type = (char*)os_zalloc(len + 3); |
||||
CMD_PopArg(&req, (uint8_t*)client->content_type, len); |
||||
client->content_type[len] = '\r'; |
||||
client->content_type[len+1] = '\n'; |
||||
client->content_type[len+2] = 0; |
||||
DBG_REST("REST: Set content_type: %s\r\n", client->content_type); |
||||
break; |
||||
case HEADER_USER_AGENT: |
||||
if(client->user_agent) os_free(client->user_agent); |
||||
client->user_agent = (char*)os_zalloc(len + 3); |
||||
CMD_PopArg(&req, (uint8_t*)client->user_agent, len); |
||||
client->user_agent[len] = '\r'; |
||||
client->user_agent[len+1] = '\n'; |
||||
client->user_agent[len+2] = 0; |
||||
DBG_REST("REST: Set user_agent: %s\r\n", client->user_agent); |
||||
break; |
||||
} |
||||
return 1; |
||||
} |
||||
|
||||
uint32_t ICACHE_FLASH_ATTR |
||||
REST_Request(CmdPacket *cmd) { |
||||
CmdRequest req; |
||||
CMD_Request(&req, cmd); |
||||
DBG_REST("REST: request"); |
||||
// Get client
|
||||
uint32_t clientNum; |
||||
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) goto fail; |
||||
if ((clientNum & 0xffff0000) != REST_CB) goto fail; |
||||
clientNum &= 0xffff; |
||||
RestClient *client = restClient + clientNum % MAX_REST; |
||||
DBG_REST(" #%ld", clientNum); |
||||
// Get HTTP method
|
||||
uint16_t len = CMD_ArgLen(&req); |
||||
if (len > 15) goto fail; |
||||
char method[16]; |
||||
CMD_PopArg(&req, method, len); |
||||
method[len] = 0; |
||||
DBG_REST(" method=%s", method); |
||||
// Get HTTP path
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 1023) goto fail; |
||||
char path[1024]; |
||||
CMD_PopArg(&req, path, len); |
||||
path[len] = 0; |
||||
DBG_REST(" path=%s", path); |
||||
// Get HTTP body
|
||||
uint32_t realLen = 0; |
||||
if (CMD_GetArgc(&req) == 3) { |
||||
realLen = 0; |
||||
len = 0; |
||||
} else { |
||||
CMD_PopArg(&req, (uint8_t*)&realLen, 4); |
||||
|
||||
len = CMD_ArgLen(&req); |
||||
if (len > 2048 || realLen > len) goto fail; |
||||
} |
||||
DBG_REST(" bodyLen=%ld", realLen); |
||||
|
||||
// we need to allocate memory for the header plus the body. First we count the length of the
|
||||
// header (including some extra counted "%s" and then we add the body length. We allocate the
|
||||
// whole shebang and copy everything into it.
|
||||
// BTW, use http/1.0 to avoid responses with transfer-encoding: chunked
|
||||
char *headerFmt = "%s %s HTTP/1.0\r\n" |
||||
"Host: %s\r\n" |
||||
"%s" |
||||
"Content-Length: %d\r\n" |
||||
"Connection: close\r\n" |
||||
"Content-Type: %s\r\n" |
||||
"User-Agent: %s\r\n\r\n"; |
||||
uint16_t headerLen = strlen(headerFmt) + strlen(method) + strlen(path) + strlen(client->host) + |
||||
strlen(client->header) + strlen(client->content_type) + strlen(client->user_agent); |
||||
DBG_REST(" hdrLen=%d", headerLen); |
||||
if (client->data) os_free(client->data); |
||||
client->data = (char*)os_zalloc(headerLen + realLen); |
||||
if (client->data == NULL) goto fail; |
||||
DBG_REST(" totLen=%ld data=%p", headerLen + realLen, client->data); |
||||
client->data_len = os_sprintf((char*)client->data, headerFmt, method, path, client->host, |
||||
client->header, realLen, client->content_type, client->user_agent); |
||||
DBG_REST(" hdrLen=%d", client->data_len); |
||||
|
||||
if (realLen > 0) { |
||||
CMD_PopArg(&req, client->data + client->data_len, realLen); |
||||
client->data_len += realLen; |
||||
} |
||||
DBG_REST("\n"); |
||||
|
||||
//DBG_REST("REST request: %s", (char*)client->data);
|
||||
|
||||
DBG_REST("REST: pCon state=%d\n", client->pCon->state); |
||||
client->pCon->state = ESPCONN_NONE; |
||||
espconn_regist_connectcb(client->pCon, tcpclient_connect_cb); |
||||
espconn_regist_reconcb(client->pCon, tcpclient_recon_cb); |
||||
|
||||
if(UTILS_StrToIP((char *)client->host, &client->pCon->proto.tcp->remote_ip)) { |
||||
DBG_REST("REST: Connect to ip %s:%ld\n",client->host, client->port); |
||||
//if(client->security){
|
||||
// espconn_secure_connect(client->pCon);
|
||||
//}
|
||||
//else {
|
||||
espconn_connect(client->pCon); |
||||
//}
|
||||
} else { |
||||
DBG_REST("REST: Connect to host %s:%ld\n", client->host, client->port); |
||||
espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, rest_dns_found); |
||||
} |
||||
|
||||
return 1; |
||||
|
||||
fail: |
||||
DBG_REST("\n"); |
||||
return 0; |
||||
} |
@ -0,0 +1,40 @@ |
||||
/*
|
||||
* api.h |
||||
* |
||||
* Created on: Mar 4, 2015 |
||||
* Author: Minh |
||||
*/ |
||||
|
||||
#ifndef MODULES_API_H_ |
||||
#define MODULES_API_H_ |
||||
|
||||
#include "c_types.h" |
||||
#include "ip_addr.h" |
||||
#include "cmd.h" |
||||
|
||||
typedef enum { |
||||
HEADER_GENERIC = 0, |
||||
HEADER_CONTENT_TYPE, |
||||
HEADER_USER_AGENT |
||||
} HEADER_TYPE; |
||||
|
||||
typedef struct { |
||||
char *host; |
||||
uint32_t port; |
||||
uint32_t security; |
||||
ip_addr_t ip; |
||||
struct espconn *pCon; |
||||
char *header; |
||||
char *data; |
||||
uint16_t data_len; |
||||
uint16_t data_sent; |
||||
char *content_type; |
||||
char *user_agent; |
||||
uint32_t resp_cb; |
||||
} RestClient; |
||||
|
||||
uint32_t REST_Setup(CmdPacket *cmd); |
||||
uint32_t REST_Request(CmdPacket *cmd); |
||||
uint32_t REST_SetHeader(CmdPacket *cmd); |
||||
|
||||
#endif /* MODULES_INCLUDE_API_H_ */ |
@ -0,0 +1,78 @@ |
||||
/*
|
||||
* Copyright (c) 2005, Swedish Institute of Computer Science |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* 3. Neither the name of the Institute nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND |
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE |
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
||||
* SUCH DAMAGE. |
||||
* |
||||
* This file is part of the Contiki operating system. |
||||
* |
||||
*/ |
||||
|
||||
/** \addtogroup crc16
|
||||
* @{ */ |
||||
|
||||
/**
|
||||
* \file |
||||
* Implementation of the CRC16 calculcation |
||||
* \author |
||||
* Adam Dunkels <adam@sics.se> |
||||
* |
||||
*/ |
||||
|
||||
/* CITT CRC16 polynomial ^16 + ^12 + ^5 + 1 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
unsigned short |
||||
crc16_add(unsigned char b, unsigned short acc) |
||||
{ |
||||
/*
|
||||
acc = (unsigned char)(acc >> 8) | (acc << 8); |
||||
acc ^= b; |
||||
acc ^= (unsigned char)(acc & 0xff) >> 4; |
||||
acc ^= (acc << 8) << 4; |
||||
acc ^= ((acc & 0xff) << 4) << 1; |
||||
*/ |
||||
|
||||
acc ^= b; |
||||
acc = (acc >> 8) | (acc << 8); |
||||
acc ^= (acc & 0xff00) << 4; |
||||
acc ^= (acc >> 8) >> 4; |
||||
acc ^= (acc & 0xff00) >> 5; |
||||
return acc; |
||||
} |
||||
/*---------------------------------------------------------------------------*/ |
||||
unsigned short |
||||
crc16_data(const unsigned char *data, int len, unsigned short acc) |
||||
{ |
||||
int i; |
||||
|
||||
for(i = 0; i < len; ++i) { |
||||
acc = crc16_add(*data, acc); |
||||
++data; |
||||
} |
||||
return acc; |
||||
} |
||||
/*---------------------------------------------------------------------------*/ |
||||
|
||||
/** @} */ |
@ -0,0 +1,100 @@ |
||||
/*
|
||||
* Copyright (c) 2005, Swedish Institute of Computer Science |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* 3. Neither the name of the Institute nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND |
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE |
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
||||
* SUCH DAMAGE. |
||||
* |
||||
* This file is part of the Contiki operating system. |
||||
* |
||||
*/ |
||||
|
||||
/**
|
||||
* \file |
||||
* Header file for the CRC16 calculcation |
||||
* \author |
||||
* Adam Dunkels <adam@sics.se> |
||||
* |
||||
*/ |
||||
|
||||
/** \addtogroup lib
|
||||
* @{ */ |
||||
|
||||
/**
|
||||
* \defgroup crc16 Cyclic Redundancy Check 16 (CRC16) calculation |
||||
* |
||||
* The Cyclic Redundancy Check 16 is a hash function that produces a |
||||
* checksum that is used to detect errors in transmissions. The CRC16 |
||||
* calculation module is an iterative CRC calculator that can be used |
||||
* to cumulatively update a CRC checksum for every incoming byte. |
||||
* |
||||
* @{ |
||||
*/ |
||||
|
||||
#ifndef CRC16_H_ |
||||
#define CRC16_H_ |
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
/**
|
||||
* \brief Update an accumulated CRC16 checksum with one byte. |
||||
* \param b The byte to be added to the checksum |
||||
* \param crc The accumulated CRC that is to be updated. |
||||
* \return The updated CRC checksum. |
||||
* |
||||
* This function updates an accumulated CRC16 checksum |
||||
* with one byte. It can be used as a running checksum, or |
||||
* to checksum an entire data block. |
||||
* |
||||
* \note The algorithm used in this implementation is |
||||
* tailored for a running checksum and does not perform as |
||||
* well as a table-driven algorithm when checksumming an |
||||
* entire data block. |
||||
* |
||||
*/ |
||||
unsigned short crc16_add(unsigned char b, unsigned short crc); |
||||
|
||||
/**
|
||||
* \brief Calculate the CRC16 over a data area |
||||
* \param data Pointer to the data |
||||
* \param datalen The length of the data |
||||
* \param acc The accumulated CRC that is to be updated (or zero). |
||||
* \return The CRC16 checksum. |
||||
* |
||||
* This function calculates the CRC16 checksum of a data area. |
||||
* |
||||
* \note The algorithm used in this implementation is |
||||
* tailored for a running checksum and does not perform as |
||||
* well as a table-driven algorithm when checksumming an |
||||
* entire data block. |
||||
*/ |
||||
unsigned short crc16_data(const unsigned char *data, int datalen, |
||||
unsigned short acc); |
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif /* CRC16_H_ */ |
||||
|
||||
/** @} */ |
||||
/** @} */ |
@ -0,0 +1,133 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
|
||||
#include "esp8266.h" |
||||
#include "uart.h" |
||||
#include "crc16.h" |
||||
#include "serbridge.h" |
||||
#include "console.h" |
||||
#include "cmd.h" |
||||
|
||||
uint8_t slip_disabled; // temporarily disable slip to allow flashing of attached MCU
|
||||
|
||||
extern void ICACHE_FLASH_ATTR console_process(char *buf, short len); |
||||
|
||||
// This SLIP parser does not conform to RFC 1055 https://tools.ietf.org/html/rfc1055,
|
||||
// instead, it implements the framing implemented in https://github.com/tuanpmt/esp_bridge
|
||||
// It accumulates each packet into a static buffer and calls cmd_parse() when the end
|
||||
// of a packet is reached. It expects cmd_parse() to copy anything it needs from the
|
||||
// buffer elsewhere as the buffer is immediately reused.
|
||||
// One special feature is that if the first two characters of a packet are both printable or
|
||||
// \n or \r then the parser assumes it's dealing with console debug output and calls
|
||||
// slip_console(c) for each character and does not accumulate chars in the buffer until the
|
||||
// next SLIP_END marker is seen. This allows random console debug output to come in between
|
||||
// packets as long as each packet starts *and* ends with SLIP_END (which is an official
|
||||
// variation on the SLIP protocol).
|
||||
|
||||
static bool slip_escaped; // true when prev char received is escape
|
||||
static bool slip_inpkt; // true when we're after SLIP_START and before SLIP_END
|
||||
#define SLIP_MAX 1024 // max length of SLIP packet
|
||||
static char slip_buf[SLIP_MAX]; // buffer for current SLIP packet
|
||||
static short slip_len; // accumulated length in slip_buf
|
||||
|
||||
// SLIP process a packet or a bunch of debug console chars
|
||||
static void ICACHE_FLASH_ATTR |
||||
slip_process() { |
||||
if (slip_len < 1) return; |
||||
|
||||
if (!slip_inpkt) { |
||||
// debug console stuff
|
||||
console_process(slip_buf, slip_len); |
||||
} else { |
||||
// proper SLIP packet, invoke command processor after checking CRC
|
||||
//os_printf("SLIP: rcv %d\n", slip_len);
|
||||
if (slip_len > 2) { |
||||
uint16_t crc = crc16_data((uint8_t*)slip_buf, slip_len-2, 0); |
||||
uint16_t rcv = ((uint16_t)slip_buf[slip_len-2]) | ((uint16_t)slip_buf[slip_len-1] << 8); |
||||
if (crc == rcv) { |
||||
CMD_parse_packet((uint8_t*)slip_buf, slip_len-2); |
||||
} else { |
||||
os_printf("SLIP: bad CRC, crc=%x rcv=%x\n", crc, rcv); |
||||
|
||||
#ifdef SLIP_DBG |
||||
for (short i=0; i<slip_len; i++) { |
||||
if (slip_buf[i] >= ' ' && slip_buf[i] <= '~') |
||||
os_printf("%c", slip_buf[i]); |
||||
else |
||||
os_printf("\\%02X", slip_buf[i]); |
||||
} |
||||
os_printf("\n"); |
||||
#endif |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#if 0 |
||||
// determine whether a character is printable or not (or \r \n)
|
||||
static bool ICACHE_FLASH_ATTR |
||||
slip_printable(char c) { |
||||
return (c >= ' ' && c <= '~') || c == '\n' || c == '\r'; |
||||
} |
||||
#endif |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
slip_reset() { |
||||
//os_printf("SLIP: reset\n");
|
||||
slip_inpkt = false; |
||||
slip_escaped = false; |
||||
slip_len = 0; |
||||
} |
||||
|
||||
// SLIP parse a single character
|
||||
static void ICACHE_FLASH_ATTR |
||||
slip_parse_char(char c) { |
||||
if (!slip_inpkt) { |
||||
if (c == SLIP_START) { |
||||
if (slip_len > 0) console_process(slip_buf, slip_len); |
||||
slip_reset(); |
||||
slip_inpkt = true; |
||||
#ifdef SLIP_DBG |
||||
os_printf("SLIP: start\n"); |
||||
#endif |
||||
return; |
||||
} |
||||
} else if (slip_escaped) { |
||||
// prev char was SLIP_REPL
|
||||
c = SLIP_ESC(c); |
||||
slip_escaped = false; |
||||
} else { |
||||
switch (c) { |
||||
case SLIP_REPL: |
||||
slip_escaped = true; |
||||
return; |
||||
case SLIP_END: |
||||
// end of packet, process it and get ready for next one
|
||||
if (slip_len > 0) slip_process(); |
||||
slip_reset(); |
||||
return; |
||||
case SLIP_START: |
||||
os_printf("SLIP: got SLIP_START while in packet?\n"); |
||||
//os_printf("SLIP: rcv %d:", slip_len);
|
||||
//for (int i=0; i<slip_len; i++) os_printf(" %02x", slip_buf[i]);
|
||||
//os_printf("\n");
|
||||
slip_reset(); |
||||
return; |
||||
} |
||||
} |
||||
if (slip_len < SLIP_MAX) slip_buf[slip_len++] = c; |
||||
} |
||||
|
||||
// callback with a buffer of characters that have arrived on the uart
|
||||
void ICACHE_FLASH_ATTR |
||||
slip_parse_buf(char *buf, short length) { |
||||
// do SLIP parsing
|
||||
for (short i=0; i<length; i++) |
||||
slip_parse_char(buf[i]); |
||||
|
||||
// if we're in-between packets (debug console) then print it now
|
||||
if (!slip_inpkt && length > 0) { |
||||
slip_process(); |
||||
slip_reset(); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,6 @@ |
||||
#ifndef SLIP_H |
||||
#define SLIP_H |
||||
|
||||
void slip_parse_buf(char *buf, short length); |
||||
|
||||
#endif |
@ -1,72 +0,0 @@ |
||||
/*
|
||||
Some random cgi routines. |
||||
*/ |
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------------------- |
||||
* "THE BEER-WARE LICENSE" (Revision 42): |
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain |
||||
* this notice you can do whatever you want with this stuff. If we meet some day, |
||||
* and you think this stuff is worth it, you can buy me a beer in return. |
||||
* ---------------------------------------------------------------------------- |
||||
* Heavily modified and enhanced by Thorsten von Eicken in 2015 |
||||
* ---------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
|
||||
#include <esp8266.h> |
||||
#include "cgi.h" |
||||
#include "espfs.h" |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
jsonHeader(HttpdConnData *connData, int code) { |
||||
httpdStartResponse(connData, code); |
||||
httpdHeader(connData, "Cache-Control", "no-cache, no-store, must-revalidate"); |
||||
httpdHeader(connData, "Pragma", "no-cache"); |
||||
httpdHeader(connData, "Expires", "0"); |
||||
httpdHeader(connData, "Content-Type", "application/json"); |
||||
httpdEndHeaders(connData); |
||||
} |
||||
|
||||
#define TOKEN(x) (os_strcmp(token, x) == 0) |
||||
#if 0 |
||||
// Handle system information variables and print their value, returns the number of
|
||||
// characters appended to buff
|
||||
int ICACHE_FLASH_ATTR printGlobalInfo(char *buff, int buflen, char *token) { |
||||
if (TOKEN("si_chip_id")) { |
||||
return os_sprintf(buff, "0x%x", system_get_chip_id()); |
||||
} else if (TOKEN("si_freeheap")) { |
||||
return os_sprintf(buff, "%dKB", system_get_free_heap_size()/1024); |
||||
} else if (TOKEN("si_uptime")) { |
||||
uint32 t = system_get_time() / 1000000; // in seconds
|
||||
return os_sprintf(buff, "%dd%dh%dm%ds", t/(24*3600), (t/(3600))%24, (t/60)%60, t%60); |
||||
} else if (TOKEN("si_boot_version")) { |
||||
return os_sprintf(buff, "%d", system_get_boot_version()); |
||||
} else if (TOKEN("si_boot_address")) { |
||||
return os_sprintf(buff, "0x%x", system_get_userbin_addr()); |
||||
} else if (TOKEN("si_cpu_freq")) { |
||||
return os_sprintf(buff, "%dMhz", system_get_cpu_freq()); |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
extern char *esp_link_version; // in user_main.c
|
||||
|
||||
int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
char buff[1024]; |
||||
// don't use jsonHeader so the response does get cached
|
||||
httpdStartResponse(connData, 200); |
||||
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); |
||||
httpdHeader(connData, "Content-Type", "application/json"); |
||||
httpdEndHeaders(connData); |
||||
// construct json response
|
||||
os_sprintf(buff, |
||||
"{\"menu\": [\"Home\", \"/home.html\", \"Wifi\", \"/wifi/wifi.html\"," |
||||
"\"\xC2\xB5" "C Console\", \"/console.html\", \"Debug log\", \"/log.html\" ],\n" |
||||
" \"version\": \"%s\" }", esp_link_version); |
||||
httpdSend(connData, buff, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
@ -1,9 +0,0 @@ |
||||
#ifndef CGI_H |
||||
#define CGI_H |
||||
|
||||
#include "httpd.h" |
||||
|
||||
void jsonHeader(HttpdConnData *connData, int code); |
||||
int cgiMenu(HttpdConnData *connData); |
||||
|
||||
#endif |
@ -1,577 +0,0 @@ |
||||
/*
|
||||
Cgi/template routines for the /wifi url. |
||||
*/ |
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------------------- |
||||
* "THE BEER-WARE LICENSE" (Revision 42): |
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain |
||||
* this notice you can do whatever you want with this stuff. If we meet some day, |
||||
* and you think this stuff is worth it, you can buy me a beer in return. |
||||
* ---------------------------------------------------------------------------- |
||||
* Heavily modified and enhanced by Thorsten von Eicken in 2015 |
||||
* ---------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
|
||||
#include <esp8266.h> |
||||
#include "cgiwifi.h" |
||||
#include "cgi.h" |
||||
#include "status.h" |
||||
#include "config.h" |
||||
#include "log.h" |
||||
|
||||
//#define SLEEP_MODE LIGHT_SLEEP_T
|
||||
#define SLEEP_MODE MODEM_SLEEP_T |
||||
|
||||
// ===== wifi status change callback
|
||||
|
||||
uint8_t wifiState = wifiIsDisconnected; |
||||
// reasons for which a connection failed
|
||||
uint8_t wifiReason = 0; |
||||
static char *wifiReasons[] = { |
||||
"", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", |
||||
"not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", |
||||
"ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", |
||||
"ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", |
||||
"unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", |
||||
"beacon_timeout", "no_ap_found" }; |
||||
|
||||
static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; |
||||
static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; |
||||
|
||||
static char* ICACHE_FLASH_ATTR wifiGetReason(void) { |
||||
if (wifiReason <= 24) return wifiReasons[wifiReason]; |
||||
if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; |
||||
return wifiReasons[1]; |
||||
} |
||||
|
||||
// handler for wifi status change callback coming in from espressif library
|
||||
static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) { |
||||
switch (evt->event) { |
||||
case EVENT_STAMODE_CONNECTED: |
||||
wifiState = wifiIsConnected; |
||||
wifiReason = 0; |
||||
os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, |
||||
evt->event_info.connected.channel); |
||||
statusWifiUpdate(wifiState); |
||||
break; |
||||
case EVENT_STAMODE_DISCONNECTED: |
||||
wifiState = wifiIsDisconnected; |
||||
wifiReason = evt->event_info.disconnected.reason; |
||||
os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", |
||||
evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); |
||||
statusWifiUpdate(wifiState); |
||||
break; |
||||
case EVENT_STAMODE_AUTHMODE_CHANGE: |
||||
os_printf("Wifi auth mode: %d -> %d\n", |
||||
evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); |
||||
break; |
||||
case EVENT_STAMODE_GOT_IP: |
||||
wifiState = wifiGotIP; |
||||
wifiReason = 0; |
||||
os_printf("Wifi got ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR "\n", |
||||
IP2STR(&evt->event_info.got_ip.ip), IP2STR(&evt->event_info.got_ip.mask), |
||||
IP2STR(&evt->event_info.got_ip.gw)); |
||||
statusWifiUpdate(wifiState); |
||||
break; |
||||
case EVENT_SOFTAPMODE_STACONNECTED: |
||||
os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", |
||||
MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); |
||||
break; |
||||
case EVENT_SOFTAPMODE_STADISCONNECTED: |
||||
os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", |
||||
MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// ===== wifi scanning
|
||||
|
||||
//WiFi access point data
|
||||
typedef struct { |
||||
char ssid[32]; |
||||
sint8 rssi; |
||||
char enc; |
||||
} ApData; |
||||
|
||||
//Scan result
|
||||
typedef struct { |
||||
char scanInProgress; //if 1, don't access the underlying stuff from the webpage.
|
||||
ApData **apData; |
||||
int noAps; |
||||
} ScanResultData; |
||||
|
||||
//Static scan status storage.
|
||||
static ScanResultData cgiWifiAps; |
||||
|
||||
//Callback the code calls when a wlan ap scan is done. Basically stores the result in
|
||||
//the cgiWifiAps struct.
|
||||
void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { |
||||
int n; |
||||
struct bss_info *bss_link = (struct bss_info *)arg; |
||||
|
||||
if (status!=OK) { |
||||
os_printf("wifiScanDoneCb status=%d\n", status); |
||||
cgiWifiAps.scanInProgress=0; |
||||
return; |
||||
} |
||||
|
||||
//Clear prev ap data if needed.
|
||||
if (cgiWifiAps.apData!=NULL) { |
||||
for (n=0; n<cgiWifiAps.noAps; n++) os_free(cgiWifiAps.apData[n]); |
||||
os_free(cgiWifiAps.apData); |
||||
} |
||||
|
||||
//Count amount of access points found.
|
||||
n=0; |
||||
while (bss_link != NULL) { |
||||
bss_link = bss_link->next.stqe_next; |
||||
n++; |
||||
} |
||||
//Allocate memory for access point data
|
||||
cgiWifiAps.apData=(ApData **)os_malloc(sizeof(ApData *)*n); |
||||
cgiWifiAps.noAps=n; |
||||
os_printf("Scan done: found %d APs\n", n); |
||||
|
||||
//Copy access point data to the static struct
|
||||
n=0; |
||||
bss_link = (struct bss_info *)arg; |
||||
while (bss_link != NULL) { |
||||
if (n>=cgiWifiAps.noAps) { |
||||
//This means the bss_link changed under our nose. Shouldn't happen!
|
||||
//Break because otherwise we will write in unallocated memory.
|
||||
os_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); |
||||
break; |
||||
} |
||||
//Save the ap data.
|
||||
cgiWifiAps.apData[n]=(ApData *)os_malloc(sizeof(ApData)); |
||||
cgiWifiAps.apData[n]->rssi=bss_link->rssi; |
||||
cgiWifiAps.apData[n]->enc=bss_link->authmode; |
||||
strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); |
||||
os_printf("bss%d: %s (%d)\n", n+1, (char*)bss_link->ssid, bss_link->rssi); |
||||
|
||||
bss_link = bss_link->next.stqe_next; |
||||
n++; |
||||
} |
||||
//We're done.
|
||||
cgiWifiAps.scanInProgress=0; |
||||
} |
||||
|
||||
static ETSTimer scanTimer; |
||||
static void ICACHE_FLASH_ATTR scanStartCb(void *arg) { |
||||
os_printf("Starting a scan\n"); |
||||
wifi_station_scan(NULL, wifiScanDoneCb); |
||||
} |
||||
|
||||
static int ICACHE_FLASH_ATTR cgiWiFiStartScan(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
jsonHeader(connData, 200); |
||||
if (!cgiWifiAps.scanInProgress) { |
||||
cgiWifiAps.scanInProgress = 1; |
||||
os_timer_disarm(&scanTimer); |
||||
os_timer_setfn(&scanTimer, scanStartCb, NULL); |
||||
os_timer_arm(&scanTimer, 1000, 0); |
||||
} |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
char buff[2048]; |
||||
int len; |
||||
|
||||
jsonHeader(connData, 200); |
||||
|
||||
if (cgiWifiAps.scanInProgress==1) { |
||||
//We're still scanning. Tell Javascript code that.
|
||||
len = os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); |
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
len = os_sprintf(buff, "{\"result\": {\"inProgress\": \"0\", \"APs\": [\n"); |
||||
for (int pos=0; pos<cgiWifiAps.noAps; pos++) { |
||||
len += os_sprintf(buff+len, "{\"essid\": \"%s\", \"rssi\": %d, \"enc\": \"%d\"}%s\n", |
||||
cgiWifiAps.apData[pos]->ssid, cgiWifiAps.apData[pos]->rssi, |
||||
cgiWifiAps.apData[pos]->enc, (pos==cgiWifiAps.noAps-1)?"":","); |
||||
} |
||||
len += os_sprintf(buff+len, "]}}\n"); |
||||
//os_printf("Sending %d bytes: %s\n", len, buff);
|
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { |
||||
if (connData->requestType == HTTPD_METHOD_GET) { |
||||
return cgiWiFiGetScan(connData); |
||||
} else if (connData->requestType == HTTPD_METHOD_POST) { |
||||
return cgiWiFiStartScan(connData); |
||||
} else { |
||||
jsonHeader(connData, 404); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
} |
||||
|
||||
// ===== timers to change state and rescue from failed associations
|
||||
|
||||
// reset timer changes back to STA+AP if we can't associate
|
||||
#define RESET_TIMEOUT (15000) // 15 seconds
|
||||
static ETSTimer resetTimer; |
||||
|
||||
// This routine is ran some time after a connection attempt to an access point. If
|
||||
// the connect succeeds, this gets the module in STA-only mode. If it fails, it ensures
|
||||
// that the module is in STA+AP mode so the user has a chance to recover.
|
||||
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { |
||||
int x = wifi_station_get_connect_status(); |
||||
int m = wifi_get_opmode() & 0x3; |
||||
os_printf("Wifi check: mode=%s status=%d\n", wifiMode[m], x); |
||||
|
||||
if (x == STATION_GOT_IP) { |
||||
if (m != 1) { |
||||
#ifdef CHANGE_TO_STA |
||||
// We're happily connected, go to STA mode
|
||||
os_printf("Wifi got IP. Going into STA mode..\n"); |
||||
wifi_set_opmode(1); |
||||
wifi_set_sleep_type(SLEEP_MODE); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
#endif |
||||
} |
||||
log_uart(false); |
||||
// no more resetTimer at this point, gotta use physical reset to recover if in trouble
|
||||
} else { |
||||
if (m != 3) { |
||||
os_printf("Wifi connect failed. Going into STA+AP mode..\n"); |
||||
wifi_set_opmode(3); |
||||
} |
||||
log_uart(true); |
||||
os_printf("Enabling/continuing uart log\n"); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
} |
||||
|
||||
// Temp store for new ap info.
|
||||
static struct station_config stconf; |
||||
// Reassociate timer to delay change of association so the original request can finish
|
||||
static ETSTimer reassTimer; |
||||
|
||||
// Callback actually doing reassociation
|
||||
static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { |
||||
os_printf("Wifi changing association\n"); |
||||
wifi_station_disconnect(); |
||||
stconf.bssid_set = 0; |
||||
wifi_station_set_config(&stconf); |
||||
wifi_station_connect(); |
||||
// Schedule check
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
|
||||
// This cgi uses the routines above to connect to a specific access point with the
|
||||
// given ESSID using the given password.
|
||||
int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { |
||||
char essid[128]; |
||||
char passwd[128]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||
|
||||
int el = httpdFindArg(connData->getArgs, "essid", essid, sizeof(essid)); |
||||
int pl = httpdFindArg(connData->getArgs, "passwd", passwd, sizeof(passwd)); |
||||
|
||||
if (el > 0 && pl >= 0) { |
||||
//Set to 0 if you want to disable the actual reconnecting bit
|
||||
os_strncpy((char*)stconf.ssid, essid, 32); |
||||
os_strncpy((char*)stconf.password, passwd, 64); |
||||
os_printf("Wifi try to connect to AP %s pw %s\n", essid, passwd); |
||||
|
||||
//Schedule disconnect/connect
|
||||
os_timer_disarm(&reassTimer); |
||||
os_timer_setfn(&reassTimer, reassTimerCb, NULL); |
||||
os_timer_arm(&reassTimer, 1000, 0); |
||||
jsonHeader(connData, 200); |
||||
} else { |
||||
jsonHeader(connData, 400); |
||||
httpdSend(connData, "Cannot parse ssid or password", -1); |
||||
} |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static bool parse_ip(char *buff, ip_addr_t *ip_ptr) { |
||||
char *next = buff; // where to start parsing next integer
|
||||
int found = 0; // number of integers parsed
|
||||
uint32_t ip = 0; // the ip addres parsed
|
||||
for (int i=0; i<32; i++) { // 32 is just a safety limit
|
||||
char c = buff[i]; |
||||
if (c == '.' || c == 0) { |
||||
// parse the preceding integer and accumulate into IP address
|
||||
bool last = c == 0; |
||||
buff[i] = 0; |
||||
uint32_t v = atoi(next); |
||||
ip = ip | ((v&0xff)<<(found*8)); |
||||
next = buff+i+1; // next integer starts after the '.'
|
||||
found++; |
||||
if (last) { // if at end of string we better got 4 integers
|
||||
ip_ptr->addr = ip; |
||||
return found == 4; |
||||
} |
||||
continue; |
||||
} |
||||
if (c < '0' || c > '9') return false; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
#define DEBUGIP |
||||
#ifdef DEBUGIP |
||||
static void ICACHE_FLASH_ATTR debugIP() { |
||||
struct ip_info info; |
||||
if (wifi_get_ip_info(0, &info)) { |
||||
os_printf("\"ip\": \"%d.%d.%d.%d\"\n", IP2STR(&info.ip.addr)); |
||||
os_printf("\"netmask\": \"%d.%d.%d.%d\"\n", IP2STR(&info.netmask.addr)); |
||||
os_printf("\"gateway\": \"%d.%d.%d.%d\"\n", IP2STR(&info.gw.addr)); |
||||
os_printf("\"hostname\": \"%s\"\n", wifi_station_get_hostname()); |
||||
} else { |
||||
os_printf("\"ip\": \"-none-\"\n"); |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
// configure Wifi, specifically DHCP vs static IP address based on flash config
|
||||
static void ICACHE_FLASH_ATTR configWifiIP() { |
||||
if (flashConfig.staticip == 0) { |
||||
// let's DHCP!
|
||||
wifi_station_set_hostname(flashConfig.hostname); |
||||
if (wifi_station_dhcpc_status() == DHCP_STARTED) |
||||
wifi_station_dhcpc_stop(); |
||||
wifi_station_dhcpc_start(); |
||||
os_printf("Wifi uses DHCP, hostname=%s\n", flashConfig.hostname); |
||||
} else { |
||||
// no DHCP, we got static network config!
|
||||
wifi_station_dhcpc_stop(); |
||||
struct ip_info ipi; |
||||
ipi.ip.addr = flashConfig.staticip; |
||||
ipi.netmask.addr = flashConfig.netmask; |
||||
ipi.gw.addr = flashConfig.gateway; |
||||
wifi_set_ip_info(0, &ipi); |
||||
os_printf("Wifi uses static IP %d.%d.%d.%d\n", IP2STR(&ipi.ip.addr)); |
||||
} |
||||
#ifdef DEBUGIP |
||||
debugIP(); |
||||
#endif |
||||
} |
||||
|
||||
// Change special settings
|
||||
int ICACHE_FLASH_ATTR cgiWiFiSpecial(HttpdConnData *connData) { |
||||
char dhcp[8]; |
||||
char hostname[32]; |
||||
char staticip[20]; |
||||
char netmask[20]; |
||||
char gateway[20]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||
|
||||
// get args and their string lengths
|
||||
int dl = httpdFindArg(connData->getArgs, "dhcp", dhcp, sizeof(dhcp)); |
||||
int hl = httpdFindArg(connData->getArgs, "hostname", hostname, sizeof(hostname)); |
||||
int sl = httpdFindArg(connData->getArgs, "staticip", staticip, sizeof(staticip)); |
||||
int nl = httpdFindArg(connData->getArgs, "netmask", netmask, sizeof(netmask)); |
||||
int gl = httpdFindArg(connData->getArgs, "gateway", gateway, sizeof(gateway)); |
||||
|
||||
if (!(dl > 0 && hl >= 0 && sl >= 0 && nl >= 0 && gl >= 0)) { |
||||
jsonHeader(connData, 400); |
||||
httpdSend(connData, "Request is missing fields", -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
char url[64]; // redirect URL
|
||||
if (os_strcmp(dhcp, "off") == 0) { |
||||
// parse static IP params
|
||||
struct ip_info ipi; |
||||
bool ok = parse_ip(staticip, &ipi.ip); |
||||
if (nl > 0) ok = ok && parse_ip(netmask, &ipi.netmask); |
||||
else IP4_ADDR(&ipi.netmask, 255, 255, 255, 0); |
||||
if (gl > 0) ok = ok && parse_ip(gateway, &ipi.gw); |
||||
else ipi.gw.addr = 0; |
||||
if (!ok) { |
||||
jsonHeader(connData, 400); |
||||
httpdSend(connData, "Cannot parse static IP config", -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
// save the params in flash
|
||||
flashConfig.staticip = ipi.ip.addr; |
||||
flashConfig.netmask = ipi.netmask.addr; |
||||
flashConfig.gateway = ipi.gw.addr; |
||||
// construct redirect URL
|
||||
os_sprintf(url, "{\"url\": \"http://%d.%d.%d.%d\"}", IP2STR(&ipi.ip)); |
||||
|
||||
} else { |
||||
// no static IP, set hostname
|
||||
if (hl == 0) os_strcpy(hostname, "esp-link"); |
||||
flashConfig.staticip = 0; |
||||
os_strcpy(flashConfig.hostname, hostname); |
||||
os_sprintf(url, "{\"url\": \"http://%s\"}", hostname); |
||||
} |
||||
|
||||
configSave(); // ignore error...
|
||||
// schedule change-over
|
||||
os_timer_disarm(&reassTimer); |
||||
os_timer_setfn(&reassTimer, configWifiIP, NULL); |
||||
os_timer_arm(&reassTimer, 1000, 0); |
||||
// return redirect info
|
||||
jsonHeader(connData, 200); |
||||
httpdSend(connData, url, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
//This cgi changes the operating mode: STA / AP / STA+AP
|
||||
int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { |
||||
int len; |
||||
char buff[1024]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
|
||||
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); |
||||
if (len!=0) { |
||||
int m = atoi(buff); |
||||
os_printf("Wifi switching to mode %d\n", m); |
||||
wifi_set_opmode(m&3); |
||||
if (m == 1) { |
||||
wifi_set_sleep_type(SLEEP_MODE); |
||||
// STA-only mode, reset into STA+AP after a timeout
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
jsonHeader(connData, 200); |
||||
} else { |
||||
jsonHeader(connData, 400); |
||||
} |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static char *connStatuses[] = { "idle", "connecting", "wrong password", "AP not found", |
||||
"failed", "got IP address" }; |
||||
|
||||
static char *wifiWarn[] = { 0, |
||||
"Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>", |
||||
"<b>Can't scan in this mode!</b> Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(3)\\\">STA+AP mode</a>", |
||||
"Switch to <a href=\\\"#\\\" onclick=\\\"changeWifiMode(1)\\\">STA mode</a>", |
||||
}; |
||||
|
||||
#ifdef CHANGE_TO_STA |
||||
#define MODECHANGE "yes" |
||||
#else |
||||
#define MODECHANGE "no" |
||||
#endif |
||||
|
||||
// print various Wifi information into json buffer
|
||||
int ICACHE_FLASH_ATTR printWifiInfo(char *buff) { |
||||
int len; |
||||
|
||||
struct station_config stconf; |
||||
wifi_station_get_config(&stconf); |
||||
|
||||
uint8_t op = wifi_get_opmode() & 0x3; |
||||
char *mode = wifiMode[op]; |
||||
char *status = "unknown"; |
||||
int st = wifi_station_get_connect_status(); |
||||
if (st > 0 && st < sizeof(connStatuses)) status = connStatuses[st]; |
||||
int p = wifi_get_phy_mode(); |
||||
char *phy = wifiPhy[p&3]; |
||||
char *warn = wifiWarn[op]; |
||||
sint8 rssi = wifi_station_get_rssi(); |
||||
if (rssi > 0) rssi = 0; |
||||
uint8 mac_addr[6]; |
||||
wifi_get_macaddr(0, mac_addr); |
||||
uint8_t chan = wifi_get_channel(); |
||||
|
||||
len = os_sprintf(buff, |
||||
"\"mode\": \"%s\", \"modechange\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", " |
||||
"\"rssi\": \"%ddB\", \"warn\": \"%s\", \"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\", \"chan\":%d", |
||||
mode, MODECHANGE, (char*)stconf.ssid, status, phy, rssi, warn, |
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], chan); |
||||
|
||||
struct ip_info info; |
||||
if (wifi_get_ip_info(0, &info)) { |
||||
len += os_sprintf(buff+len, ", \"ip\": \"%d.%d.%d.%d\"", IP2STR(&info.ip.addr)); |
||||
len += os_sprintf(buff+len, ", \"netmask\": \"%d.%d.%d.%d\"", IP2STR(&info.netmask.addr)); |
||||
len += os_sprintf(buff+len, ", \"gateway\": \"%d.%d.%d.%d\"", IP2STR(&info.gw.addr)); |
||||
len += os_sprintf(buff+len, ", \"hostname\": \"%s\"", flashConfig.hostname); |
||||
} else { |
||||
len += os_sprintf(buff+len, ", \"ip\": \"-none-\""); |
||||
} |
||||
len += os_sprintf(buff+len, ", \"staticip\": \"%d.%d.%d.%d\"", IP2STR(&flashConfig.staticip)); |
||||
len += os_sprintf(buff+len, ", \"dhcp\": \"%s\"", flashConfig.staticip > 0 ? "off" : "on"); |
||||
|
||||
return len; |
||||
} |
||||
|
||||
int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { |
||||
char buff[1024]; |
||||
int len; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
jsonHeader(connData, 200); |
||||
|
||||
len = os_sprintf(buff, "{"); |
||||
len += printWifiInfo(buff+len); |
||||
len += os_sprintf(buff+len, ", "); |
||||
|
||||
if (wifiReason != 0) { |
||||
len += os_sprintf(buff+len, "\"reason\": \"%s\", ", wifiGetReason()); |
||||
} |
||||
|
||||
#if 0 |
||||
// commented out 'cause often the client that requested the change can't get a request in to
|
||||
// find out that it succeeded. Better to just wait the std 15 seconds...
|
||||
int st=wifi_station_get_connect_status(); |
||||
if (st == STATION_GOT_IP) { |
||||
if (wifi_get_opmode() != 1) { |
||||
// Reset into AP-only mode sooner.
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, 1000, 0); |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
len += os_sprintf(buff+len, "\"x\":0}\n"); |
||||
|
||||
os_printf(" -> %s\n", buff); |
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
// Cgi to return various Wifi information
|
||||
int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) { |
||||
char buff[1024]; |
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
|
||||
os_strcpy(buff, "{"); |
||||
printWifiInfo(buff+1); |
||||
os_strcat(buff, "}"); |
||||
|
||||
jsonHeader(connData, 200); |
||||
httpdSend(connData, buff, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
// Init the wireless, which consists of setting a timer if we expect to connect to an AP
|
||||
// so we can revert to STA+AP mode if we can't connect.
|
||||
void ICACHE_FLASH_ATTR wifiInit() { |
||||
wifi_set_phy_mode(2); |
||||
int x = wifi_get_opmode() & 0x3; |
||||
os_printf("Wifi init, mode=%s\n", wifiMode[x]); |
||||
configWifiIP(); |
||||
|
||||
wifi_set_event_handler_cb(wifiHandleEventCb); |
||||
// check on the wifi in a few seconds to see whether we need to switch mode
|
||||
os_timer_disarm(&resetTimer); |
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
||||
os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); |
||||
} |
||||
|
@ -1,128 +0,0 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
/* Configuration stored in flash */ |
||||
|
||||
#include <esp8266.h> |
||||
#include <osapi.h> |
||||
#include "config.h" |
||||
#include "espfs.h" |
||||
|
||||
// hack: this from LwIP
|
||||
extern uint16_t inet_chksum(void *dataptr, uint16_t len); |
||||
|
||||
FlashConfig flashConfig; |
||||
FlashConfig flashDefault = { |
||||
33, 0, 0, |
||||
MCU_RESET_PIN, MCU_ISP_PIN, LED_CONN_PIN, LED_SERIAL_PIN, |
||||
115200, |
||||
"esp-link\0 ", // hostname
|
||||
0, 0x00ffffff, 0, // static ip, netmask, gateway
|
||||
0, // log mode
|
||||
}; |
||||
|
||||
typedef union { |
||||
FlashConfig fc; |
||||
uint8_t block[128]; |
||||
} FlashFull; |
||||
|
||||
#define FLASH_MAGIC (0xaa55) |
||||
|
||||
#define FLASH_ADDR (0x3E000) |
||||
#define FLASH_SECT (4096) |
||||
static int flash_pri; // primary flash sector (0 or 1, or -1 for error)
|
||||
|
||||
#if 0 |
||||
static void memDump(void *addr, int len) { |
||||
for (int i=0; i<len; i++) { |
||||
os_printf("0x%02x", ((uint8_t *)addr)[i]); |
||||
} |
||||
os_printf("\n"); |
||||
} |
||||
#endif |
||||
|
||||
bool ICACHE_FLASH_ATTR configSave(void) { |
||||
|
||||
FlashFull ff; |
||||
memset(&ff, 0, sizeof(ff)); |
||||
memcpy(&ff, &flashConfig, sizeof(FlashConfig)); |
||||
uint32_t seq = ff.fc.seq+1; |
||||
// erase secondary
|
||||
uint32_t addr = FLASH_ADDR + (1-flash_pri)*FLASH_SECT; |
||||
if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) |
||||
return false; // no harm done, give up
|
||||
// calculate CRC
|
||||
ff.fc.seq = seq; |
||||
ff.fc.magic = FLASH_MAGIC; |
||||
ff.fc.crc = 0; |
||||
//os_printf("cksum of: ");
|
||||
//memDump(&ff, sizeof(ff));
|
||||
ff.fc.crc = inet_chksum(&ff, sizeof(ff)); |
||||
//os_printf("cksum is %04x\n", ff.fc.crc);
|
||||
// write primary with incorrect seq
|
||||
ff.fc.seq = 0xffffffff; |
||||
if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) |
||||
return false; // no harm done, give up
|
||||
// fill in correct seq
|
||||
ff.fc.seq = seq; |
||||
if (spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)) != SPI_FLASH_RESULT_OK) |
||||
return false; // most likely failed, but no harm if successful
|
||||
// now that we have safely written the new version, erase old primary
|
||||
addr = FLASH_ADDR + flash_pri*FLASH_SECT; |
||||
flash_pri = 1-flash_pri; |
||||
if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) |
||||
return true; // no back-up but we're OK
|
||||
// write secondary
|
||||
ff.fc.seq = 0xffffffff; |
||||
if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) |
||||
return true; // no back-up but we're OK
|
||||
ff.fc.seq = seq; |
||||
spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)); |
||||
return true; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR configWipe(void) { |
||||
spi_flash_erase_sector(FLASH_ADDR>>12); |
||||
spi_flash_erase_sector((FLASH_ADDR+FLASH_SECT)>>12); |
||||
} |
||||
|
||||
static uint32_t ICACHE_FLASH_ATTR selectPrimary(FlashFull *fc0, FlashFull *fc1); |
||||
|
||||
bool ICACHE_FLASH_ATTR configRestore(void) { |
||||
FlashFull ff0, ff1; |
||||
// read both flash sectors
|
||||
if (spi_flash_read(FLASH_ADDR, (void *)&ff0, sizeof(ff0)) != SPI_FLASH_RESULT_OK) |
||||
memset(&ff0, 0, sizeof(ff0)); // clear in case of error
|
||||
if (spi_flash_read(FLASH_ADDR+FLASH_SECT, (void *)&ff1, sizeof(ff1)) != SPI_FLASH_RESULT_OK) |
||||
memset(&ff1, 0, sizeof(ff1)); // clear in case of error
|
||||
// figure out which one is good
|
||||
flash_pri = selectPrimary(&ff0, &ff1); |
||||
// if neither is OK, we revert to defaults
|
||||
if (flash_pri < 0) { |
||||
memcpy(&flashConfig, &flashDefault, sizeof(FlashConfig)); |
||||
flash_pri = 0; |
||||
return false; |
||||
} |
||||
// copy good one into global var and return
|
||||
memcpy(&flashConfig, flash_pri == 0 ? &ff0.fc : &ff1.fc, sizeof(FlashConfig)); |
||||
return true; |
||||
} |
||||
|
||||
static uint32_t ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) { |
||||
// check CRC of ff0
|
||||
uint16_t crc = ff0->fc.crc; |
||||
ff0->fc.crc = 0; |
||||
bool ff0_crc_ok = inet_chksum(ff0, sizeof(FlashFull)) == crc; |
||||
|
||||
// check CRC of ff1
|
||||
crc = ff1->fc.crc; |
||||
ff1->fc.crc = 0; |
||||
bool ff1_crc_ok = inet_chksum(ff1, sizeof(FlashFull)) == crc; |
||||
|
||||
// decided which we like better
|
||||
if (ff0_crc_ok) |
||||
if (!ff1_crc_ok || ff0->fc.seq >= ff1->fc.seq) |
||||
return 0; // use first sector as primary
|
||||
else |
||||
return 1; // second sector is newer
|
||||
else |
||||
return ff1_crc_ok ? 1 : -1; |
||||
} |
@ -1,19 +0,0 @@ |
||||
#ifndef CONFIG_H |
||||
#define CONFIG_H |
||||
|
||||
typedef struct { |
||||
uint32_t seq; // flash write sequence number
|
||||
uint16_t magic, crc; |
||||
int8_t reset_pin, isp_pin, conn_led_pin, ser_led_pin; |
||||
int32_t baud_rate; |
||||
char hostname[32]; // if using DHCP
|
||||
uint32_t staticip, netmask, gateway; // using DHCP if staticip==0
|
||||
uint8_t log_mode; // UART log debug mode
|
||||
} FlashConfig; |
||||
extern FlashConfig flashConfig; |
||||
|
||||
bool configSave(void); |
||||
bool configRestore(void); |
||||
void configWipe(void); |
||||
|
||||
#endif |
@ -1,164 +0,0 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
|
||||
#include <esp8266.h> |
||||
#include "uart.h" |
||||
#include "cgi.h" |
||||
#include "config.h" |
||||
#include "log.h" |
||||
|
||||
// Web log for the esp8266 to replace outputting to uart1.
|
||||
// The web log has a 1KB circular in-memory buffer which os_printf prints into and
|
||||
// the HTTP handler simply displays the buffer content on a web page.
|
||||
|
||||
// see console.c for invariants (same here)
|
||||
#define BUF_MAX (1400) |
||||
static char log_buf[BUF_MAX]; |
||||
static int log_wr, log_rd; |
||||
static int log_pos; |
||||
static bool log_no_uart; // start out printing to uart
|
||||
static bool log_newline; // at start of a new line
|
||||
|
||||
// called from wifi reset timer to turn UART on when we loose wifi and back off
|
||||
// when we connect to wifi AP. Here this is gated by the flash setting
|
||||
void ICACHE_FLASH_ATTR |
||||
log_uart(bool enable) { |
||||
if (!enable && !log_no_uart && flashConfig.log_mode != LOG_MODE_ON) { |
||||
// we're asked to turn uart off, and uart is on, and the flash setting isn't always-on
|
||||
#if 1 |
||||
os_printf("Turning OFF uart log\n"); |
||||
os_delay_us(4*1000L); // time for uart to flush
|
||||
log_no_uart = !enable; |
||||
#endif |
||||
} else if (enable && log_no_uart && flashConfig.log_mode != LOG_MODE_OFF) { |
||||
// we're asked to turn uart on, and uart is off, and the flash setting isn't always-off
|
||||
log_no_uart = !enable; |
||||
os_printf("Turning ON uart log\n"); |
||||
} |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
log_write(char c) { |
||||
log_buf[log_wr] = c; |
||||
log_wr = (log_wr+1) % BUF_MAX; |
||||
if (log_wr == log_rd) { |
||||
log_rd = (log_rd+1) % BUF_MAX; // full, eat first char
|
||||
log_pos++; |
||||
} |
||||
} |
||||
|
||||
#if 0 |
||||
static char ICACHE_FLASH_ATTR |
||||
log_read(void) { |
||||
char c = 0; |
||||
if (log_rd != log_wr) { |
||||
c = log_buf[log_rd]; |
||||
log_rd = (log_rd+1) % BUF_MAX; |
||||
} |
||||
return c; |
||||
} |
||||
#endif |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
log_write_char(char c) { |
||||
// Uart output unless disabled
|
||||
if (!log_no_uart) { |
||||
if (log_newline) { |
||||
char buff[16]; |
||||
int l = os_sprintf(buff, "%6d> ", (system_get_time()/1000)%1000000); |
||||
for (int i=0; i<l; i++) |
||||
uart0_write_char(buff[i]); |
||||
log_newline = false; |
||||
} |
||||
uart0_write_char(c); |
||||
if (c == '\n') { |
||||
log_newline = true; |
||||
uart0_write_char('\r'); |
||||
} |
||||
} |
||||
// Store in log buffer
|
||||
if (c == '\n') log_write('\r'); |
||||
log_write(c); |
||||
} |
||||
|
||||
int ICACHE_FLASH_ATTR |
||||
ajaxLog(HttpdConnData *connData) { |
||||
char buff[2048]; |
||||
int len; // length of text in buff
|
||||
int log_len = (log_wr+BUF_MAX-log_rd) % BUF_MAX; // num chars in log_buf
|
||||
int start = 0; // offset onto log_wr to start sending out chars
|
||||
|
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
jsonHeader(connData, 200); |
||||
|
||||
// figure out where to start in buffer based on URI param
|
||||
len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); |
||||
if (len > 0) { |
||||
start = atoi(buff); |
||||
if (start < log_pos) { |
||||
start = 0; |
||||
} else if (start >= log_pos+log_len) { |
||||
start = log_len; |
||||
} else { |
||||
start = start - log_pos; |
||||
} |
||||
} |
||||
|
||||
// start outputting
|
||||
len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", |
||||
log_len-start, log_pos+start); |
||||
|
||||
int rd = (log_rd+start) % BUF_MAX; |
||||
while (len < 2040 && rd != log_wr) { |
||||
uint8_t c = log_buf[rd]; |
||||
if (c == '\\' || c == '"') { |
||||
buff[len++] = '\\'; |
||||
buff[len++] = c; |
||||
} else if (c < ' ') { |
||||
len += os_sprintf(buff+len, "\\u%04x", c); |
||||
} else { |
||||
buff[len++] = c; |
||||
} |
||||
rd = (rd + 1) % BUF_MAX; |
||||
} |
||||
os_strcpy(buff+len, "\"}"); len+=2; |
||||
httpdSend(connData, buff, len); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
static char *dbg_mode[] = { "auto", "off", "on" }; |
||||
|
||||
int ICACHE_FLASH_ATTR |
||||
ajaxLogDbg(HttpdConnData *connData) { |
||||
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
|
||||
char buff[512]; |
||||
int len, status = 400; |
||||
len = httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); |
||||
if (len > 0) { |
||||
int8_t mode = -1; |
||||
if (os_strcmp(buff, "auto") == 0) mode = LOG_MODE_AUTO; |
||||
if (os_strcmp(buff, "off") == 0) mode = LOG_MODE_OFF; |
||||
if (os_strcmp(buff, "on") == 0) mode = LOG_MODE_ON; |
||||
if (mode >= 0) { |
||||
flashConfig.log_mode = mode; |
||||
if (mode != LOG_MODE_AUTO) log_uart(mode == LOG_MODE_ON); |
||||
status = configSave() ? 200 : 400; |
||||
} |
||||
} else if (connData->requestType == HTTPD_METHOD_GET) { |
||||
status = 200; |
||||
} |
||||
|
||||
jsonHeader(connData, status); |
||||
os_sprintf(buff, "{\"mode\": \"%s\"}", dbg_mode[flashConfig.log_mode]); |
||||
httpdSend(connData, buff, -1); |
||||
return HTTPD_CGI_DONE; |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR logInit() { |
||||
log_no_uart = flashConfig.log_mode == LOG_MODE_OFF; // ON unless set to always-off
|
||||
log_wr = 0; |
||||
log_rd = 0; |
||||
os_install_putc1((void *)log_write_char); |
||||
} |
||||
|
||||
|
@ -1,77 +0,0 @@ |
||||
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||
|
||||
#include <esp8266.h> |
||||
#include "config.h" |
||||
#include "serled.h" |
||||
#include "cgiwifi.h" |
||||
|
||||
static ETSTimer ledTimer; |
||||
|
||||
static void ICACHE_FLASH_ATTR setLed(int on) { |
||||
int8_t pin = flashConfig.conn_led_pin; |
||||
if (pin < 0) return; // disabled
|
||||
// LED is active-low
|
||||
if (on) { |
||||
gpio_output_set(0, (1<<pin), (1<<pin), 0); |
||||
} else { |
||||
gpio_output_set((1<<pin), 0, (1<<pin), 0); |
||||
} |
||||
} |
||||
|
||||
static uint8_t ledState = 0; |
||||
static uint8_t wifiState = 0; |
||||
|
||||
static void ICACHE_FLASH_ATTR ledTimerCb(void *v) { |
||||
int time = 1000; |
||||
|
||||
if (wifiState == wifiGotIP) { |
||||
// connected, all is good, solid light
|
||||
ledState = 1-ledState; |
||||
time = ledState ? 2900 : 100; |
||||
} else if (wifiState == wifiIsConnected) { |
||||
// waiting for DHCP, go on/off every second
|
||||
ledState = 1 - ledState; |
||||
time = 1000; |
||||
} else { |
||||
// idle
|
||||
switch (wifi_get_opmode()) { |
||||
case 1: // STA
|
||||
ledState = 0; |
||||
break; |
||||
case 2: // AP
|
||||
ledState = 1-ledState; |
||||
time = ledState ? 50 : 1950; |
||||
break; |
||||
case 3: // STA+AP
|
||||
ledState = 1-ledState; |
||||
time = ledState ? 50 : 950; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
setLed(ledState); |
||||
os_timer_arm(&ledTimer, time, 0); |
||||
} |
||||
|
||||
// change the wifi state
|
||||
void ICACHE_FLASH_ATTR statusWifiUpdate(uint8_t state) { |
||||
wifiState = state; |
||||
// schedule an update (don't want to run into concurrency issues)
|
||||
os_timer_disarm(&ledTimer); |
||||
os_timer_setfn(&ledTimer, ledTimerCb, NULL); |
||||
os_timer_arm(&ledTimer, 500, 0); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR statusInit(void) { |
||||
if (flashConfig.conn_led_pin >= 0) { |
||||
makeGpio(flashConfig.conn_led_pin); |
||||
setLed(1); |
||||
} |
||||
os_printf("CONN led=%d\n", flashConfig.conn_led_pin); |
||||
|
||||
os_timer_disarm(&ledTimer); |
||||
os_timer_setfn(&ledTimer, ledTimerCb, NULL); |
||||
os_timer_arm(&ledTimer, 2000, 0); |
||||
} |
||||
|
||||
|
@ -1,162 +1,7 @@ |
||||
/*
|
||||
* ---------------------------------------------------------------------------- |
||||
* "THE BEER-WARE LICENSE" (Revision 42): |
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain |
||||
* this notice you can do whatever you want with this stuff. If we meet some day, |
||||
* and you think this stuff is worth it, you can buy me a beer in return. |
||||
* ---------------------------------------------------------------------------- |
||||
* Heavily modified and enhanced by Thorsten von Eicken in 2015 |
||||
* ---------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
|
||||
#include <esp8266.h> |
||||
#include "httpd.h" |
||||
#include "httpdespfs.h" |
||||
#include "cgi.h" |
||||
#include "cgiwifi.h" |
||||
#include "cgipins.h" |
||||
#include "cgiflash.h" |
||||
#include "auth.h" |
||||
#include "espfs.h" |
||||
#include "uart.h" |
||||
#include "serbridge.h" |
||||
#include "status.h" |
||||
#include "serled.h" |
||||
#include "console.h" |
||||
#include "config.h" |
||||
#include "log.h" |
||||
#include <gpio.h> |
||||
|
||||
//#define SHOW_HEAP_USE
|
||||
|
||||
//Function that tells the authentication system what users/passwords live on the system.
|
||||
//This is disabled in the default build; if you want to try it, enable the authBasic line in
|
||||
//the builtInUrls below.
|
||||
int myPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen) { |
||||
if (no==0) { |
||||
os_strcpy(user, "admin"); |
||||
os_strcpy(pass, "s3cr3t"); |
||||
return 1; |
||||
//Add more users this way. Check against incrementing no for each user added.
|
||||
// } else if (no==1) {
|
||||
// os_strcpy(user, "user1");
|
||||
// os_strcpy(pass, "something");
|
||||
// return 1;
|
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
/*
|
||||
This is the main url->function dispatching data struct. |
||||
In short, it's a struct with various URLs plus their handlers. The handlers can |
||||
be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument. |
||||
They can also be auth-functions. An asterisk will match any url starting with |
||||
everything before the asterisks; "*" matches everything. The list will be |
||||
handled top-down, so make sure to put more specific rules above the more |
||||
general ones. Authorization things (like authBasic) act as a 'barrier' and |
||||
should be placed above the URLs they protect. |
||||
*/ |
||||
HttpdBuiltInUrl builtInUrls[]={ |
||||
{"/", cgiRedirect, "/home.html"}, |
||||
{"/menu", cgiMenu, NULL}, |
||||
{"/flash/next", cgiGetFirmwareNext, NULL}, |
||||
{"/flash/upload", cgiUploadFirmware, NULL}, |
||||
{"/flash/reboot", cgiRebootFirmware, NULL}, |
||||
//{"/home.html", cgiEspFsHtml, NULL},
|
||||
//{"/log.html", cgiEspFsHtml, NULL},
|
||||
{"/log/text", ajaxLog, NULL}, |
||||
{"/log/dbg", ajaxLogDbg, NULL}, |
||||
//{"/console.html", cgiEspFsHtml, NULL},
|
||||
{"/console/reset", ajaxConsoleReset, NULL}, |
||||
{"/console/baud", ajaxConsoleBaud, NULL}, |
||||
{"/console/text", ajaxConsole, NULL}, |
||||
|
||||
//Routines to make the /wifi URL and everything beneath it work.
|
||||
|
||||
//Enable the line below to protect the WiFi configuration with an username/password combo.
|
||||
// {"/wifi/*", authBasic, myPassFn},
|
||||
|
||||
{"/wifi", cgiRedirect, "/wifi/wifi.html"}, |
||||
{"/wifi/", cgiRedirect, "/wifi/wifi.html"}, |
||||
//{"/wifi/wifi.html", cgiEspFsHtml, NULL},
|
||||
{"/wifi/info", cgiWifiInfo, NULL}, |
||||
{"/wifi/scan", cgiWiFiScan, NULL}, |
||||
{"/wifi/connect", cgiWiFiConnect, NULL}, |
||||
{"/wifi/connstatus", cgiWiFiConnStatus, NULL}, |
||||
{"/wifi/setmode", cgiWiFiSetMode, NULL}, |
||||
{"/wifi/special", cgiWiFiSpecial, NULL}, |
||||
{"/pins", cgiPins, NULL}, |
||||
|
||||
{"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem
|
||||
{NULL, NULL, NULL} |
||||
}; |
||||
|
||||
|
||||
//#define SHOW_HEAP_USE
|
||||
|
||||
#ifdef SHOW_HEAP_USE |
||||
static ETSTimer prHeapTimer; |
||||
|
||||
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) { |
||||
os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size()); |
||||
} |
||||
#endif |
||||
|
||||
void user_rf_pre_init(void) { |
||||
} |
||||
|
||||
// address of espfs binary blob
|
||||
extern uint32_t _binary_espfs_img_start; |
||||
|
||||
static char *rst_codes[] = { |
||||
"normal", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "???", |
||||
}; |
||||
|
||||
# define VERS_STR_STR(V) #V |
||||
# define VERS_STR(V) VERS_STR_STR(V) |
||||
char *esp_link_version = VERS_STR(VERSION); |
||||
|
||||
//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done.
|
||||
void user_init(void) { |
||||
// get the flash config so we know how to init things
|
||||
//configWipe(); // uncomment to reset the config for testing purposes
|
||||
bool restoreOk = configRestore(); |
||||
// init gpio pin registers
|
||||
gpio_init(); |
||||
// init UART
|
||||
uart_init(flashConfig.baud_rate, 115200); |
||||
logInit(); // must come after init of uart
|
||||
// say hello (leave some time to cause break in TX after boot loader's msg
|
||||
os_delay_us(10000L); |
||||
os_printf("\n\n** %s\n", esp_link_version); |
||||
os_printf("Flash config restore %s\n", restoreOk ? "ok" : "*FAILED*"); |
||||
// Status LEDs
|
||||
statusInit(); |
||||
serledInit(); |
||||
// Wifi
|
||||
wifiInit(); |
||||
// init the flash filesystem with the html stuff
|
||||
EspFsInitResult res = espFsInit(&_binary_espfs_img_start); |
||||
os_printf("espFsInit %s\n", res?"ERR":"ok"); |
||||
// mount the http handlers
|
||||
httpdInit(builtInUrls, 80); |
||||
// init the wifi-serial transparent bridge (port 23)
|
||||
serbridgeInit(23); |
||||
uart_add_recv_cb(&serbridgeUartCb); |
||||
#ifdef SHOW_HEAP_USE |
||||
os_timer_disarm(&prHeapTimer); |
||||
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); |
||||
os_timer_arm(&prHeapTimer, 10000, 1); |
||||
#endif |
||||
#include <config.h> |
||||
|
||||
struct rst_info *rst_info = system_get_rst_info(); |
||||
os_printf("Reset cause: %d=%s\n", rst_info->reason, rst_codes[rst_info->reason]); |
||||
os_printf("exccause=%d epc1=0x%x epc2=0x%x epc3=0x%x excvaddr=0x%x depc=0x%x\n", |
||||
rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3, |
||||
rst_info->excvaddr, rst_info->depc); |
||||
os_printf("Flash map %d, chip %08X\n", system_get_flash_size_map(), spi_flash_get_id()); |
||||
// initialize the custom stuff that goes beyond esp-link
|
||||
void app_init() { |
||||
|
||||
os_printf("** esp-link ready\n"); |
||||
} |
||||
|
Loading…
Reference in new issue