Added files
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "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.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
This is a 'captive portal' DNS server: it basically replies with a fixed IP (in this case:
|
||||
the one of the SoftAP interface of this ESP module) for any and all DNS queries. This can
|
||||
be used to send mobile phones, tablets etc which connect to the ESP in AP mode directly to
|
||||
the internal webserver.
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#ifdef FREERTOS
|
||||
#include "espressif/esp_common.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "queue.h"
|
||||
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/err.h"
|
||||
static int sockFd;
|
||||
#endif
|
||||
|
||||
|
||||
#define DNS_LEN 512
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
uint16_t id;
|
||||
uint8_t flags;
|
||||
uint8_t rcode;
|
||||
uint16_t qdcount;
|
||||
uint16_t ancount;
|
||||
uint16_t nscount;
|
||||
uint16_t arcount;
|
||||
} DnsHeader;
|
||||
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
uint8_t len;
|
||||
uint8_t data;
|
||||
} DnsLabel;
|
||||
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
//before: label
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
} DnsQuestionFooter;
|
||||
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
//before: label
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
uint32_t ttl;
|
||||
uint16_t rdlength;
|
||||
//after: rdata
|
||||
} DnsResourceFooter;
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
uint16_t prio;
|
||||
uint16_t weight;
|
||||
} DnsUriHdr;
|
||||
|
||||
|
||||
#define FLAG_QR (1<<7)
|
||||
#define FLAG_AA (1<<2)
|
||||
#define FLAG_TC (1<<1)
|
||||
#define FLAG_RD (1<<0)
|
||||
|
||||
#define QTYPE_A 1
|
||||
#define QTYPE_NS 2
|
||||
#define QTYPE_CNAME 5
|
||||
#define QTYPE_SOA 6
|
||||
#define QTYPE_WKS 11
|
||||
#define QTYPE_PTR 12
|
||||
#define QTYPE_HINFO 13
|
||||
#define QTYPE_MINFO 14
|
||||
#define QTYPE_MX 15
|
||||
#define QTYPE_TXT 16
|
||||
#define QTYPE_URI 256
|
||||
|
||||
#define QCLASS_IN 1
|
||||
#define QCLASS_ANY 255
|
||||
#define QCLASS_URI 256
|
||||
|
||||
|
||||
//Function to put unaligned 16-bit network values
|
||||
static void ICACHE_FLASH_ATTR setn16(void *pp, int16_t n) {
|
||||
char *p=pp;
|
||||
*p++=(n>>8);
|
||||
*p++=(n&0xff);
|
||||
}
|
||||
|
||||
//Function to put unaligned 32-bit network values
|
||||
static void ICACHE_FLASH_ATTR setn32(void *pp, int32_t n) {
|
||||
char *p=pp;
|
||||
*p++=(n>>24)&0xff;
|
||||
*p++=(n>>16)&0xff;
|
||||
*p++=(n>>8)&0xff;
|
||||
*p++=(n&0xff);
|
||||
}
|
||||
|
||||
static uint16_t ICACHE_FLASH_ATTR my_ntohs(uint16_t *in) {
|
||||
char *p=(char*)in;
|
||||
return ((p[0]<<8)&0xff00)|(p[1]&0xff);
|
||||
}
|
||||
|
||||
|
||||
//Parses a label into a C-string containing a dotted
|
||||
//Returns pointer to start of next fields in packet
|
||||
static char* ICACHE_FLASH_ATTR labelToStr(char *packet, char *labelPtr, int packetSz, char *res, int resMaxLen) {
|
||||
int i, j, k;
|
||||
char *endPtr=NULL;
|
||||
i=0;
|
||||
do {
|
||||
if ((*labelPtr&0xC0)==0) {
|
||||
j=*labelPtr++; //skip past length
|
||||
//Add separator period if there already is data in res
|
||||
if (i<resMaxLen && i!=0) res[i++]='.';
|
||||
//Copy label to res
|
||||
for (k=0; k<j; k++) {
|
||||
if ((labelPtr-packet)>packetSz) return NULL;
|
||||
if (i<resMaxLen) res[i++]=*labelPtr++;
|
||||
}
|
||||
} else if ((*labelPtr&0xC0)==0xC0) {
|
||||
//Compressed label pointer
|
||||
endPtr=labelPtr+2;
|
||||
int offset=my_ntohs(((uint16_t *)labelPtr))&0x3FFF;
|
||||
//Check if offset points to somewhere outside of the packet
|
||||
if (offset>packetSz) return NULL;
|
||||
labelPtr=&packet[offset];
|
||||
}
|
||||
//check for out-of-bound-ness
|
||||
if ((labelPtr-packet)>packetSz) return NULL;
|
||||
} while (*labelPtr!=0);
|
||||
res[i]=0; //zero-terminate
|
||||
if (endPtr==NULL) endPtr=labelPtr+1;
|
||||
return endPtr;
|
||||
}
|
||||
|
||||
|
||||
//Converts a dotted hostname to the weird label form dns uses.
|
||||
static char ICACHE_FLASH_ATTR *strToLabel(char *str, char *label, int maxLen) {
|
||||
char *len=label; //ptr to len byte
|
||||
char *p=label+1; //ptr to next label byte to be written
|
||||
while (1) {
|
||||
if (*str=='.' || *str==0) {
|
||||
*len=((p-len)-1); //write len of label bit
|
||||
len=p; //pos of len for next part
|
||||
p++; //data ptr is one past len
|
||||
if (*str==0) break; //done
|
||||
str++;
|
||||
} else {
|
||||
*p++=*str++; //copy byte
|
||||
// if ((p-label)>maxLen) return NULL; //check out of bounds
|
||||
}
|
||||
}
|
||||
*len=0;
|
||||
return p; //ptr to first free byte in resp
|
||||
}
|
||||
|
||||
|
||||
//Receive a DNS packet and maybe send a response back
|
||||
#ifndef FREERTOS
|
||||
static void ICACHE_FLASH_ATTR captdnsRecv(void* arg, char *pusrdata, unsigned short length) {
|
||||
struct espconn *conn=(struct espconn *)arg;
|
||||
#else
|
||||
static void ICACHE_FLASH_ATTR captdnsRecv(struct sockaddr_in *premote_addr, char *pusrdata, unsigned short length) {
|
||||
#endif
|
||||
char buff[DNS_LEN];
|
||||
char reply[DNS_LEN];
|
||||
int i;
|
||||
char *rend=&reply[length];
|
||||
char *p=pusrdata;
|
||||
DnsHeader *hdr=(DnsHeader*)p;
|
||||
DnsHeader *rhdr=(DnsHeader*)&reply[0];
|
||||
p+=sizeof(DnsHeader);
|
||||
// httpd_printf("DNS packet: id 0x%X flags 0x%X rcode 0x%X qcnt %d ancnt %d nscount %d arcount %d len %d\n",
|
||||
// my_ntohs(&hdr->id), hdr->flags, hdr->rcode, my_ntohs(&hdr->qdcount), my_ntohs(&hdr->ancount), my_ntohs(&hdr->nscount), my_ntohs(&hdr->arcount), length);
|
||||
//Some sanity checks:
|
||||
if (length>DNS_LEN) return; //Packet is longer than DNS implementation allows
|
||||
if (length<sizeof(DnsHeader)) return; //Packet is too short
|
||||
if (hdr->ancount || hdr->nscount || hdr->arcount) return; //this is a reply, don't know what to do with it
|
||||
if (hdr->flags&FLAG_TC) return; //truncated, can't use this
|
||||
//Reply is basically the request plus the needed data
|
||||
memcpy(reply, pusrdata, length);
|
||||
rhdr->flags|=FLAG_QR;
|
||||
for (i=0; i<my_ntohs(&hdr->qdcount); i++) {
|
||||
//Grab the labels in the q string
|
||||
p=labelToStr(pusrdata, p, length, buff, sizeof(buff));
|
||||
if (p==NULL) return;
|
||||
DnsQuestionFooter *qf=(DnsQuestionFooter*)p;
|
||||
p+=sizeof(DnsQuestionFooter);
|
||||
httpd_printf("DNS: Q (type 0x%X class 0x%X) for %s\n", my_ntohs(&qf->type), my_ntohs(&qf->class), buff);
|
||||
if (my_ntohs(&qf->type)==QTYPE_A) {
|
||||
//They want to know the IPv4 address of something.
|
||||
//Build the response.
|
||||
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
|
||||
if (rend==NULL) return;
|
||||
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
|
||||
rend+=sizeof(DnsResourceFooter);
|
||||
setn16(&rf->type, QTYPE_A);
|
||||
setn16(&rf->class, QCLASS_IN);
|
||||
setn32(&rf->ttl, 0);
|
||||
setn16(&rf->rdlength, 4); //IPv4 addr is 4 bytes;
|
||||
//Grab the current IP of the softap interface
|
||||
struct ip_info info;
|
||||
wifi_get_ip_info(SOFTAP_IF, &info);
|
||||
*rend++=ip4_addr1(&info.ip);
|
||||
*rend++=ip4_addr2(&info.ip);
|
||||
*rend++=ip4_addr3(&info.ip);
|
||||
*rend++=ip4_addr4(&info.ip);
|
||||
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
|
||||
// httpd_printf("Added A rec to resp. Resp len is %d\n", (rend-reply));
|
||||
} else if (my_ntohs(&qf->type)==QTYPE_NS) {
|
||||
//Give ns server. Basically can be whatever we want because it'll get resolved to our IP later anyway.
|
||||
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
|
||||
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
|
||||
rend+=sizeof(DnsResourceFooter);
|
||||
setn16(&rf->type, QTYPE_NS);
|
||||
setn16(&rf->class, QCLASS_IN);
|
||||
setn16(&rf->ttl, 0);
|
||||
setn16(&rf->rdlength, 4);
|
||||
*rend++=2;
|
||||
*rend++='n';
|
||||
*rend++='s';
|
||||
*rend++=0;
|
||||
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
|
||||
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
|
||||
} else if (my_ntohs(&qf->type)==QTYPE_URI) {
|
||||
//Give uri to us
|
||||
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
|
||||
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
|
||||
rend+=sizeof(DnsResourceFooter);
|
||||
DnsUriHdr *uh=(DnsUriHdr *)rend;
|
||||
rend+=sizeof(DnsUriHdr);
|
||||
setn16(&rf->type, QTYPE_URI);
|
||||
setn16(&rf->class, QCLASS_URI);
|
||||
setn16(&rf->ttl, 0);
|
||||
setn16(&rf->rdlength, 4+16);
|
||||
setn16(&uh->prio, 10);
|
||||
setn16(&uh->weight, 1);
|
||||
memcpy(rend, "http://esp.nonet", 16);
|
||||
rend+=16;
|
||||
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
|
||||
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
|
||||
}
|
||||
}
|
||||
//Send the response
|
||||
#ifndef FREERTOS
|
||||
remot_info *remInfo=NULL;
|
||||
//Send data to port/ip it came from, not to the ip/port we listen on.
|
||||
if (espconn_get_connection_info(conn, &remInfo, 0)==ESPCONN_OK) {
|
||||
conn->proto.udp->remote_port=remInfo->remote_port;
|
||||
memcpy(conn->proto.udp->remote_ip, remInfo->remote_ip, sizeof(remInfo->remote_ip));
|
||||
}
|
||||
espconn_sendto(conn, (uint8*)reply, rend-reply);
|
||||
#else
|
||||
sendto(sockFd,(uint8*)reply, rend-reply, 0, (struct sockaddr *)premote_addr, sizeof(struct sockaddr_in));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef FREERTOS
|
||||
static void captdnsTask(void *pvParameters) {
|
||||
struct sockaddr_in server_addr;
|
||||
int32 ret;
|
||||
struct sockaddr_in from;
|
||||
socklen_t fromlen;
|
||||
struct ip_info ipconfig;
|
||||
char udp_msg[DNS_LEN];
|
||||
|
||||
memset(&ipconfig, 0, sizeof(ipconfig));
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(53);
|
||||
server_addr.sin_len = sizeof(server_addr);
|
||||
|
||||
do {
|
||||
sockFd=socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sockFd==-1) {
|
||||
httpd_printf("captdns_task failed to create sock!\n");
|
||||
vTaskDelay(1000/portTICK_RATE_MS);
|
||||
}
|
||||
} while (sockFd==-1);
|
||||
|
||||
do {
|
||||
ret=bind(sockFd, (struct sockaddr *)&server_addr, sizeof(server_addr));
|
||||
if (ret!=0) {
|
||||
httpd_printf("captdns_task failed to bind sock!\n");
|
||||
vTaskDelay(1000/portTICK_RATE_MS);
|
||||
}
|
||||
} while (ret!=0);
|
||||
|
||||
httpd_printf("CaptDNS inited.\n");
|
||||
while(1) {
|
||||
memset(&from, 0, sizeof(from));
|
||||
fromlen=sizeof(struct sockaddr_in);
|
||||
ret=recvfrom(sockFd, (u8 *)udp_msg, DNS_LEN, 0,(struct sockaddr *)&from,(socklen_t *)&fromlen);
|
||||
if (ret>0) captdnsRecv(&from,udp_msg,ret);
|
||||
}
|
||||
|
||||
close(sockFd);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void captdnsInit(void) {
|
||||
xTaskCreate(captdnsTask, (const signed char *)"captdns_task", 1200, NULL, 3, NULL);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ICACHE_FLASH_ATTR captdnsInit(void) {
|
||||
static struct espconn conn;
|
||||
static esp_udp udpconn;
|
||||
conn.type=ESPCONN_UDP;
|
||||
conn.proto.udp=&udpconn;
|
||||
conn.proto.udp->local_port = 53;
|
||||
espconn_regist_recvcb(&conn, captdnsRecv);
|
||||
espconn_create(&conn);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
Some flash handling cgi routines. Used for reading the existing flash and updating the ESPFS image.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "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.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgiflash.h"
|
||||
#include "espfs.h"
|
||||
#include "cgiflash.h"
|
||||
#include "espfs.h"
|
||||
|
||||
//#include <osapi.h>
|
||||
#include "cgiflash.h"
|
||||
#include "espfs.h"
|
||||
|
||||
#ifndef UPGRADE_FLAG_FINISH
|
||||
#define UPGRADE_FLAG_FINISH 0x02
|
||||
#endif
|
||||
|
||||
// Check that the header of the firmware blob looks like actual firmware...
|
||||
static int ICACHE_FLASH_ATTR checkBinHeader(void *buf) {
|
||||
uint8_t *cd = (uint8_t *)buf;
|
||||
if (cd[0] != 0xEA) return 0;
|
||||
if (cd[1] != 4 || cd[2] > 3 || cd[3] > 0x40) return 0;
|
||||
if (((uint16_t *)buf)[3] != 0x4010) return 0;
|
||||
if (((uint32_t *)buf)[2] != 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ICACHE_FLASH_ATTR checkEspfsHeader(void *buf) {
|
||||
if (memcmp(buf, "ESfs", 4)!=0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// Cgi to query which firmware needs to be uploaded next
|
||||
int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) {
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
uint8 id = system_upgrade_userbin_check();
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "text/plain");
|
||||
httpdHeader(connData, "Content-Length", "9");
|
||||
httpdEndHeaders(connData);
|
||||
char *next = id == 1 ? "user1.bin" : "user2.bin";
|
||||
httpdSend(connData, next, -1);
|
||||
httpd_printf("Next firmware: %s (got %d)\n", next, id);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
|
||||
//Cgi that reads the SPI flash. Assumes 512KByte flash.
|
||||
//ToDo: Figure out real flash size somehow?
|
||||
int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) {
|
||||
int *pos=(int *)&connData->cgiData;
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
if (*pos==0) {
|
||||
httpd_printf("Start flash download.\n");
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "application/bin");
|
||||
httpdEndHeaders(connData);
|
||||
*pos=0x40200000;
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
//Send 1K of flash per call. We will get called again if we haven't sent 512K yet.
|
||||
httpdSend(connData, (char*)(*pos), 1024);
|
||||
*pos+=1024;
|
||||
if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE;
|
||||
}
|
||||
|
||||
|
||||
//Cgi that allows the firmware to be replaced via http POST This takes
|
||||
//a direct POST from e.g. Curl or a Javascript AJAX call with either the
|
||||
//firmware given by cgiGetFirmwareNext or an OTA upgrade image.
|
||||
|
||||
//Because we don't have the buffer to allocate an entire sector but will
|
||||
//have to buffer some data because the post buffer may be misaligned, we
|
||||
//write SPI data in pages. The page size is a software thing, not
|
||||
//a hardware one.
|
||||
#define PAGELEN 64
|
||||
|
||||
#define FLST_START 0
|
||||
#define FLST_WRITE 1
|
||||
#define FLST_SKIP 2
|
||||
#define FLST_DONE 3
|
||||
#define FLST_ERROR 4
|
||||
|
||||
#define FILETYPE_ESPFS 0
|
||||
#define FILETYPE_FLASH 1
|
||||
#define FILETYPE_OTA 2
|
||||
typedef struct {
|
||||
int state;
|
||||
int filetype;
|
||||
int flashPos;
|
||||
char pageData[PAGELEN];
|
||||
int pagePos;
|
||||
int address;
|
||||
int len;
|
||||
int skip;
|
||||
char *err;
|
||||
} UploadState;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char magic[4];
|
||||
char tag[28];
|
||||
int32_t len1;
|
||||
int32_t len2;
|
||||
} OtaHeader;
|
||||
|
||||
|
||||
int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) {
|
||||
CgiUploadFlashDef *def=(CgiUploadFlashDef*)connData->cgiArg;
|
||||
UploadState *state=(UploadState *)connData->cgiData;
|
||||
int len;
|
||||
char buff[128];
|
||||
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
if (state!=NULL) free(state);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
if (state==NULL) {
|
||||
//First call. Allocate and initialize state variable.
|
||||
httpd_printf("Firmware upload cgi start.\n");
|
||||
state=malloc(sizeof(UploadState));
|
||||
if (state==NULL) {
|
||||
httpd_printf("Can't allocate firmware upload struct!\n");
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
memset(state, 0, sizeof(UploadState));
|
||||
state->state=FLST_START;
|
||||
connData->cgiData=state;
|
||||
state->err="Premature end";
|
||||
}
|
||||
|
||||
char *data=connData->post->buff;
|
||||
int dataLen=connData->post->buffLen;
|
||||
|
||||
while (dataLen!=0) {
|
||||
if (state->state==FLST_START) {
|
||||
//First call. Assume the header of whatever we're uploading already is in the POST buffer.
|
||||
if (def->type==CGIFLASH_TYPE_FW && memcmp(data, "EHUG", 4)==0) {
|
||||
//Type is combined flash1/flash2 file
|
||||
OtaHeader *h=(OtaHeader*)data;
|
||||
strncpy(buff, h->tag, 27);
|
||||
buff[27]=0;
|
||||
if (strcmp(buff, def->tagName)!=0) {
|
||||
httpd_printf("OTA tag mismatch! Current=`%s` uploaded=`%s`.\n",
|
||||
def->tagName, buff);
|
||||
len=httpdFindArg(connData->getArgs, "force", buff, sizeof(buff));
|
||||
if (len!=-1 && atoi(buff)) {
|
||||
httpd_printf("Forcing firmware flash.\n");
|
||||
} else {
|
||||
state->err="Firmware not intended for this device!\n";
|
||||
state->state=FLST_ERROR;
|
||||
}
|
||||
}
|
||||
if (state->state!=FLST_ERROR && connData->post->len > def->fwSize*2+sizeof(OtaHeader)) {
|
||||
state->err="Firmware image too large";
|
||||
state->state=FLST_ERROR;
|
||||
}
|
||||
if (state->state!=FLST_ERROR) {
|
||||
//Flash header seems okay.
|
||||
dataLen-=sizeof(OtaHeader); //skip header when parsing data
|
||||
data+=sizeof(OtaHeader);
|
||||
if (system_upgrade_userbin_check()==1) {
|
||||
httpd_printf("Flashing user1.bin from ota image\n");
|
||||
state->len=h->len1;
|
||||
state->skip=h->len2;
|
||||
state->state=FLST_WRITE;
|
||||
state->address=def->fw1Pos;
|
||||
} else {
|
||||
httpd_printf("Flashing user2.bin from ota image\n");
|
||||
state->len=h->len2;
|
||||
state->skip=h->len1;
|
||||
state->state=FLST_SKIP;
|
||||
state->address=def->fw2Pos;
|
||||
}
|
||||
}
|
||||
} else if (def->type==CGIFLASH_TYPE_FW && checkBinHeader(connData->post->buff)) {
|
||||
if (connData->post->len > def->fwSize) {
|
||||
state->err="Firmware image too large";
|
||||
state->state=FLST_ERROR;
|
||||
} else {
|
||||
state->len=connData->post->len;
|
||||
state->address=def->fw1Pos;
|
||||
state->state=FLST_WRITE;
|
||||
}
|
||||
} else if (def->type==CGIFLASH_TYPE_ESPFS && checkEspfsHeader(connData->post->buff)) {
|
||||
if (connData->post->len > def->fwSize) {
|
||||
state->err="Firmware image too large";
|
||||
state->state=FLST_ERROR;
|
||||
} else {
|
||||
state->len=connData->post->len;
|
||||
state->address=def->fw1Pos;
|
||||
state->state=FLST_WRITE;
|
||||
}
|
||||
} else {
|
||||
state->err="Invalid flash image type!";
|
||||
state->state=FLST_ERROR;
|
||||
httpd_printf("Did not recognize flash image type!\n");
|
||||
}
|
||||
} else if (state->state==FLST_SKIP) {
|
||||
//Skip bytes without doing anything with them
|
||||
if (state->skip>dataLen) {
|
||||
//Skip entire buffer
|
||||
state->skip-=dataLen;
|
||||
dataLen=0;
|
||||
} else {
|
||||
//Only skip part of buffer
|
||||
dataLen-=state->skip;
|
||||
data+=state->skip;
|
||||
state->skip=0;
|
||||
if (state->len) state->state=FLST_WRITE; else state->state=FLST_DONE;
|
||||
}
|
||||
} else if (state->state==FLST_WRITE) {
|
||||
//Copy bytes to page buffer, and if page buffer is full, flash the data.
|
||||
//First, calculate the amount of bytes we need to finish the page buffer.
|
||||
int lenLeft=PAGELEN-state->pagePos;
|
||||
if (state->len<lenLeft) lenLeft=state->len; //last buffer can be a cut-off one
|
||||
//See if we need to write the page.
|
||||
if (dataLen<lenLeft) {
|
||||
//Page isn't done yet. Copy data to buffer and exit.
|
||||
memcpy(&state->pageData[state->pagePos], data, dataLen);
|
||||
state->pagePos+=dataLen;
|
||||
state->len-=dataLen;
|
||||
dataLen=0;
|
||||
} else {
|
||||
//Finish page; take data we need from post buffer
|
||||
memcpy(&state->pageData[state->pagePos], data, lenLeft);
|
||||
data+=lenLeft;
|
||||
dataLen-=lenLeft;
|
||||
state->pagePos+=lenLeft;
|
||||
state->len-=lenLeft;
|
||||
//Erase sector, if needed
|
||||
if ((state->address&(SPI_FLASH_SEC_SIZE-1))==0) {
|
||||
spi_flash_erase_sector(state->address/SPI_FLASH_SEC_SIZE);
|
||||
}
|
||||
//Write page
|
||||
//httpd_printf("Writing %d bytes of data to SPI pos 0x%x...\n", state->pagePos, state->address);
|
||||
spi_flash_write(state->address, (uint32 *)state->pageData, state->pagePos);
|
||||
state->address+=PAGELEN;
|
||||
state->pagePos=0;
|
||||
if (state->len==0) {
|
||||
//Done.
|
||||
if (state->skip) state->state=FLST_SKIP; else state->state=FLST_DONE;
|
||||
}
|
||||
}
|
||||
} else if (state->state==FLST_DONE) {
|
||||
httpd_printf("Huh? %d bogus bytes received after data received.\n", dataLen);
|
||||
//Ignore those bytes.
|
||||
dataLen=0;
|
||||
} else if (state->state==FLST_ERROR) {
|
||||
//Just eat up any bytes we receive.
|
||||
dataLen=0;
|
||||
}
|
||||
}
|
||||
|
||||
if (connData->post->len==connData->post->received) {
|
||||
//We're done! Format a response.
|
||||
httpd_printf("Upload done. Sending response.\n");
|
||||
httpdStartResponse(connData, state->state==FLST_ERROR?400:200);
|
||||
httpdHeader(connData, "Content-Type", "text/plain");
|
||||
httpdEndHeaders(connData);
|
||||
if (state->state!=FLST_DONE) {
|
||||
httpdSend(connData, "Firmware image error:", -1);
|
||||
httpdSend(connData, state->err, -1);
|
||||
httpdSend(connData, "\n", -1);
|
||||
}
|
||||
free(state);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static os_timer_t resetTimer;
|
||||
|
||||
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
|
||||
system_upgrade_flag_set(UPGRADE_FLAG_FINISH);
|
||||
system_upgrade_reboot();
|
||||
}
|
||||
|
||||
// Handle request to reboot into the new firmware
|
||||
int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) {
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
// TODO: sanity-check that the 'next' partition actually contains something that looks like
|
||||
// valid firmware
|
||||
|
||||
//Do reboot in a timer callback so we still have time to send the response.
|
||||
os_timer_disarm(&resetTimer);
|
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
|
||||
os_timer_arm(&resetTimer, 200, 0);
|
||||
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "text/plain");
|
||||
httpdEndHeaders(connData);
|
||||
httpdSend(connData, "Rebooting...", -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-8266-WebSocket
|
||||
*/
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "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.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "httpd.h"
|
||||
#include "sha1.h"
|
||||
#include "base64.h"
|
||||
#include "cgiwebsocket.h"
|
||||
|
||||
#define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: "
|
||||
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
/* from IEEE RFC6455 sec 5.2
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
||||
| |1|2|3| |K| | |
|
||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
| Extended payload length continued, if payload len == 127 |
|
||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
||||
| |Masking-key, if MASK set to 1 |
|
||||
+-------------------------------+-------------------------------+
|
||||
| Masking-key (continued) | Payload Data |
|
||||
+-------------------------------- - - - - - - - - - - - - - - - +
|
||||
: Payload Data continued ... :
|
||||
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
| Payload Data continued ... |
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#define FLAG_FIN (1 << 7)
|
||||
|
||||
#define OPCODE_CONTINUE 0x0
|
||||
#define OPCODE_TEXT 0x1
|
||||
#define OPCODE_BINARY 0x2
|
||||
#define OPCODE_CLOSE 0x8
|
||||
#define OPCODE_PING 0x9
|
||||
#define OPCODE_PONG 0xA
|
||||
|
||||
#define FLAGS_MASK ((uint8_t)0xF0)
|
||||
#define OPCODE_MASK ((uint8_t)0x0F)
|
||||
#define IS_MASKED ((uint8_t)(1<<7))
|
||||
#define PAYLOAD_MASK ((uint8_t)0x7F)
|
||||
|
||||
typedef struct WebsockFrame WebsockFrame;
|
||||
|
||||
#define ST_FLAGS 0
|
||||
#define ST_LEN0 1
|
||||
#define ST_LEN1 2
|
||||
#define ST_LEN2 3
|
||||
//...
|
||||
#define ST_LEN8 9
|
||||
#define ST_MASK1 10
|
||||
#define ST_MASK4 13
|
||||
#define ST_PAYLOAD 14
|
||||
|
||||
struct WebsockFrame {
|
||||
uint8_t flags;
|
||||
uint8_t len8;
|
||||
uint64_t len;
|
||||
uint8_t mask[4];
|
||||
};
|
||||
|
||||
struct WebsockPriv {
|
||||
struct WebsockFrame fr;
|
||||
uint8_t maskCtr;
|
||||
uint8 frameCont;
|
||||
uint8 closedHere;
|
||||
int wsStatus;
|
||||
Websock *next; //in linked list
|
||||
};
|
||||
|
||||
static Websock *llStart=NULL;
|
||||
|
||||
static int ICACHE_FLASH_ATTR sendFrameHead(Websock *ws, int opcode, int len) {
|
||||
char buf[14];
|
||||
int i=0;
|
||||
buf[i++]=opcode;
|
||||
if (len>65535) {
|
||||
buf[i++]=127;
|
||||
buf[i++]=0; buf[i++]=0; buf[i++]=0; buf[i++]=0;
|
||||
buf[i++]=len>>24;
|
||||
buf[i++]=len>>16;
|
||||
buf[i++]=len>>8;
|
||||
buf[i++]=len;
|
||||
} else if (len>125) {
|
||||
buf[i++]=126;
|
||||
buf[i++]=len>>8;
|
||||
buf[i++]=len;
|
||||
} else {
|
||||
buf[i++]=len;
|
||||
}
|
||||
httpd_printf("WS: Sent frame head for payload of %d bytes.\n", len);
|
||||
return httpdSend(ws->conn, buf, i);
|
||||
}
|
||||
|
||||
int ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags) {
|
||||
int r=0;
|
||||
int fl=0;
|
||||
if (flags&WEBSOCK_FLAG_BIN) fl=OPCODE_BINARY; else fl=OPCODE_TEXT;
|
||||
if (!(flags&WEBSOCK_FLAG_CONT)) fl|=FLAG_FIN;
|
||||
sendFrameHead(ws, fl, len);
|
||||
if (len!=0) r=httpdSend(ws->conn, data, len);
|
||||
httpdFlushSendBuffer(ws->conn);
|
||||
return r;
|
||||
}
|
||||
|
||||
//Broadcast data to all websockets at a specific url. Returns the amount of connections sent to.
|
||||
int ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags) {
|
||||
//This is majorly broken (and actually, always, it just tended to work because the circumstances
|
||||
//were juuuust right). Because the socket is used outside of the httpd send/receive context, it
|
||||
//will not have an associated send buffer. This means httpdSend will write to a dangling pointer!
|
||||
//Disabled for now. If you really need this, open an issue on github or otherwise poke me and I'll
|
||||
//see what I can do.
|
||||
/*
|
||||
Websock *lw=llStart;
|
||||
int ret=0;
|
||||
while (lw!=NULL) {
|
||||
if (strcmp(lw->conn->url, resource)==0) {
|
||||
cgiWebsocketSend(lw, data, len, flags);
|
||||
ret++;
|
||||
}
|
||||
lw=lw->priv->next;
|
||||
}
|
||||
return ret;
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR cgiWebsocketClose(Websock *ws, int reason) {
|
||||
char rs[2]={reason>>8, reason&0xff};
|
||||
sendFrameHead(ws, FLAG_FIN|OPCODE_CLOSE, 2);
|
||||
httpdSend(ws->conn, rs, 2);
|
||||
ws->priv->closedHere=1;
|
||||
httpdFlushSendBuffer(ws->conn);
|
||||
}
|
||||
|
||||
|
||||
static void ICACHE_FLASH_ATTR websockFree(Websock *ws) {
|
||||
httpd_printf("Ws: Free\n");
|
||||
if (ws->closeCb) ws->closeCb(ws);
|
||||
//Clean up linked list
|
||||
if (llStart==ws) {
|
||||
llStart=ws->priv->next;
|
||||
} else if (llStart) {
|
||||
Websock *lws=llStart;
|
||||
//Find ws that links to this one.
|
||||
while (lws!=NULL && lws->priv->next!=ws) lws=lws->priv->next;
|
||||
if (lws!=NULL) lws->priv->next=ws->priv->next;
|
||||
}
|
||||
if (ws->priv) free(ws->priv);
|
||||
}
|
||||
|
||||
int ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdConnData *connData, char *data, int len) {
|
||||
int i, j, sl;
|
||||
int r=HTTPD_CGI_MORE;
|
||||
int wasHeaderByte;
|
||||
Websock *ws=(Websock*)connData->cgiData;
|
||||
for (i=0; i<len; i++) {
|
||||
// httpd_printf("Ws: State %d byte 0x%02X\n", ws->priv->wsStatus, data[i]);
|
||||
wasHeaderByte=1;
|
||||
if (ws->priv->wsStatus==ST_FLAGS) {
|
||||
ws->priv->maskCtr=0;
|
||||
ws->priv->frameCont=0;
|
||||
ws->priv->fr.flags=(uint8_t)data[i];
|
||||
ws->priv->wsStatus=ST_LEN0;
|
||||
} else if (ws->priv->wsStatus==ST_LEN0) {
|
||||
ws->priv->fr.len8=(uint8_t)data[i];
|
||||
if ((ws->priv->fr.len8&127)>=126) {
|
||||
ws->priv->fr.len=0;
|
||||
ws->priv->wsStatus=ST_LEN1;
|
||||
} else {
|
||||
ws->priv->fr.len=ws->priv->fr.len8&127;
|
||||
ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD;
|
||||
}
|
||||
} else if (ws->priv->wsStatus<=ST_LEN8) {
|
||||
ws->priv->fr.len=(ws->priv->fr.len<<8)|data[i];
|
||||
if (((ws->priv->fr.len8&127)==126 && ws->priv->wsStatus==ST_LEN2) || ws->priv->wsStatus==ST_LEN8) {
|
||||
ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD;
|
||||
} else {
|
||||
ws->priv->wsStatus++;
|
||||
}
|
||||
} else if (ws->priv->wsStatus<=ST_MASK4) {
|
||||
ws->priv->fr.mask[ws->priv->wsStatus-ST_MASK1]=data[i];
|
||||
ws->priv->wsStatus++;
|
||||
} else {
|
||||
//Was a payload byte.
|
||||
wasHeaderByte=0;
|
||||
}
|
||||
|
||||
if (ws->priv->wsStatus==ST_PAYLOAD && wasHeaderByte) {
|
||||
//We finished parsing the header, but i still is on the last header byte. Move one forward so
|
||||
//the payload code works as usual.
|
||||
i++;
|
||||
}
|
||||
//Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame
|
||||
//is zero.
|
||||
if (ws->priv->wsStatus==ST_PAYLOAD) {
|
||||
//Okay, header is in; this is a data byte. We're going to process all the data bytes we have
|
||||
//received here at the same time; no more byte iterations till the end of this frame.
|
||||
//First, unmask the data
|
||||
sl=len-i;
|
||||
httpd_printf("Ws: Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x\n", wasHeaderByte, (int)ws->priv->fr.len, (int)sl, ws->priv->fr.flags);
|
||||
if (sl > ws->priv->fr.len) sl=ws->priv->fr.len;
|
||||
for (j=0; j<sl; j++) data[i+j]^=(ws->priv->fr.mask[(ws->priv->maskCtr++)&3]);
|
||||
|
||||
// httpd_printf("Unmasked: ");
|
||||
// for (j=0; j<sl; j++) httpd_printf("%02X ", data[i+j]&0xff);
|
||||
// httpd_printf("\n");
|
||||
|
||||
//Inspect the header to see what we need to do.
|
||||
if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_PING) {
|
||||
if (ws->priv->fr.len>125) {
|
||||
if (!ws->priv->frameCont) cgiWebsocketClose(ws, 1002);
|
||||
r=HTTPD_CGI_DONE;
|
||||
break;
|
||||
} else {
|
||||
if (!ws->priv->frameCont) sendFrameHead(ws, OPCODE_PONG|FLAG_FIN, ws->priv->fr.len);
|
||||
if (sl>0) httpdSend(ws->conn, data+i, sl);
|
||||
}
|
||||
} else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_TEXT ||
|
||||
(ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY ||
|
||||
(ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CONTINUE) {
|
||||
if (sl>ws->priv->fr.len) sl=ws->priv->fr.len;
|
||||
if (!(ws->priv->fr.len8&IS_MASKED)) {
|
||||
//We're a server; client should send us masked packets.
|
||||
cgiWebsocketClose(ws, 1002);
|
||||
r=HTTPD_CGI_DONE;
|
||||
break;
|
||||
} else {
|
||||
int flags=0;
|
||||
if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY) flags|=WEBSOCK_FLAG_BIN;
|
||||
if ((ws->priv->fr.flags&FLAG_FIN)==0) flags|=WEBSOCK_FLAG_CONT;
|
||||
if (ws->recvCb) ws->recvCb(ws, data+i, sl, flags);
|
||||
}
|
||||
} else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CLOSE) {
|
||||
httpd_printf("WS: Got close frame\n");
|
||||
if (!ws->priv->closedHere) {
|
||||
httpd_printf("WS: Sending response close frame\n");
|
||||
cgiWebsocketClose(ws, ((data[i]<<8)&0xff00)+(data[i+1]&0xff));
|
||||
}
|
||||
r=HTTPD_CGI_DONE;
|
||||
break;
|
||||
} else {
|
||||
if (!ws->priv->frameCont) httpd_printf("WS: Unknown opcode 0x%X\n", ws->priv->fr.flags&OPCODE_MASK);
|
||||
}
|
||||
i+=sl-1;
|
||||
ws->priv->fr.len-=sl;
|
||||
if (ws->priv->fr.len==0) {
|
||||
ws->priv->wsStatus=ST_FLAGS; //go receive next frame
|
||||
} else {
|
||||
ws->priv->frameCont=1; //next payload is continuation of this frame.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (r==HTTPD_CGI_DONE) {
|
||||
//We're going to tell the main webserver we're done. The webserver expects us to clean up by ourselves
|
||||
//we're chosing to be done. Do so.
|
||||
websockFree(ws);
|
||||
free(connData->cgiData);
|
||||
connData->cgiData=NULL;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
//Websocket 'cgi' implementation
|
||||
int ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData) {
|
||||
char buff[256];
|
||||
int i;
|
||||
sha1nfo s;
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
httpd_printf("WS: Cleanup\n");
|
||||
if (connData->cgiData) {
|
||||
Websock *ws=(Websock*)connData->cgiData;
|
||||
websockFree(ws);
|
||||
free(connData->cgiData);
|
||||
connData->cgiData=NULL;
|
||||
}
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
if (connData->cgiData==NULL) {
|
||||
// httpd_printf("WS: First call\n");
|
||||
//First call here. Check if client headers are OK, send server header.
|
||||
i=httpdGetHeader(connData, "Upgrade", buff, sizeof(buff)-1);
|
||||
httpd_printf("WS: Upgrade: %s\n", buff);
|
||||
if (i && strcasecmp(buff, "websocket")==0) {
|
||||
i=httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff)-1);
|
||||
if (i) {
|
||||
// httpd_printf("WS: Key: %s\n", buff);
|
||||
//Seems like a WebSocket connection.
|
||||
// Alloc structs
|
||||
connData->cgiData=malloc(sizeof(Websock));
|
||||
memset(connData->cgiData, 0, sizeof(Websock));
|
||||
Websock *ws=(Websock*)connData->cgiData;
|
||||
ws->priv=malloc(sizeof(WebsockPriv));
|
||||
memset(ws->priv, 0, sizeof(WebsockPriv));
|
||||
ws->conn=connData;
|
||||
//Reply with the right headers.
|
||||
strcat(buff, WS_GUID);
|
||||
sha1_init(&s);
|
||||
sha1_write(&s, buff, strlen(buff));
|
||||
httpdDisableTransferEncoding(connData);
|
||||
httpdStartResponse(connData, 101);
|
||||
httpdHeader(connData, "Upgrade", "websocket");
|
||||
httpdHeader(connData, "Connection", "upgrade");
|
||||
base64_encode(20, sha1_result(&s), sizeof(buff), buff);
|
||||
httpdHeader(connData, "Sec-WebSocket-Accept", buff);
|
||||
httpdEndHeaders(connData);
|
||||
//Set data receive handler
|
||||
connData->recvHdl=cgiWebSocketRecv;
|
||||
//Inform CGI function we have a connection
|
||||
WsConnectedCb connCb=connData->cgiArg;
|
||||
connCb(ws);
|
||||
//Insert ws into linked list
|
||||
if (llStart==NULL) {
|
||||
llStart=ws;
|
||||
} else {
|
||||
Websock *lw=llStart;
|
||||
while (lw->priv->next) lw=lw->priv->next;
|
||||
lw->priv->next=ws;
|
||||
}
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
}
|
||||
//No valid websocket connection
|
||||
httpdStartResponse(connData, 500);
|
||||
httpdEndHeaders(connData);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
//Sending is done. Call the sent callback if we have one.
|
||||
Websock *ws=(Websock*)connData->cgiData;
|
||||
if (ws && ws->sentCb) ws->sentCb(ws);
|
||||
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
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.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgiwifi.h"
|
||||
|
||||
//Enable this to disallow any changes in AP settings
|
||||
//#define DEMO_MODE
|
||||
|
||||
//WiFi access point data
|
||||
typedef struct {
|
||||
char ssid[32];
|
||||
char bssid[8];
|
||||
int channel;
|
||||
char 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;
|
||||
|
||||
#define CONNTRY_IDLE 0
|
||||
#define CONNTRY_WORKING 1
|
||||
#define CONNTRY_SUCCESS 2
|
||||
#define CONNTRY_FAIL 3
|
||||
//Connection result var
|
||||
static int connTryStatus=CONNTRY_IDLE;
|
||||
static os_timer_t resetTimer;
|
||||
|
||||
//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;
|
||||
httpd_printf("wifiScanDoneCb %d\n", status);
|
||||
if (status!=OK) {
|
||||
cgiWifiAps.scanInProgress=0;
|
||||
return;
|
||||
}
|
||||
|
||||
//Clear prev ap data if needed.
|
||||
if (cgiWifiAps.apData!=NULL) {
|
||||
for (n=0; n<cgiWifiAps.noAps; n++) free(cgiWifiAps.apData[n]);
|
||||
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 **)malloc(sizeof(ApData *)*n);
|
||||
cgiWifiAps.noAps=n;
|
||||
httpd_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.
|
||||
httpd_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps);
|
||||
break;
|
||||
}
|
||||
//Save the ap data.
|
||||
cgiWifiAps.apData[n]=(ApData *)malloc(sizeof(ApData));
|
||||
cgiWifiAps.apData[n]->rssi=bss_link->rssi;
|
||||
cgiWifiAps.apData[n]->channel=bss_link->channel;
|
||||
cgiWifiAps.apData[n]->enc=bss_link->authmode;
|
||||
strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32);
|
||||
strncpy(cgiWifiAps.apData[n]->bssid, (char*)bss_link->bssid, 6);
|
||||
|
||||
bss_link = bss_link->next.stqe_next;
|
||||
n++;
|
||||
}
|
||||
//We're done.
|
||||
cgiWifiAps.scanInProgress=0;
|
||||
}
|
||||
|
||||
|
||||
//Routine to start a WiFi access point scan.
|
||||
static void ICACHE_FLASH_ATTR wifiStartScan() {
|
||||
// int x;
|
||||
if (cgiWifiAps.scanInProgress) return;
|
||||
cgiWifiAps.scanInProgress=1;
|
||||
wifi_station_scan(NULL, wifiScanDoneCb);
|
||||
}
|
||||
|
||||
//This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a
|
||||
//scan for access points and if available will return the result of an earlier scan.
|
||||
//The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl.
|
||||
int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) {
|
||||
int pos=(int)connData->cgiData;
|
||||
int len;
|
||||
char buff[1024];
|
||||
|
||||
if (!cgiWifiAps.scanInProgress && pos!=0) {
|
||||
//Fill in json code for an access point
|
||||
if (pos-1<cgiWifiAps.noAps) {
|
||||
len=sprintf(buff, "{\"essid\": \"%s\", \"bssid\": \"" MACSTR "\", \"rssi\": \"%d\", \"enc\": \"%d\", \"channel\": \"%d\"}%s\n",
|
||||
cgiWifiAps.apData[pos-1]->ssid, MAC2STR(cgiWifiAps.apData[pos-1]->bssid), cgiWifiAps.apData[pos-1]->rssi,
|
||||
cgiWifiAps.apData[pos-1]->enc, cgiWifiAps.apData[pos-1]->channel, (pos-1==cgiWifiAps.noAps-1)?"":",");
|
||||
httpdSend(connData, buff, len);
|
||||
}
|
||||
pos++;
|
||||
if ((pos-1)>=cgiWifiAps.noAps) {
|
||||
len=sprintf(buff, "]\n}\n}\n");
|
||||
httpdSend(connData, buff, len);
|
||||
//Also start a new scan.
|
||||
wifiStartScan();
|
||||
return HTTPD_CGI_DONE;
|
||||
} else {
|
||||
connData->cgiData=(void*)pos;
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
}
|
||||
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "text/json");
|
||||
httpdEndHeaders(connData);
|
||||
|
||||
if (cgiWifiAps.scanInProgress==1) {
|
||||
//We're still scanning. Tell Javascript code that.
|
||||
len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n");
|
||||
httpdSend(connData, buff, len);
|
||||
return HTTPD_CGI_DONE;
|
||||
} else {
|
||||
//We have a scan result. Pass it on.
|
||||
len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"0\", \n\"APs\": [\n");
|
||||
httpdSend(connData, buff, len);
|
||||
if (cgiWifiAps.apData==NULL) cgiWifiAps.noAps=0;
|
||||
connData->cgiData=(void *)1;
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
}
|
||||
|
||||
//Temp store for new ap info.
|
||||
static struct station_config stconf;
|
||||
|
||||
//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.
|
||||
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) {
|
||||
int x=wifi_station_get_connect_status();
|
||||
if (x==STATION_GOT_IP) {
|
||||
//Go to STA mode. This needs a reset, so do that.
|
||||
httpd_printf("Got IP. Going into STA mode..\n");
|
||||
wifi_set_opmode(1);
|
||||
system_restart();
|
||||
} else {
|
||||
connTryStatus=CONNTRY_FAIL;
|
||||
httpd_printf("Connect fail. Not going into STA-only mode.\n");
|
||||
//Maybe also pass this through on the webpage?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Actually connect to a station. This routine is timed because I had problems
|
||||
//with immediate connections earlier. It probably was something else that caused it,
|
||||
//but I can't be arsed to put the code back :P
|
||||
static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) {
|
||||
int x;
|
||||
httpd_printf("Try to connect to AP....\n");
|
||||
wifi_station_disconnect();
|
||||
wifi_station_set_config(&stconf);
|
||||
wifi_station_connect();
|
||||
x=wifi_get_opmode();
|
||||
connTryStatus=CONNTRY_WORKING;
|
||||
if (x!=1) {
|
||||
//Schedule disconnect/connect
|
||||
os_timer_disarm(&resetTimer);
|
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
|
||||
os_timer_arm(&resetTimer, 15000, 0); //time out after 15 secs of trying to connect
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//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];
|
||||
static os_timer_t reassTimer;
|
||||
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid));
|
||||
httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd));
|
||||
|
||||
strncpy((char*)stconf.ssid, essid, 32);
|
||||
strncpy((char*)stconf.password, passwd, 64);
|
||||
httpd_printf("Try to connect to AP %s pw %s\n", essid, passwd);
|
||||
|
||||
//Schedule disconnect/connect
|
||||
os_timer_disarm(&reassTimer);
|
||||
os_timer_setfn(&reassTimer, reassTimerCb, NULL);
|
||||
//Set to 0 if you want to disable the actual reconnecting bit
|
||||
#ifdef DEMO_MODE
|
||||
httpdRedirect(connData, "/wifi");
|
||||
#else
|
||||
os_timer_arm(&reassTimer, 500, 0);
|
||||
httpdRedirect(connData, "connecting.html");
|
||||
#endif
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
//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 cgiWiFiSetMode(HttpdConnData *connData) {
|
||||
int len;
|
||||
char buff[1024];
|
||||
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff));
|
||||
if (len!=0) {
|
||||
httpd_printf("cgiWifiSetMode: %s\n", buff);
|
||||
#ifndef DEMO_MODE
|
||||
wifi_set_opmode(atoi(buff));
|
||||
system_restart();
|
||||
#endif
|
||||
}
|
||||
httpdRedirect(connData, "/wifi");
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) {
|
||||
char buff[1024];
|
||||
int len;
|
||||
struct ip_info info;
|
||||
int st=wifi_station_get_connect_status();
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "text/json");
|
||||
httpdEndHeaders(connData);
|
||||
if (connTryStatus==CONNTRY_IDLE) {
|
||||
len=sprintf(buff, "{\n \"status\": \"idle\"\n }\n");
|
||||
} else if (connTryStatus==CONNTRY_WORKING || connTryStatus==CONNTRY_SUCCESS) {
|
||||
if (st==STATION_GOT_IP) {
|
||||
wifi_get_ip_info(0, &info);
|
||||
len=sprintf(buff, "{\n \"status\": \"success\",\n \"ip\": \"%d.%d.%d.%d\" }\n",
|
||||
(info.ip.addr>>0)&0xff, (info.ip.addr>>8)&0xff,
|
||||
(info.ip.addr>>16)&0xff, (info.ip.addr>>24)&0xff);
|
||||
//Reset into AP-only mode sooner.
|
||||
os_timer_disarm(&resetTimer);
|
||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL);
|
||||
os_timer_arm(&resetTimer, 1000, 0);
|
||||
} else {
|
||||
len=sprintf(buff, "{\n \"status\": \"working\"\n }\n");
|
||||
}
|
||||
} else {
|
||||
len=sprintf(buff, "{\n \"status\": \"fail\"\n }\n");
|
||||
}
|
||||
|
||||
httpdSend(connData, buff, len);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
//Template code for the WLAN page.
|
||||
int ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) {
|
||||
char buff[1024];
|
||||
int x;
|
||||
static struct station_config stconf;
|
||||
if (token==NULL) return HTTPD_CGI_DONE;
|
||||
wifi_station_get_config(&stconf);
|
||||
|
||||
strcpy(buff, "Unknown");
|
||||
if (strcmp(token, "WiFiMode")==0) {
|
||||
x=wifi_get_opmode();
|
||||
if (x==1) strcpy(buff, "Client");
|
||||
if (x==2) strcpy(buff, "SoftAP");
|
||||
if (x==3) strcpy(buff, "STA+AP");
|
||||
} else if (strcmp(token, "currSsid")==0) {
|
||||
strcpy(buff, (char*)stconf.ssid);
|
||||
} else if (strcmp(token, "WiFiPasswd")==0) {
|
||||
strcpy(buff, (char*)stconf.password);
|
||||
} else if (strcmp(token, "WiFiapwarn")==0) {
|
||||
x=wifi_get_opmode();
|
||||
if (x==2) {
|
||||
strcpy(buff, "<b>Can't scan in this mode.</b> Click <a href=\"setmode.cgi?mode=3\">here</a> to go to STA+AP mode.");
|
||||
} else {
|
||||
strcpy(buff, "Click <a href=\"setmode.cgi?mode=2\">here</a> to go to standalone AP mode.");
|
||||
}
|
||||
}
|
||||
httpdSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user