From 02b31b1818f4c9b784a3594e00bf2b32aeca8bf8 Mon Sep 17 00:00:00 2001 From: Thorsten von Eicken Date: Sun, 18 Jun 2017 21:13:23 -0700 Subject: [PATCH] merge megaflash and split-up-recv back in --- .travis.yml | 7 +- Makefile | 26 +- cmd/cmd.h | 10 + cmd/handlers.c | 210 +++++++- esp-link/cgimega.c | 1167 ++++++++++++++++++++++++++++++++++++++++ esp-link/cgimega.h | 14 + esp-link/cgioptiboot.c | 148 +---- esp-link/cgiwifi.c | 116 ++-- esp-link/cgiwifi.h | 6 + esp-link/main.c | 16 + esp-link/pgmshared.c | 149 +++++ esp-link/pgmshared.h | 54 ++ esp-link/stk500v2.h | 114 ++++ include/esp8266.h | 2 + megaflash | 114 ++++ rest/rest.c | 4 +- serial/serbridge.c | 1 - socket/socket.c | 26 +- 18 files changed, 1998 insertions(+), 186 deletions(-) create mode 100644 esp-link/cgimega.c create mode 100644 esp-link/cgimega.h create mode 100644 esp-link/pgmshared.c create mode 100644 esp-link/pgmshared.h create mode 100644 esp-link/stk500v2.h create mode 100755 megaflash diff --git a/.travis.yml b/.travis.yml index 008251c..4ccff52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,12 @@ language: c +git: + depth: 1000 + before_install: - curl -Ls http://s3.voneicken.com/xtensa-lx106-elf-20160330.tgx | tar Jxf - - - curl -Ls http://s3.voneicken.com/esp_iot_sdk_v2.0.0.p1.tgx | tar -C .. -Jxf - + - curl -Ls http://s3.voneicken.com/esp_iot_sdk_v2.1.0.tgx | tar -C .. -Jxf - after_script: # upload to an S3 bucket, requires S3_BUCKET, AWS_ACCESS_KEY_ID and AWS_SECRET_KEY to be set @@ -25,6 +28,8 @@ script: - export XTENSA_TOOLS_ROOT=$PWD/xtensa-lx106-elf/bin/ - export BRANCH=$TRAVIS_BRANCH #- export SDK_BASE=$PWD/esp_iot_sdk_v2.0.0.p1 + - git tag -n1 + - git describe --tags --match 'v*.0' --long --debug - make release notifications: diff --git a/Makefile b/Makefile index 57d80f6..9e01a31 100644 --- a/Makefile +++ b/Makefile @@ -189,7 +189,8 @@ endif TRAVIS_BRANCH?=$(shell git symbolic-ref --short HEAD --quiet) # Use git describe to get the latest version tag, commits since then, sha and dirty flag, this # results is something like "v1.2.0-13-ab6cedf-dirty" -VERSION := $(shell (git describe --tags --match 'v*' --long --dirty || echo "no-tag") | sed -re 's/(\.0)?-/./') +NO_TAG ?= "no-tag" +VERSION := $(shell (git describe --tags --match 'v*.0' --long --dirty || echo $(NO_TAG)) | sed -re 's/(\.0)?-/./') # If not on master then insert the branch name ifneq ($(TRAVIS_BRANCH),master) ifneq ($(findstring V%,$(TRAVIS_BRANCH)),) @@ -199,19 +200,6 @@ endif VERSION :=$(VERSION) $(info VERSION is $(VERSION)) -# OLD - DEPRECATED -# This queries git to produce a version string like "esp-link v0.9.0 2015-06-01 34bc76" -# If you don't have a proper git checkout or are on windows, then simply swap for the constant -# Steps to release: create release on github, git pull, git describe --tags to verify you're -# on the release tag, make release, upload esp-link.tgz into the release files -#VERSION ?= "esp-link custom version" -#DATE := $(shell date '+%F %T') -#BRANCH ?= $(shell if git diff --quiet HEAD; then git describe --tags; \ -# else git symbolic-ref --short HEAD; fi) -#SHA := $(shell if git diff --quiet HEAD; then git rev-parse --short HEAD | cut -d"/" -f 3; \ -# else echo "development"; fi) -#VERSION ?=esp-link $(BRANCH) - $(DATE) - $(SHA) - # Output directors to store intermediate compiled files # relative to the project directory BUILD_BASE = build @@ -519,3 +507,13 @@ ifeq ("$(COMPRESS_W_HTMLCOMPRESSOR)","yes") endif $(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir)))) + +depend: + makedepend -p${BUILD_BASE}/ -Y -- $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) -I${XTENSA_TOOLS_ROOT}../xtensa-lx106-elf/include -I${XTENSA_TOOLS_ROOT}../lib/gcc/xtensa-lx106-elf/4.8.2/include -- */*.c + +# Rebuild version at least at every Makefile change + +${BUILD_BASE}/esp-link/main.o: Makefile + +# DO NOT DELETE + diff --git a/cmd/cmd.h b/cmd/cmd.h index 3b4e8ab..31803b8 100644 --- a/cmd/cmd.h +++ b/cmd/cmd.h @@ -42,11 +42,14 @@ typedef enum { CMD_CB_ADD, CMD_CB_EVENTS, CMD_GET_TIME, // get current time in seconds since the unix epoch + CMD_GET_WIFI_INFO, // query ip address info + CMD_SET_WIFI_INFO, // set ip address info CMD_MQTT_SETUP = 10, // set-up callbacks CMD_MQTT_PUBLISH, // publish a message CMD_MQTT_SUBSCRIBE, // subscribe to a topic CMD_MQTT_LWT, // set the last-will-topic and messge + CMD_MQTT_GET_CLIENTID, CMD_REST_SETUP = 20, // set-up callbacks CMD_REST_REQUEST, // do REST request @@ -58,6 +61,13 @@ typedef enum { CMD_SOCKET_SETUP = 40, // set-up callbacks CMD_SOCKET_SEND, // send data over UDP socket + CMD_WIFI_GET_APCOUNT = 50, // Query the number of networks / Access Points known + CMD_WIFI_GET_APNAME, // Query the name (SSID) of an Access Point (AP) + CMD_WIFI_SELECT_SSID, // Connect to a specific network + CMD_WIFI_SIGNAL_STRENGTH, // Query RSSI + CMD_WIFI_GET_SSID, // Query SSID currently connected to + CMD_WIFI_START_SCAN, // Trigger a scan (takes a long time) + } CmdName; typedef void (*cmdfunc_t)(CmdPacket *cmd); diff --git a/cmd/handlers.c b/cmd/handlers.c index c191091..8605f68 100644 --- a/cmd/handlers.c +++ b/cmd/handlers.c @@ -17,6 +17,10 @@ #ifdef SOCKET #include #endif +#include +#include "esp-link/cgi.h" + +#include "config.h" #ifdef CMD_DBG #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) @@ -28,8 +32,19 @@ static void cmdNull(CmdPacket *cmd); static void cmdSync(CmdPacket *cmd); static void cmdWifiStatus(CmdPacket *cmd); static void cmdGetTime(CmdPacket *cmd); +static void cmdGetWifiInfo(CmdPacket *cmd); +// static void cmdSetWifiInfo(CmdPacket *cmd); static void cmdAddCallback(CmdPacket *cmd); +static void cmdWifiGetApCount(CmdPacket *cmd); +static void cmdWifiGetApName(CmdPacket *cmd); +static void cmdWifiSelectSSID(CmdPacket *cmd); +static void cmdWifiSignalStrength(CmdPacket *cmd); +static void cmdWifiQuerySSID(CmdPacket *cmd); +static void cmdWifiStartScan(CmdPacket *cmd); + +void cmdMqttGetClientId(CmdPacket *cmd); + // keep track of last status sent to uC so we can notify it when it changes static uint8_t lastWifiStatus = wifiIsDisconnected; // keep track of whether we have registered our cb handler with the wifi subsystem @@ -44,11 +59,22 @@ const CmdList commands[] = { {CMD_WIFI_STATUS, "WIFI_STATUS", cmdWifiStatus}, {CMD_CB_ADD, "ADD_CB", cmdAddCallback}, {CMD_GET_TIME, "GET_TIME", cmdGetTime}, + {CMD_GET_WIFI_INFO, "GET_WIFI_INFO", cmdGetWifiInfo}, + // {CMD_SET_WIFI_INFO, "SET_WIFI_INFO", cmdSetWifiInfo}, + + {CMD_WIFI_GET_APCOUNT, "WIFI_GET_APCOUNT", cmdWifiGetApCount}, + {CMD_WIFI_GET_APNAME, "WIFI_GET_APNAME", cmdWifiGetApName}, + {CMD_WIFI_SELECT_SSID, "WIFI_SELECT_SSID", cmdWifiSelectSSID}, + {CMD_WIFI_SIGNAL_STRENGTH, "WIFI_SIGNAL_STRENGTH", cmdWifiSignalStrength}, + {CMD_WIFI_GET_SSID, "WIFI_GET_SSID", cmdWifiQuerySSID}, + {CMD_WIFI_START_SCAN, "WIFI_START_SCAN", cmdWifiStartScan}, + #ifdef MQTT {CMD_MQTT_SETUP, "MQTT_SETUP", MQTTCMD_Setup}, {CMD_MQTT_PUBLISH, "MQTT_PUB", MQTTCMD_Publish}, {CMD_MQTT_SUBSCRIBE , "MQTT_SUB", MQTTCMD_Subscribe}, {CMD_MQTT_LWT, "MQTT_LWT", MQTTCMD_Lwt}, + {CMD_MQTT_GET_CLIENTID,"MQTT_CLIENTID", cmdMqttGetClientId}, #endif #ifdef REST {CMD_REST_SETUP, "REST_SETUP", REST_Setup}, @@ -72,7 +98,7 @@ CmdCallback callbacks[MAX_CALLBACKS]; // cleared in cmdSync uint32_t ICACHE_FLASH_ATTR cmdAddCb(char* name, uint32_t cb) { for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { - //os_printf("cmdAddCb: index %d name=%s cb=%p\n", i, callbacks[i].name, + //DBG("cmdAddCb: 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') { @@ -89,7 +115,7 @@ cmdAddCb(char* name, uint32_t cb) { CmdCallback* ICACHE_FLASH_ATTR cmdGetCbByName(char* name) { for (uint8_t i = 0; i < MAX_CALLBACKS; i++) { - //os_printf("cmdGetCbByName: index %d name=%s cb=%p\n", i, callbacks[i].name, + //DBG("cmdGetCbByName: 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) { @@ -97,7 +123,7 @@ cmdGetCbByName(char* name) { return &callbacks[i]; } } - os_printf("cmdGetCbByName: cb %s not found\n", name); + DBG("cmdGetCbByName: cb %s not found\n", name); return 0; } @@ -178,6 +204,33 @@ cmdGetTime(CmdPacket *cmd) { return; } +// Command handler for IP information +static void ICACHE_FLASH_ATTR +cmdGetWifiInfo(CmdPacket *cmd) { + CmdRequest req; + + cmdRequest(&req, cmd); + if(cmd->argc != 0 || cmd->value == 0) { + cmdResponseStart(CMD_RESP_V, 0, 0); + cmdResponseEnd(); + return; + } + + uint32_t callback = req.cmd->value; + + struct ip_info info; + wifi_get_ip_info(0, &info); + uint8_t mac[6]; + wifi_get_macaddr(0, mac); + + cmdResponseStart(CMD_RESP_CB, callback, 4); + cmdResponseBody(&info.ip.addr, sizeof(info.ip.addr)); + cmdResponseBody(&info.netmask.addr, sizeof(info.netmask.addr)); + cmdResponseBody(&info.gw.addr, sizeof(info.gw.addr)); + cmdResponseBody(mac, sizeof(mac)); + cmdResponseEnd(); +} + // Command handler to add a callback to the named-callbacks list, this is for a callback to the uC static void ICACHE_FLASH_ATTR cmdAddCallback(CmdPacket *cmd) { @@ -197,3 +250,154 @@ cmdAddCallback(CmdPacket *cmd) { cmdAddCb(name, cmd->value); // save the sensor callback } + +// Query the number of wifi access points +static void ICACHE_FLASH_ATTR cmdWifiGetApCount(CmdPacket *cmd) { + int n = wifiGetApCount(); + DBG("WifiGetApCount : %d\n", n); + cmdResponseStart(CMD_RESP_V, n, 0); + cmdResponseEnd(); +} + +// Query the name of a wifi access point +static void ICACHE_FLASH_ATTR cmdWifiGetApName(CmdPacket *cmd) { + CmdRequest req; + + cmdRequest(&req, cmd); + + int argc = cmdGetArgc(&req); + DBG("cmdWifiGetApName: argc %d\n", argc); + if (argc != 1) + return; + + uint16_t i; + cmdPopArg(&req, (uint8_t*)&i, 2); + + uint32_t callback = req.cmd->value; + + char myssid[33]; + wifiGetApName(i, myssid); + myssid[32] = '\0'; + DBG("wifiGetApName(%d) -> {%s}\n", i, myssid); + + cmdResponseStart(CMD_RESP_CB, callback, 1); + cmdResponseBody(myssid, strlen(myssid)+1); + cmdResponseEnd(); +} + +/* + * Select a wireless network. + * This can be called in two ways : + * - with a pair of strings (SSID, password) + * - with a number and a string (index into network array, password) + */ +static void ICACHE_FLASH_ATTR cmdWifiSelectSSID(CmdPacket *cmd) { + CmdRequest req; + cmdRequest(&req, cmd); + int argc = cmdGetArgc(&req); + char ssid[33], pass[65]; + + if (argc != 2) return; + + int len = cmdArgLen(&req); + if (len == 1) { + // Assume this is the index + uint8_t ix; + cmdPopArg(&req, &ix, 1); + wifiGetApName(ix, ssid); + ssid[32] = '\0'; + } else { + // Longer than 1 byte: must be SSID + if (len > 32) return; + cmdPopArg(&req, ssid, len); + ssid[len] = 0; + } + + // Extract password from message + len = cmdArgLen(&req); + if (len > 64) return; + cmdPopArg(&req, pass, len); + pass[len] = 0; + + DBG("SelectSSID(%s,%s)", ssid, pass); + connectToNetwork(ssid, pass); +} + +#if 0 +/* + * Once we're attached to some wireless network, choose not to pick up address from + * DHCP or so but set our own. + */ +static void ICACHE_FLASH_ATTR cmdSetWifiInfo(CmdPacket *cmd) { + DBG("SetWifiInfo()\n"); +} +#endif + +static void ICACHE_FLASH_ATTR cmdWifiSignalStrength(CmdPacket *cmd) { + CmdRequest req; + + cmdRequest(&req, cmd); + + int argc = cmdGetArgc(&req); + if (argc != 1) { + DBG("cmdWifiSignalStrength: argc %d\n", argc); + return; + } + + char x; + cmdPopArg(&req, (uint8_t*)&x, 1); + int i = x; + DBG("cmdWifiSignalStrength: argc %d, ", argc); + DBG("i %d\n", i); + + int rssi = wifiSignalStrength(i); + + cmdResponseStart(CMD_RESP_V, rssi, 0); + cmdResponseEnd(); +} + +// +static void ICACHE_FLASH_ATTR cmdWifiQuerySSID(CmdPacket *cmd) { + CmdRequest req; + cmdRequest(&req, cmd); + uint32_t callback = req.cmd->value; + + struct station_config conf; + bool res = wifi_station_get_config(&conf); + if (res) { + // #warning handle me + } else { + } + + DBG("QuerySSID : %s\n", conf.ssid); + + cmdResponseStart(CMD_RESP_CB, callback, 1); + cmdResponseBody(conf.ssid, strlen((char *)conf.ssid)+1); + cmdResponseEnd(); +} + +// Start scanning, API interface +static void ICACHE_FLASH_ATTR cmdWifiStartScan(CmdPacket *cmd) { + // call a function that belongs in esp-link/cgiwifi.c due to variable access + wifiStartScan(); +} + +// Command handler for MQTT information +void ICACHE_FLASH_ATTR cmdMqttGetClientId(CmdPacket *cmd) { + CmdRequest req; + + cmdRequest(&req, cmd); + if(cmd->argc != 0 || cmd->value == 0) { + cmdResponseStart(CMD_RESP_V, 0, 0); + cmdResponseEnd(); + return; + } + + uint32_t callback = req.cmd->value; + + cmdResponseStart(CMD_RESP_CB, callback, 1); + cmdResponseBody(flashConfig.mqtt_clientid, strlen(flashConfig.mqtt_clientid)+1); + cmdResponseEnd(); + + os_printf("MqttGetClientId : %s\n", flashConfig.mqtt_clientid); +} diff --git a/esp-link/cgimega.c b/esp-link/cgimega.c new file mode 100644 index 0000000..d5acec0 --- /dev/null +++ b/esp-link/cgimega.c @@ -0,0 +1,1167 @@ +/* + * STK500v2 implementation for esp-link + * Copyright (c) 2016-2017 by Danny Backx + * + * based on the framework for "optiboot" (STK500 v1 protocol) in esp-link + * which is copyright (c) 2015 by Thorsten von Eicken + * + * see LICENSE.txt in the esp-link repo + * + * Documentation about the protocol used : see http://www.atmel.com/Images/doc2591.pdf + * + * Note the Intel HEX format is read by this code. + * Format description is e.g. in http://www.keil.com/support/docs/1584/ + * Summary : each line (HEX file record) is formatted as ":llaaaatt[dd...]cc" + * : is the colon that starts every Intel HEX record. + * ll is the record-length field that represents the number of data bytes (dd) in the record. + * aaaa is the address field that represents the starting address for subsequent data. + * tt is the field that represents the HEX record type, which may be one of the following: + * 00 - data record + * 01 - end-of-file record + * 02 - extended segment address record + * 04 - extended linear address record + * 05 - start linear address record (MDK-ARM only) + * dd is a data field that represents one byte of data. A record may have multiple data bytes. + * The number of data bytes in the record must match the number specified by the ll field. + * cc is the checksum field that represents the checksum of the record. The checksum is + * calculated by summing the values of all hexadecimal digit pairs in the record modulo + * 256 and taking the two's complement. + * + */ + +#include +#include +#include "cgi.h" +#include "config.h" +#include "uart.h" +#include "stk500v2.h" +#include "serbridge.h" +#include "serled.h" +#include "pgmshared.h" + +#define INIT_DELAY 150 // wait this many millisecs before sending anything +#define BAUD_INTERVAL 600 // interval after which we change baud rate +#define PGM_TIMEOUT 20000 // timeout after sync is achieved, in milliseconds +#define PGM_INTERVAL 200 // send sync at this interval in ms when in programming mode +#define ATTEMPTS 8 // number of attempts total to make + +#define DBG_GPIO5 1 // define to 1 to use GPIO5 to trigger scope + +//===== global state + +static ETSTimer optibootTimer; + +static enum { // overall programming states + stateInit = 0, // initial delay + stateSync, // waiting to hear back + stateVar1, // Steps in reading subsequent parameters + stateVar2, + stateVar3, + stateGetSig1, // Reading device signature + stateGetSig2, + stateGetSig3, + stateGetFuse1, + stateGetFuse2, + stateGetFuse3, + stateProg, // programming... +} progState; +static char* progStates[] = { "init", "sync", "var1", "var2", "var3", + "sig1", "sig2", "sig3", + "fuse1", "fuse2", "fuse3", + "prog" }; +static short baudCnt; // counter for sync attempts at different baud rates +static short ackWait; // counter of expected ACKs +static uint32_t baudRate; // baud rate at which we're programming + +#define MAX_PAGE_SZ 512 // max flash page size supported +#define MAX_SAVED 512 // max chars in saved buffer + +// Sequence number of current command/answer. Increment before sending packet. +static int cur_seqno; + +// Structure of a STK500v2 message +struct packet { + uint8_t start; // 0x1B + uint8_t seqno; + uint8_t ms1, ms2; + uint8_t token; // 0x0E + uint8_t *body; // documented max length is 275 + uint8_t cksum; // xor of all bytes including start and body +} pbuf; + +// forward function references +static void megaTimerCB(void *); +static void megaUartRecv(char *buffer, short length); +static void armTimer(uint32_t ms); +static void initBaud(void); +static void initPacket(); +static void allocateOptibootData(); +#if 0 +static void cleanupPacket(); +#endif +static int readSyncPacket(); +static void sendSyncPacket(); +static void sendQueryPacket(int param); +static void sendQuerySignaturePacket(int param); +static void sendRebootMCUQuery(); +static void readRebootMCUReply(); +static void sendLoadAddressQuery(uint32_t); +static void sendProgramPageQuery(char *, int); +static void readProgramPageReply(); +static bool reply_ok = false; +static int readReadFuseReply(); +static int getFuseReply(char *, int); +static void sendReadFuseQuery(char fuse); + +static void debug_reset(); +static bool debug(); +static int debug_cnt = 0; +static void debugOn(bool on); + +static void ICACHE_FLASH_ATTR optibootInit() { + debug_reset(); + + progState = stateInit; + baudCnt = 0; + uart0_baud(flashConfig.baud_rate); + ackWait = 0; + errMessage[0] = 0; + responseLen = 0; + programmingCB = NULL; + if (optibootData != NULL) { + if (optibootData->conn != NULL) + optibootData->conn->cgiPrivData = (void *)-1; // signal that request has been aborted + if (optibootData->pageBuf) os_free(optibootData->pageBuf); + if (optibootData->saved) os_free(optibootData->saved); + os_free(optibootData); + optibootData = NULL; + } + os_timer_disarm(&optibootTimer); + DBG("OB init\n"); + + reply_ok = false; +} + +// append one string to another but visually escape non-printing characters in the second +// string using \x00 hex notation, max is the max chars in the concatenated string. +static void ICACHE_FLASH_ATTR appendPretty(char *buf, int max, char *raw, int rawLen) { + int off = strlen(buf); + max -= off + 1; // for null termination + for (int i=0; i= ' ' && c <= '~') { + buf[off++] = c; + } else if (c == '\n') { + buf[off++] = '\\'; + buf[off++] = 'n'; + } else if (c == '\r') { + buf[off++] = '\\'; + buf[off++] = 'r'; + } else { + buf[off++] = '\\'; + buf[off++] = 'x'; + buf[off++] = '0'+(unsigned char)((c>>4)+((c>>4)>9?7:0)); + buf[off++] = '0'+(unsigned char)((c&0xf)+((c&0xf)>9?7:0)); + } + } + buf[off] = 0; +} + +//===== Cgi to reset AVR and get Optiboot into sync +int ICACHE_FLASH_ATTR cgiMegaSync(HttpdConnData *connData) { + if (connData->conn==NULL) + return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + // check that we know the reset pin, else error out with that + if (flashConfig.reset_pin < 0) { + errorResponse(connData, 400, "No reset pin defined"); + + } else if (connData->requestType == HTTPD_METHOD_POST) { + if (debug()) DBG("cgiMegaSync POST\n"); + + // issue reset + optibootInit(); + baudRate = flashConfig.baud_rate; + programmingCB = megaUartRecv; + initBaud(); + serbridgeReset(); +#if DBG_GPIO5 + makeGpio(5); + gpio_output_set(0, (1<<5), (1<<5), 0); // output 0 +#endif + + // start sync timer + os_timer_disarm(&optibootTimer); + os_timer_setfn(&optibootTimer, megaTimerCB, NULL); + os_timer_arm(&optibootTimer, INIT_DELAY, 0); + + // respond with optimistic OK + noCacheHeaders(connData, 204); + httpdEndHeaders(connData); + httpdSend(connData, "", 0); + + } else if (connData->requestType == HTTPD_METHOD_GET) { + if (debug()) DBG("cgiMegaSync GET\n"); + + noCacheHeaders(connData, 200); + httpdEndHeaders(connData); + if (!errMessage[0] && progState >= stateProg) { + char buf[64]; + if (optibootData == NULL) + allocateOptibootData(); + DBG("OB got sync\n"); + os_sprintf(buf, "SYNC at %d baud, board %02x.%02x.%02x, hardware v%d, firmware %d.%d", + baudRate, optibootData->signature[0], optibootData->signature[1], optibootData->signature[2], + optibootData->hardwareVersion, optibootData->firmwareVersionMajor, optibootData->firmwareVersionMinor); + httpdSend(connData, buf, -1); + } else if (errMessage[0] && progState == stateSync) { + DBG("OB cannot sync\n"); + char buf[512]; + os_sprintf(buf, "FAILED to SYNC: %s, got: %d chars\r\n", errMessage, responseLen); + appendPretty(buf, 512, responseBuf, responseLen); + httpdSend(connData, buf, -1); + + // All done, cleanup + // cleanupPacket(); + } else { + httpdSend(connData, errMessage[0] ? errMessage : "NOT READY", -1); + } + + } else { + errorResponse(connData, 404, "Only GET and POST supported"); + } + + return HTTPD_CGI_DONE; +} + +// Allocate and initialize, to be called upon first CGI query +static void ICACHE_FLASH_ATTR initPacket() { + cur_seqno = 0; + + pbuf.body = os_malloc(275); + pbuf.start = MESSAGE_START; // 0x1B + pbuf.token = TOKEN; // 0x0E +} + +static void ICACHE_FLASH_ATTR writePacket() { + int i, len; + len = (pbuf.ms1 << 8) + pbuf.ms2; + + // Copy sequence number after incrementing it. + pbuf.seqno = ++cur_seqno; + + // Compute cksum + pbuf.cksum = 0; + pbuf.cksum ^= pbuf.start; + pbuf.cksum ^= pbuf.seqno; + pbuf.cksum ^= pbuf.ms1; + pbuf.cksum ^= pbuf.ms2; + pbuf.cksum ^= pbuf.token; + for (i=0; i> 1; + + if (pbuf.body == 0) + return; + pbuf.body[0] = CMD_LOAD_ADDRESS; + pbuf.body[1] = 0xFF & (a2 >> 24); + pbuf.body[2] = 0xFF & (a2 >> 16); + pbuf.body[3] = 0xFF & (a2 >> 8); + pbuf.body[4] = 0xFF & a2; + + writePacket(); +} + +static void ICACHE_FLASH_ATTR readLoadAddressReply() { + reply_ok = false; + + int len = readPacket(); + if (len == 2) { + if (pbuf.body[0] == CMD_LOAD_ADDRESS && pbuf.body[1] == STATUS_CMD_OK) { + reply_ok = true; + // DBG("LoadAddress Reply : ok\n"); + return; + } + } + + DBG("LoadAddress : %02x %02x (expected %02x %02x), len %d exp 2\n", + pbuf.body[0], pbuf.body[1], CMD_LOAD_ADDRESS, STATUS_CMD_OK, len); +} + +static void ICACHE_FLASH_ATTR sendProgramPageQuery(char *data, int pgmLen) { + int totalLen = pgmLen + 10; + int mode = 0xC1; // already includes the 0x80 "write page" bit + + pbuf.ms1 = totalLen >> 8; + pbuf.ms2 = totalLen & 0xFF; + + pbuf.body[0] = CMD_PROGRAM_FLASH_ISP; + pbuf.body[1] = pgmLen >> 8; + pbuf.body[2] = pgmLen & 0xFF; + pbuf.body[3] = mode; // mode byte + pbuf.body[4] = 0x0A; // delay + pbuf.body[5] = 0x40; // cmd1 + pbuf.body[6] = 0x4C; // cmd2 + pbuf.body[7] = 0x20; // cmd3 + pbuf.body[8] = 0x00; // poll1 + pbuf.body[9] = 0x00; // poll2 + for (int i=0, j=10; ipageBuf = pageBuf; + optibootData->saved = saved; + optibootData->startTime = system_get_time(); + optibootData->mega = true; + optibootData->pgmSz = 256; // Try to force this to write 256 bytes per page + DBG("OB data alloc\n"); + + optibootData->address = optibootData->segment; +} + +/* + * This is called on /pgmmega/upload . + * + * Functionality provided : flash the hex file that is sent to the Arduino Mega. + * Prerequisite : a succesfull call to /pgmmega/sync to force the Mega into programming mode + * and set up synchronized communication with it. + */ +int ICACHE_FLASH_ATTR cgiMegaData(HttpdConnData *connData) { + // DBG("cgiMegaData state=%d postLen=%d\n", progState, connData->post->len); + + if (connData->conn==NULL) + return HTTPD_CGI_DONE; // Connection aborted. Clean up. + if (!optibootData) + if (debug()) DBG("OB pgm: state=%d postLen=%d\n", progState, connData->post->len); + + // check that we have sync + if (errMessage[0] || progState < stateProg) { + DBG("OB not in sync, state=%d (%s), err=%s\n", progState, progStates[progState], errMessage); + errorResponse(connData, 400, errMessage[0] ? errMessage : "Optiboot not in sync"); + return HTTPD_CGI_DONE; + } + + // check that we don't have two concurrent programming requests going on + if (connData->cgiPrivData == (void *)-1) { + DBG("OB aborted\n"); + errorResponse(connData, 400, "Request got aborted by a concurrent sync request"); + return HTTPD_CGI_DONE; + } + + // allocate data structure to track programming + if (!optibootData) { + allocateOptibootData(); + if (!optibootData || !optibootData->pageBuf || !optibootData->saved) { + errorResponse(connData, 400, "Out of memory"); + return HTTPD_CGI_DONE; + } + } + + // iterate through the data received and program the AVR one block at a time + HttpdPostData *post = connData->post; + char *saved = optibootData->saved; + while (post->buffLen > 0) { + // first fill-up the saved buffer + short saveLen = strlen(saved); + if (saveLen < MAX_SAVED) { + short cpy = MAX_SAVED-saveLen; + if (cpy > post->buffLen) cpy = post->buffLen; + os_memcpy(saved+saveLen, post->buff, cpy); + saveLen += cpy; + saved[saveLen] = 0; // string terminator + os_memmove(post->buff, post->buff+cpy, post->buffLen-cpy); + post->buffLen -= cpy; + //DBG("OB cp %d buff->saved\n", cpy); + } + + // process HEX records + while (saveLen >= 11) { // 11 is minimal record length + // skip any CR/LF + short skip = 0; + while (skip < saveLen && (saved[skip] == '\n' || saved[skip] == '\r')) + skip++; + if (skip > 0) { + // shift out cr/lf (keep terminating \0) + os_memmove(saved, saved+skip, saveLen+1-skip); + saveLen -= skip; + if (saveLen < 11) break; + DBG("OB skip %d cr/lf\n", skip); + } + + // inspect whether we have a proper record start + if (saved[0] != ':') { + DBG("OB found non-: start\n"); + os_sprintf(errMessage, "Expected start of record in POST data, got %s", saved); + errorResponse(connData, 400, errMessage); + optibootInit(); + return HTTPD_CGI_DONE; + } + + if (!checkHex(saved+1, 2)) { + errorResponse(connData, 400, errMessage); + optibootInit(); + return HTTPD_CGI_DONE; + } + uint8_t recLen = getHexValue(saved+1, 2); + //DBG("OB record %d\n", recLen); + + // process record + if (saveLen >= 11+recLen*2) { + if (!processRecord(saved, 11+recLen*2)) { + DBG("OB process err %s\n", errMessage); + errorResponse(connData, 400, errMessage); + optibootInit(); + return HTTPD_CGI_DONE; + } + short shift = 11+recLen*2; + os_memmove(saved, saved+shift, saveLen+1-shift); + saveLen -= shift; + //DBG("OB %d byte record\n", shift); + } else { + break; + } + } + } + + short code; + if (post->received < post->len) { + //DBG("OB pgm need more\n"); + return HTTPD_CGI_MORE; + } + + if (optibootData->eof) { + code = 200; + // calculate some stats + float dt = ((system_get_time() - optibootData->startTime)/1000)/1000.0; // in seconds + uint32_t pgmDone = optibootData->pgmDone; + optibootInit(); + os_sprintf(errMessage, "Success. %d bytes at %d baud in %d.%ds, %dB/s %d%% efficient", + pgmDone, baudRate, (int)dt, (int)(dt*10)%10, (int)(pgmDone/dt), + (int)(100.0*(10.0*pgmDone/baudRate)/dt)); + } else { + code = 400; + optibootInit(); + os_strcpy(errMessage, "Improperly terminated POST data"); + } + + DBG("OB pgm done: %d -- %s\n", code, errMessage); + noCacheHeaders(connData, code); + httpdEndHeaders(connData); + httpdSend(connData, errMessage, -1); + errMessage[0] = 0; + + return HTTPD_CGI_DONE; +} + +// Program a flash page +bool ICACHE_FLASH_ATTR megaProgramPage(void) { + if (optibootData == NULL) + allocateOptibootData(); + if (debug()) + DBG("programPage len %d addr 0x%04x\n", optibootData->pageLen, optibootData->address + optibootData->segment); + + if (optibootData->pageLen == 0) + return true; + armTimer(PGM_TIMEOUT); // keep the timerCB out of the picture + + uint16_t pgmLen = optibootData->pageLen; + if (pgmLen > optibootData->pgmSz) + pgmLen = optibootData->pgmSz; + // DBG("OB pgm %d@0x%x\n", pgmLen, optibootData->address + optibootData->segment); + + // send address to optiboot (little endian format) +#ifdef DBG_GPIO5 + gpio_output_set((1<<5), 0, (1<<5), 0); // output 1 +#endif + ackWait++; + + uint32_t addr = optibootData->address + optibootData->segment; + sendLoadAddressQuery(addr); + readLoadAddressReply(); + + armTimer(PGM_TIMEOUT); + if (! reply_ok) { + DBG("OB pgm failed in load address\n"); + return false; + } + armTimer(PGM_TIMEOUT); + // DBG("OB sent address 0x%04x\n", addr); + + // send page content + sendProgramPageQuery(optibootData->pageBuf, pgmLen); + + armTimer(PGM_TIMEOUT); + readProgramPageReply(); + armTimer(PGM_TIMEOUT); + if (!reply_ok) { + DBG("OB pgm failed in prog page\n"); + return false; + } + + // shift data out of buffer + os_memmove(optibootData->pageBuf, optibootData->pageBuf+pgmLen, optibootData->pageLen-pgmLen); + optibootData->pageLen -= pgmLen; +#if 1 + optibootData->address += pgmLen; +#else + DBG("Address old %08x ", optibootData->address + optibootData->segment); + optibootData->address += pgmLen; + DBG(" new %08x\n", optibootData->address + optibootData->segment); +#endif + optibootData->pgmDone += pgmLen; + + // DBG("OB pgm OK\n"); + return true; +} + +//===== Rebooting and getting sync + +static void ICACHE_FLASH_ATTR armTimer(uint32_t ms) { + os_timer_disarm(&optibootTimer); + os_timer_arm(&optibootTimer, ms, 0); +} + +static int baudRates[] = { 0, 9600, 57600, 115200 }; + +static void ICACHE_FLASH_ATTR setBaud() { + baudRate = baudRates[(baudCnt++) % 4]; + uart0_baud(baudRate); + //DBG("OB changing to %ld baud\n", baudRate); +} + +static void ICACHE_FLASH_ATTR initBaud() { + baudRates[0] = flashConfig.baud_rate; + setBaud(); +} + +/* + * The timer callback does two things : + * 1. initially we await a timeout to start chatting with the Mega. This is good. + * 2. in other cases, timeouts are a problem, we failed somewhere. + */ +static void ICACHE_FLASH_ATTR megaTimerCB(void *arg) { + if (debug()) DBG("megaTimerCB state %d (%s)\n", progState, progStates[progState]); + + switch (progState) { + case stateInit: // initial delay expired, send sync chars + initPacket(); + + // DBG("Reset pin %d to LOW ...", flashConfig.reset_pin); + GPIO_OUTPUT_SET(flashConfig.reset_pin, 0); + os_delay_us(2000L); // Keep reset line low for 2 ms + GPIO_OUTPUT_SET(flashConfig.reset_pin, 1); + // DBG(" and up again.\n"); + + os_delay_us(2000L); // Now wait an additional 2 ms before sending packets + + // Send a couple of packets, until we get a reply + int ok = -1, i = 0; + while (ok < 0 && i++ < 8) { + os_delay_us(200L); + sendSyncPacket(); + cur_seqno++; + ok = readSyncPacket(); + } + if (ok < 0) + break; // Don't increment progState + + progState++; + + // Discard input until now + responseLen = 0; + + // Send a query, reply to be picked up by megaUartRecv(). + sendQueryPacket(PARAM_HW_VER); + + // Trigger timeout + armTimer(BAUD_INTERVAL-INIT_DELAY); + return; + case stateSync: // oops, must have not heard back!? + case stateProg: // we're programming and we timed-out of inaction + default: // we're trying to get some info from optiboot and it should have responded! + break; + } +} + +/* + * Receive response from optiboot. + * + * Based on our state, pick the response and interpret accordingly; then move state forward + * while sending the next query. + * + * This plays a ping-pong game between us and the MCU. + * Initial query is sent from megaTimerCB(). + */ +static void ICACHE_FLASH_ATTR megaUartRecv(char *buf, short length) { + int ok; + + //if (progState > stateGetSig3) + if (debug()) { + DBG("megaUartRecv %d bytes, ", length); + for (int i=0; i= 9) { + if (responseBuf[4] == TOKEN && responseBuf[5] == CMD_GET_PARAMETER && responseBuf[6] == STATUS_CMD_OK) { + optibootData->hardwareVersion = responseBuf[7]; + ok++; + } + os_memcpy(responseBuf, responseBuf+9, responseLen-9); + responseLen -= 9; + } + progState++; + sendQueryPacket(PARAM_SW_MAJOR); + armTimer(PGM_INTERVAL); // reset timer + break; + case stateVar1: + if (responseLen >= 9) { + if (responseBuf[4] == TOKEN && responseBuf[5] == CMD_GET_PARAMETER && responseBuf[6] == STATUS_CMD_OK) { + optibootData->firmwareVersionMajor = responseBuf[7]; + ok++; + } + os_memcpy(responseBuf, responseBuf+9, responseLen-9); + responseLen -= 9; + } + progState++; + sendQueryPacket(PARAM_SW_MINOR); + armTimer(PGM_INTERVAL); // reset timer + break; + case stateVar2: + if (responseLen >= 9) { + if (responseBuf[4] == TOKEN && responseBuf[5] == CMD_GET_PARAMETER && responseBuf[6] == STATUS_CMD_OK) { + optibootData->firmwareVersionMinor = responseBuf[7]; + ok++; + } + os_memcpy(responseBuf, responseBuf+9, responseLen-9); + responseLen -= 9; + } + progState++; + sendQueryPacket(PARAM_VTARGET); + armTimer(PGM_INTERVAL); // reset timer + break; + case stateVar3: + if (responseLen >= 9) { + if (responseBuf[4] == TOKEN && responseBuf[5] == CMD_GET_PARAMETER && responseBuf[6] == STATUS_CMD_OK) { + optibootData->vTarget = responseBuf[7]; + ok++; + } + os_memcpy(responseBuf, responseBuf+9, responseLen-9); + responseLen -= 9; + } + + progState++; + sendQuerySignaturePacket(0); + if (debug()) + DBG("Hardware version %d, firmware %d.%d. Vtarget = %d.%d V\n", + optibootData->hardwareVersion, optibootData->firmwareVersionMajor, optibootData->firmwareVersionMinor, optibootData->vTarget / 16, optibootData->vTarget % 16); + armTimer(PGM_INTERVAL); // reset timer + break; + case stateGetSig1: + if (responseLen >= 13) { + if (responseBuf[4] == TOKEN && responseBuf[5] == CMD_SPI_MULTI && responseBuf[3] == 7) { + optibootData->signature[0] = responseBuf[10]; + } + os_memcpy(responseBuf, responseBuf+13, responseLen-13); + responseLen -= 13; + progState++; + sendQuerySignaturePacket(1); + } + armTimer(PGM_INTERVAL); // reset timer + break; + case stateGetSig2: + if (responseLen >= 13) { + if (responseBuf[4] == TOKEN && responseBuf[5] == CMD_SPI_MULTI && responseBuf[3] == 7) { + optibootData->signature[1] = responseBuf[10]; + } + os_memcpy(responseBuf, responseBuf+13, responseLen-13); + responseLen -= 13; + progState++; + sendQuerySignaturePacket(2); + } + armTimer(PGM_INTERVAL); // reset timer + break; + case stateGetSig3: + if (responseLen >= 13) { + if (responseBuf[4] == TOKEN && responseBuf[5] == CMD_SPI_MULTI && responseBuf[3] == 7) { + optibootData->signature[2] = responseBuf[10]; + } + os_memcpy(responseBuf, responseBuf+13, responseLen-13); + responseLen -= 13; + progState++; + + if (debug()) DBG("Board signature %02x.%02x.%02x\n", optibootData->signature[0], optibootData->signature[1], optibootData->signature[2]); + sendReadFuseQuery('l'); + } + armTimer(PGM_INTERVAL); // reset timer + break; + + case stateGetFuse1: + optibootData->lfuse = getFuseReply(buf, length); + sendReadFuseQuery('h'); + progState++; + break; + + case stateGetFuse2: + optibootData->hfuse = getFuseReply(buf, length); + sendReadFuseQuery('e'); + progState++; + break; + + case stateGetFuse3: + optibootData->efuse = getFuseReply(buf, length); + if (debug()) + DBG("Fuses %02x %02x %02x\n", optibootData->lfuse, optibootData->hfuse, optibootData->efuse); + progState++; + break; + + case stateProg: + // Not sure what to do here. + break; + } +} + +int ICACHE_FLASH_ATTR cgiMegaRead(HttpdConnData *connData) { + // DBG("cgiMegaRead %s\n", connData->url); + + int p, i, len; + char *buffer, s[12]; + + debug_cnt++; + + // sscanf(connData->url + 14, "0x%x,%x", &p, &len); + char *ptr = connData->url + 14; + p = strtoul(ptr, &ptr, 16); + ptr++; // skip comma + len = strtoul(ptr, NULL, 16); + // DBG("cgiMegaRead : %x %x\n", p, len); + + buffer = os_malloc(len / 16 * 60); + if (buffer == 0) { + espconn_send(connData->conn, (uint8_t *)"Cannot allocate sufficient memory\n", 35); + return HTTPD_CGI_DONE; + } + buffer[0] = 0; + + // initPacket(); + sendLoadAddressQuery(p); + readLoadAddressReply(); + if (! reply_ok) { + espconn_send(connData->conn, (uint8_t *)"Load address failed\n", 20); + return HTTPD_CGI_DONE; + } + + for (i=0; iconn, (uint8_t *)"Unknown problem\n", 16); + return HTTPD_CGI_DONE; + } + + int j; + + os_sprintf(s, "%08x: ", p + i); + strcat(buffer, s); + + for (j=0; j<16; j++) { + os_sprintf(s, "%02x ", flash[j]); + strcat(buffer, s); + } + strcat(buffer, "\n"); + } + espconn_send(connData->conn, (uint8_t *)buffer, strlen(buffer)); + + // cleanupPacket(); + + // espconn_sent(connData->conn, "This is the output ... :-)\n", 27); + return HTTPD_CGI_DONE; +} + +void ICACHE_FLASH_ATTR sendReadFuseQuery(char fuse) { + pbuf.ms1 = 0; + pbuf.ms2 = 8; + pbuf.body[0] = CMD_SPI_MULTI; // 0x1D + pbuf.body[1] = 4; + pbuf.body[2] = 4; + pbuf.body[3] = 0; + switch (fuse) { + case 'l': + pbuf.body[4] = 0x50; pbuf.body[5] = 0x00; + break; + case 'h': + pbuf.body[4] = 0x58; pbuf.body[5] = 0x08; + break; + case 'e': + pbuf.body[4] = 0x50; pbuf.body[5] = 0x08; + break; + } + pbuf.body[6] = 0; + pbuf.body[7] = 0; + + writePacket(); +} + +/* + * To read the feedback directly. + */ +int ICACHE_FLASH_ATTR readReadFuseReply() { + reply_ok = false; + int len = readPacket(); + if (len != 7) { + DBG("readReadFuseReply: packet len %d, expexted 13.\n", len); + return -1; + } + if (pbuf.body[0] == CMD_SPI_MULTI && pbuf.body[1] == STATUS_CMD_OK) { + reply_ok = true; + return pbuf.body[5]; + } + return 0; +} + +/* + * Called from megaUartRecv so read from the buffer passed as parameter, + * don't call readPacket(). + */ +int ICACHE_FLASH_ATTR getFuseReply(char *ptr, int len) { + reply_ok = false; + if (len != 13) { + DBG("readReadFuseReply: packet len %d, expexted 13.\n", len); + return -1; + } + if (ptr[4] != TOKEN && ptr[0] != MESSAGE_START) + return -1; + if (ptr[5] == CMD_SPI_MULTI && ptr[6] == STATUS_CMD_OK) { + reply_ok = true; + return ptr[10]; + } + return 0; +} + +int ICACHE_FLASH_ATTR readFuse(char fuse) { + sendReadFuseQuery(fuse); + int x = readReadFuseReply(); + return x; +} + +/* + * /pgmmega/fuse/r1 reads fuse 1 + * /pgmmega/fuse/1 writes fuse 1 + */ +int ICACHE_FLASH_ATTR cgiMegaFuse(HttpdConnData *connData) { + DBG("cgiMegaFuse %s\n", connData->url); + + // decode url + char fuse = 'l'; + + // read it + int x = readFuse(fuse); + + // provide result + + // handle error cases + if (reply_ok) { + char buf[20]; + os_sprintf(buf, "Fuse '%c' : 0x%x", fuse, x); + + noCacheHeaders(connData, 200); + httpdEndHeaders(connData); + httpdSend(connData, buf, 0); + } else { + errorResponse(connData, 404, "Failed to reboot the MCU"); + } + + return HTTPD_CGI_DONE; +} + +/* + * Reboot the MCU, after which it will be in Arduino mode and we'll have lost + * synchronized communication with it. + */ +int ICACHE_FLASH_ATTR cgiMegaRebootMCU(HttpdConnData *connData) { + sendRebootMCUQuery(); + readRebootMCUReply(); + + if (reply_ok) { + noCacheHeaders(connData, 200); + httpdEndHeaders(connData); + httpdSend(connData, "", 0); + } else { + errorResponse(connData, 404, "Failed to reboot the MCU"); + } + return HTTPD_CGI_DONE; +} + +/* + * Several versions of a function to selectively enable debug output + */ +static void ICACHE_FLASH_ATTR debug_reset() { + debug_cnt = 0; + debugOn(false); +} + +static bool debug_on = false; + +static bool ICACHE_FLASH_ATTR debug() { +#if 0 + debug_cnt++; + if (debug_cnt < 30) + return true; + return false; +#endif +#if 0 + return true; +#endif + +#if 0 + if (debug_cnt > 0) + return true; + return false; +#endif + +#if 0 + if (optibootData == 0) + return false; + if ((0x0000F000 <= optibootData->address) && (optibootData->address <= 0x00010200)) + return true; + return false; +#endif + + return debug_on; +} + +static void debugOn(bool on) { + debug_on = on; +} diff --git a/esp-link/cgimega.h b/esp-link/cgimega.h new file mode 100644 index 0000000..e30a2d1 --- /dev/null +++ b/esp-link/cgimega.h @@ -0,0 +1,14 @@ +// Copyright (c) 2016-2017 by Danny Backx, see LICENSE.txt in the esp-link repo + +#ifndef CGIMEGA_H +#define CGIMEGA_H + +#include + +int ICACHE_FLASH_ATTR cgiMegaSync(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiMegaData(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiMegaRead(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiMegaFuse(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiMegaRebootMCU(HttpdConnData *connData); + +#endif diff --git a/esp-link/cgioptiboot.c b/esp-link/cgioptiboot.c index 831b74b..c94ac96 100644 --- a/esp-link/cgioptiboot.c +++ b/esp-link/cgioptiboot.c @@ -1,7 +1,11 @@ // Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo +// Some code moved to esp-link/pgmshared.c to avoid code duplication. +// Those changes are Copyright (c) 2017 by Danny Backx. + +// Protocol used : https://github.com/Optiboot/optiboot/wiki/HowOptibootWorks + #include -#include #include "cgi.h" #include "cgioptiboot.h" #include "config.h" @@ -11,6 +15,8 @@ #include "mqtt_cmd.h" #include "serled.h" +#include "pgmshared.h" + #define INIT_DELAY 150 // wait this many millisecs before sending anything #define BAUD_INTERVAL 600 // interval after which we change baud rate #define PGM_TIMEOUT 20000 // timeout after sync is achieved, in milliseconds @@ -43,33 +49,12 @@ static short ackWait; // counter of expected ACKs static uint16_t optibootVers; static uint32_t baudRate; // baud rate at which we're programming -#define RESP_SZ 64 -static char responseBuf[RESP_SZ]; // buffer to accumulate responses from optiboot -static short responseLen = 0; // amount accumulated so far -#define ERR_MAX 128 -static char errMessage[ERR_MAX]; // error message - #define MAX_PAGE_SZ 512 // max flash page size supported #define MAX_SAVED 512 // max chars in saved buffer -// structure used to remember request details from one callback to the next -// allocated dynamically so we don't burn so much static RAM -static struct optibootData { - char *saved; // buffer for saved incomplete hex records - char *pageBuf; // buffer for received data to be sent to AVR - uint16_t pageLen; // number of bytes in pageBuf - uint16_t pgmSz; // size of flash page to be programmed at a time - uint16_t pgmDone; // number of bytes programmed - uint32_t address; // address to write next page to - uint32_t startTime; // time of program POST request - HttpdConnData *conn; // request doing the programming, so we can cancel it - bool eof; // got EOF record -} *optibootData; // forward function references static void optibootTimerCB(void *); static void optibootUartRecv(char *buffer, short length); -static bool processRecord(char *buf, short len); -static bool programPage(void); static void armTimer(uint32_t ms); static void initBaud(void); @@ -143,7 +128,7 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) { // start sync timer os_timer_disarm(&optibootTimer); os_timer_setfn(&optibootTimer, optibootTimerCB, NULL); - os_timer_arm(&optibootTimer, INIT_DELAY, 0); + os_timer_arm_us(&optibootTimer, INIT_DELAY * 1000, 0); // respond with optimistic OK noCacheHeaders(connData, 204); @@ -176,30 +161,6 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) { return HTTPD_CGI_DONE; } -// verify that N chars are hex characters -static bool ICACHE_FLASH_ATTR checkHex(char *buf, short len) { - while (len--) { - char c = *buf++; - if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) - continue; - DBG("OB non-hex\n"); - os_sprintf(errMessage, "Non hex char in POST record: '%c'/0x%02x", c, c); - return false; - } - return true; -} - -// get hex value of some hex characters -static uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len) { - uint32_t v = 0; - while (len--) { - v = (v<<4) | (uint32_t)(*buf & 0xf); - if (*buf > '9') v += 9; - buf++; - } - return v; -} - //===== Cgi to write firmware to Optiboot, requires prior sync call int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. @@ -229,6 +190,7 @@ int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) { errorResponse(connData, 400, "Out of memory"); return HTTPD_CGI_DONE; } + optibootData->mega = false; optibootData->pageBuf = pageBuf; optibootData->saved = saved; optibootData->startTime = system_get_time(); @@ -333,80 +295,6 @@ int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) { return HTTPD_CGI_DONE; } -// verify checksum -static bool ICACHE_FLASH_ATTR verifyChecksum(char *buf, short len) { - uint8_t sum = 0; - while (len >= 2) { - sum += (uint8_t)getHexValue(buf, 2); - buf += 2; - len -= 2; - } - return sum == 0; -} - -// Process a hex record -- assumes that the records starts with ':' & hex length -static bool ICACHE_FLASH_ATTR processRecord(char *buf, short len) { - buf++; len--; // skip leading ':' - // check we have all hex chars - if (!checkHex(buf, len)) return false; - // verify checksum - if (!verifyChecksum(buf, len)) { - buf[len] = 0; - os_sprintf(errMessage, "Invalid checksum for record %s", buf); - return false; - } - // dispatch based on record type - uint8_t type = getHexValue(buf+6, 2); - switch (type) { - case 0x00: { // data - //DBG("OB REC data %ld pglen=%d\n", getHexValue(buf, 2), optibootData->pageLen); - uint32_t addr = getHexValue(buf+2, 4); - // check whether we need to program previous record(s) - if (optibootData->pageLen > 0 && - addr != ((optibootData->address+optibootData->pageLen)&0xffff)) { - //DBG("OB addr chg\n"); - if (!programPage()) return false; - } - // set address, unless we're adding to the end (programPage may have changed pageLen) - if (optibootData->pageLen == 0) { - optibootData->address = (optibootData->address & 0xffff0000) | addr; - //DBG("OB set-addr 0x%lx\n", optibootData->address); - } - // append record - uint16_t recLen = getHexValue(buf, 2); - for (uint16_t i=0; ipageBuf[optibootData->pageLen++] = getHexValue(buf+8+2*i, 2); - // program page, if we have a full page - if (optibootData->pageLen >= optibootData->pgmSz) { - //DBG("OB full\n"); - if (!programPage()) return false; - } - break; } - case 0x01: // EOF - DBG("OB EOF\n"); - // program any remaining partial page - if (optibootData->pageLen > 0) - if (!programPage()) return false; - optibootData->eof = true; - break; - case 0x04: // address - DBG("OB address 0x%x\n", getHexValue(buf+8, 4) << 16); - // program any remaining partial page - if (optibootData->pageLen > 0) - if (!programPage()) return false; - optibootData->address = getHexValue(buf+8, 4) << 16; - break; - case 0x05: // start address - // ignore, there's no way to tell optiboot that... - break; - default: - DBG("OB bad record type\n"); - os_sprintf(errMessage, "Invalid/unknown record type: 0x%02x", type); - return false; - } - return true; -} - // Poll UART for ACKs, max 50ms static bool pollAck() { char recv[16]; @@ -427,7 +315,7 @@ static bool pollAck() { } // Program a flash page -static bool ICACHE_FLASH_ATTR programPage(void) { +bool ICACHE_FLASH_ATTR optibootProgramPage(void) { if (optibootData->pageLen == 0) return true; armTimer(PGM_TIMEOUT); // keep the timerCB out of the picture @@ -494,7 +382,7 @@ static bool ICACHE_FLASH_ATTR programPage(void) { static void ICACHE_FLASH_ATTR armTimer(uint32_t ms) { os_timer_disarm(&optibootTimer); - os_timer_arm(&optibootTimer, ms, 0); + os_timer_arm_us(&optibootTimer, ms * 1000, 0); } static int baudRates[] = { 0, 9600, 57600, 115200 }; @@ -549,6 +437,14 @@ static void ICACHE_FLASH_ATTR optibootTimerCB(void *arg) { } } +#if 0 +static void ICACHE_FLASH_ATTR print_buff(char *msg, char *buf, short length) { + DBG("OB GOT %s %d:", msg, length); + for (int i=0; i 1 && buf[0] == STK_INSYNC && buf[1] == STK_OK) { @@ -561,6 +457,7 @@ static short ICACHE_FLASH_ATTR skipInSync(char *buf, short length) { // receive response from optiboot, we only store the last response static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { + //print_buff("RAW", buf, length); // append what we got to what we have accumulated if (responseLen < RESP_SZ-1) { char *rb = responseBuf+responseLen; @@ -569,6 +466,7 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { responseLen = rb-responseBuf; responseBuf[responseLen] = 0; // string terminator } + //print_buff("RBUF", responseBuf, responseLen); // dispatch based the current state switch (progState) { @@ -583,13 +481,13 @@ static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { } else if (responseLen > 1 && responseBuf[responseLen-2] == STK_INSYNC && responseBuf[responseLen-1] == STK_OK) { // got sync response - os_memcpy(responseBuf, responseBuf+2, responseLen-2); - responseLen -= 2; + responseLen = 0; // ignore anything that may have accumulated // send request to get signature uart0_write_char(STK_READ_SIGN); uart0_write_char(CRC_EOP); progState++; armTimer(PGM_INTERVAL); // reset timer + //DBG("OB: got sync, sent read-sig\n"); } else { // nothing useful, keep at most half the buffer for error message purposes if (responseLen > RESP_SZ/2) { diff --git a/esp-link/cgiwifi.c b/esp-link/cgiwifi.c index 987cb89..bf699ca 100644 --- a/esp-link/cgiwifi.c +++ b/esp-link/cgiwifi.c @@ -13,6 +13,7 @@ Cgi/template routines for the /wifi url. * ---------------------------------------------------------------------------- */ + #include #include "cgiwifi.h" #include "cgi.h" @@ -34,7 +35,7 @@ bool mdns_started = false; // ===== wifi status change callbacks static WifiStateChangeCb wifi_state_change_cb[4]; -// Temp store for new staion config +// Temp store for new station config struct station_config stconf; // Temp store for new ap config @@ -226,25 +227,13 @@ static void ICACHE_FLASH_ATTR scanStartCb(void *arg) { 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, 200, 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[1460]; const int chunk = 1460/64; // ssid is up to 32 chars int len = 0; - os_printf("GET scan: cgiData=%d noAps=%d\n", (int)connData->cgiData, cgiWifiAps.noAps); + DBG("GET scan: cgiData=%d noAps=%d\n", (int)connData->cgiData, cgiWifiAps.noAps); // handle continuation call, connData->cgiData-1 is the position in the scan results where we // we need to continue sending from (using -1 'cause 0 means it's the first call) @@ -284,6 +273,27 @@ static int ICACHE_FLASH_ATTR cgiWiFiGetScan(HttpdConnData *connData) { return HTTPD_CGI_MORE; } +// Start scanning, without parameters +void ICACHE_FLASH_ATTR wifiStartScan() { + if (!cgiWifiAps.scanInProgress) { + cgiWifiAps.scanInProgress = 1; + os_timer_disarm(&scanTimer); + os_timer_setfn(&scanTimer, scanStartCb, NULL); + os_timer_arm_us(&scanTimer, 200000, 0); + } +} + +// Start scanning, web interface +int ICACHE_FLASH_ATTR cgiWiFiStartScan(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + jsonHeader(connData, 200); + + // Don't duplicate code, reuse the function above. + wifiStartScan(); + + return HTTPD_CGI_DONE; +} + int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { if (connData->requestType == HTTPD_METHOD_GET) { return cgiWiFiGetScan(connData); @@ -358,7 +368,19 @@ static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { // IP address os_timer_disarm(&resetTimer); os_timer_setfn(&resetTimer, resetTimerCb, NULL); - os_timer_arm(&resetTimer, 4*RESET_TIMEOUT, 0); + os_timer_arm_us(&resetTimer, 4*RESET_TIMEOUT * 1000, 0); +} + +// Kick off connection to some network +void ICACHE_FLASH_ATTR connectToNetwork(char *ssid, char *pass) { + os_strncpy((char*)stconf.ssid, ssid, 32); + os_strncpy((char*)stconf.password, pass, 64); + DBG("Wifi try to connect to AP %s pw %s\n", ssid, pass); + + // Schedule disconnect/connect + os_timer_disarm(&reassTimer); + os_timer_setfn(&reassTimer, reassTimerCb, NULL); + os_timer_arm_us(&reassTimer, 1000000, 0); // 1 second for the response of this request to make it } // This cgi uses the routines above to connect to a specific access point with the @@ -380,14 +402,8 @@ int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { 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); - DBG("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); // 1 second for the response of this request to make it + + connectToNetwork(essid, passwd); jsonHeader(connData, 200); } else { jsonHeader(connData, 400); @@ -425,12 +441,12 @@ static bool ICACHE_FLASH_ATTR parse_ip(char *buff, ip_addr_t *ip_ptr) { 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()); + DBG("\"ip\": \"%d.%d.%d.%d\"\n", IP2STR(&info.ip.addr)); + DBG("\"netmask\": \"%d.%d.%d.%d\"\n", IP2STR(&info.netmask.addr)); + DBG("\"gateway\": \"%d.%d.%d.%d\"\n", IP2STR(&info.gw.addr)); + DBG("\"hostname\": \"%s\"\n", wifi_station_get_hostname()); } else { - os_printf("\"ip\": \"-none-\"\n"); + DBG("\"ip\": \"-none-\"\n"); } } #endif @@ -562,7 +578,7 @@ int ICACHE_FLASH_ATTR cgiApSettingsChange(HttpdConnData *connData) { if (checkString(buff) && len>7 && len<=64) { // String preprocessing done in client side, wifiap.js line 31 os_memcpy(apconf.password, buff, len); - os_printf("Setting AP password len=%d\n", len); + DBG("Setting AP password len=%d\n", len); } else if (len != 0) { jsonHeader(connData, 400); httpdSend(connData, "PASSWORD not valid or out of range", -1); @@ -578,18 +594,18 @@ int ICACHE_FLASH_ATTR cgiApSettingsChange(HttpdConnData *connData) { apconf.authmode = value; } else { // If out of range set by default - os_printf("Forcing AP authmode to WPA_WPA2_PSK\n"); + DBG("Forcing AP authmode to WPA_WPA2_PSK\n"); apconf.authmode = 4; } } else { // Valid password but wrong auth mode, default 4 - os_printf("Forcing AP authmode to WPA_WPA2_PSK\n"); + DBG("Forcing AP authmode to WPA_WPA2_PSK\n"); apconf.authmode = 4; } } else { apconf.authmode = 0; } - os_printf("Setting AP authmode=%d\n", apconf.authmode); + DBG("Setting AP authmode=%d\n", apconf.authmode); // Set max connection number len=httpdFindArg(connData->getArgs, "ap_maxconn", buff, sizeof(buff)); if(len>0){ @@ -943,5 +959,39 @@ void ICACHE_FLASH_ATTR wifiInit() { // 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); + os_timer_arm_us(&resetTimer, RESET_TIMEOUT * 1000, 0); +} + +// Access functions for cgiWifiAps : query the number of entries in the table +int ICACHE_FLASH_ATTR wifiGetApCount() { + if (cgiWifiAps.scanInProgress) + return 0; + return cgiWifiAps.noAps; +} + +// Access functions for cgiWifiAps : returns the name of a network, i is the index into the array, return stored in memory pointed to by ptr. +ICACHE_FLASH_ATTR void wifiGetApName(int i, char *ptr) { + if (i < 0) + return; + if (i >= cgiWifiAps.noAps) + return; + + if (ptr != 0) + strncpy(ptr, cgiWifiAps.apData[i]->ssid, 32); + + DBG("AP %s\n", cgiWifiAps.apData[i]->ssid); +} + +// Access functions for cgiWifiAps : returns the signal strength of network (i is index into array). Return current network strength for negative i. +ICACHE_FLASH_ATTR int wifiSignalStrength(int i) { + sint8 rssi; + + if (i < 0 || i == 255) + rssi = wifi_station_get_rssi(); // Current network's signal strength + else if (i >= cgiWifiAps.noAps) + rssi = 0; // FIX ME + else + rssi = cgiWifiAps.apData[i]->rssi; // Signal strength of any known network + + return rssi; } diff --git a/esp-link/cgiwifi.h b/esp-link/cgiwifi.h index 667f27d..ccae68f 100644 --- a/esp-link/cgiwifi.h +++ b/esp-link/cgiwifi.h @@ -24,4 +24,10 @@ int checkString(char *str); extern uint8_t wifiState; extern bool mdns_started; +int wifiGetApCount(); +void wifiGetApName(int, char *); +int wifiSignalStrength(int); +void connectToNetwork(char *, char *); +void wifiStartScan(); + #endif diff --git a/esp-link/main.c b/esp-link/main.c index 804bb5e..c132786 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -19,6 +19,7 @@ #include "cgimqtt.h" #include "cgiflash.h" #include "cgioptiboot.h" +#include "cgimega.h" #include "cgiwebserversetup.h" #include "auth.h" #include "espfs.h" @@ -31,7 +32,10 @@ #include "log.h" #include "gpio.h" #include "cgiservices.h" + +#ifdef WEBSERVER #include "web-server.h" +#endif #ifdef SYSLOG #include "syslog.h" @@ -69,8 +73,16 @@ HttpdBuiltInUrl builtInUrls[] = { { "/flash/next", cgiGetFirmwareNext, NULL }, { "/flash/upload", cgiUploadFirmware, NULL }, { "/flash/reboot", cgiRebootFirmware, NULL }, + { "/pgm/sync", cgiOptibootSync, NULL }, { "/pgm/upload", cgiOptibootData, NULL }, + + { "/pgmmega/sync", cgiMegaSync, NULL }, // Start programming mode + { "/pgmmega/upload", cgiMegaData, NULL }, // Upload stuff + { "/pgmmega/read/*", cgiMegaRead, NULL }, // Download stuff (to verify) + { "/pgmmega/fuse/*", cgiMegaFuse, NULL }, // Read or write fuse + { "/pgmmega/rebootmcu", cgiMegaRebootMCU, NULL }, // Get out of programming mode + { "/log/text", ajaxLog, NULL }, { "/log/dbg", ajaxLogDbg, NULL }, { "/log/reset", cgiReset, NULL }, @@ -99,8 +111,10 @@ HttpdBuiltInUrl builtInUrls[] = { #ifdef MQTT { "/mqtt", cgiMqtt, NULL }, #endif +#ifdef WEBSERVER { "/web-server/upload", cgiWebServerSetupUpload, NULL }, { "*.json", WEB_CgiJsonHook, NULL }, //Catch-all cgi JSON queries +#endif { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem { NULL, NULL, NULL } }; @@ -177,7 +191,9 @@ user_init(void) { //os_printf("espFsInit %s\n", res?"ERR":"ok"); // mount the http handlers httpdInit(builtInUrls, 80); +#ifdef WEBSERVER WEB_Init(); +#endif // init the wifi-serial transparent bridge (port 23) serbridgeInit(23, 2323); diff --git a/esp-link/pgmshared.c b/esp-link/pgmshared.c new file mode 100644 index 0000000..44e157a --- /dev/null +++ b/esp-link/pgmshared.c @@ -0,0 +1,149 @@ +// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo +// Copyright (c) 2016-2017 by Danny Backx + +#include +#include +#include "cgi.h" +#include "config.h" +#include "uart.h" +#include "stk500v2.h" +#include "serbridge.h" +#include "serled.h" + +#include "pgmshared.h" + +struct optibootData *optibootData; + +char responseBuf[RESP_SZ]; // buffer to accumulate responses from optiboot +short responseLen = 0; // amount accumulated so far +char errMessage[ERR_MAX]; // error message + +// verify that N chars are hex characters +bool ICACHE_FLASH_ATTR checkHex(char *buf, short len) { + while (len--) { + char c = *buf++; + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) + continue; + DBG("OB non-hex\n"); + os_sprintf(errMessage, "Non hex char in POST record: '%c'/0x%02x", c, c); + return false; + } + return true; +} + +// get hex value of some hex characters +uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len) { + uint32_t v = 0; + while (len--) { + v = (v<<4) | (uint32_t)(*buf & 0xf); + if (*buf > '9') v += 9; + buf++; + } + return v; +} + +// verify checksum +static bool ICACHE_FLASH_ATTR verifyChecksum(char *buf, short len) { + uint8_t sum = 0; + while (len >= 2) { + sum += (uint8_t)getHexValue(buf, 2); + buf += 2; + len -= 2; + } + return sum == 0; +} + +// We've not built one function that works on both, but kept the different functions. +// This calls one or the other +static bool ICACHE_FLASH_ATTR programPage() { + if (optibootData->mega) + return megaProgramPage(); + return optibootProgramPage(); +} + +// Process a hex record -- assumes that the records starts with ':' & hex length +bool ICACHE_FLASH_ATTR processRecord(char *buf, short len) { + buf++; len--; // skip leading ':' + // check we have all hex chars + if (!checkHex(buf, len)) return false; + // verify checksum + if (!verifyChecksum(buf, len)) { + buf[len] = 0; + os_sprintf(errMessage, "Invalid checksum for record %s", buf); + return false; + } + // dispatch based on record type + uint8_t type = getHexValue(buf+6, 2); + switch (type) { + case 0x00: { // Intel HEX data record + //DBG("OB REC data %ld pglen=%d\n", getHexValue(buf, 2), optibootData->pageLen); + uint32_t addr = getHexValue(buf+2, 4); + // check whether we need to program previous record(s) + if (optibootData->pageLen > 0 && + addr != ((optibootData->address+optibootData->pageLen)&0xffff)) { + //DBG("OB addr chg\n"); + //DBG("processRecord addr chg, len %d, addr 0x%04x\n", optibootData->pageLen, addr); + if (!programPage()) return false; + } + // set address, unless we're adding to the end (programPage may have changed pageLen) + if (optibootData->pageLen == 0) { + optibootData->address = (optibootData->address & 0xffff0000) | addr; + //DBG("OB set-addr 0x%lx\n", optibootData->address); + } + // append record + uint16_t recLen = getHexValue(buf, 2); + for (uint16_t i=0; ipageBuf[optibootData->pageLen++] = getHexValue(buf+8+2*i, 2); + // program page, if we have a full page + if (optibootData->pageLen >= optibootData->pgmSz) { + //DBG("OB full\n"); + DBG("processRecord %d, call programPage() %08x\n", optibootData->pgmSz, optibootData->address + optibootData->segment); + if (!programPage()) return false; + } + break; } + case 0x01: // Intel HEX EOF record + DBG("OB EOF\n"); + // program any remaining partial page +#if 1 + if (optibootData->pageLen > 0) { + // DBG("processRecord remaining partial page, len %d, addr 0x%04x\n", optibootData->pageLen, optibootData->address + optibootData->segment); + if (!programPage()) return false; + } + optibootData->eof = true; +#else + if (optibootData->pageLen > 0) { + // HACK : fill up with 0xFF + while (optibootData->pageLen < 256) { + optibootData->pageBuf[optibootData->pageLen++] = 0xFF; + } + if (!programPage()) return false; + } + optibootData->eof = true; +#endif + break; + case 0x04: // Intel HEX address record + DBG("OB address 0x%x\n", getHexValue(buf+8, 4) << 16); + // program any remaining partial page + if (optibootData->pageLen > 0) { + DBG("processRecord 0x04 remaining partial page, len %d, addr 0x%04x\n", + optibootData->pageLen, optibootData->address + optibootData->segment); + if (!programPage()) return false; + } + optibootData->address = getHexValue(buf+8, 4) << 16; + break; + case 0x05: // Intel HEX start address (MDK-ARM only) + // ignore, there's no way to tell optiboot that... + break; + case 0x02: // Intel HEX extended segment address record + // Depending on the case, just ignoring this record could solve the problem + // optibootData->segment = getHexValue(buf+8, 4) << 4; + DBG("OB segment 0x%08X\n", optibootData->segment); + return true; + default: + // DBG("OB bad record type\n"); + DBG(errMessage, "Invalid/unknown record type: 0x%02x, packet %s", type, buf); + return false; + } + return true; +} + diff --git a/esp-link/pgmshared.h b/esp-link/pgmshared.h new file mode 100644 index 0000000..d69979c --- /dev/null +++ b/esp-link/pgmshared.h @@ -0,0 +1,54 @@ +// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo +// Copyright (c) 2017 by Danny Backx + +#ifndef _PGM_SHARED_H_ +#define _PGM_SHARED_H_ + +#include "user_config.h" + +#define RESP_SZ 64 +#define ERR_MAX 128 + +extern char responseBuf[RESP_SZ]; +extern short responseLen; +extern char errMessage[ERR_MAX]; + +// structure used to remember request details from one callback to the next +// allocated dynamically so we don't burn so much static RAM +extern struct optibootData { + char *saved; // buffer for saved incomplete hex records + char *pageBuf; // buffer for received data to be sent to AVR + uint16_t pageLen; // number of bytes in pageBuf + uint16_t pgmSz; // size of flash page to be programmed at a time + uint32_t pgmDone; // number of bytes programmed + uint32_t address; // address to write next page to + uint32_t segment; // for extended segment addressing, added to the address field + uint32_t startTime; // time of program POST request + HttpdConnData *conn; // request doing the programming, so we can cancel it + bool eof; // got EOF record + + // Whether to use the Mega (STK500v2) protocol + bool mega; + + // STK500v2 variables + int hardwareVersion, + firmwareVersionMajor, + firmwareVersionMinor, + vTarget; + uint8_t signature[3]; + uint8_t lfuse, hfuse, efuse; +} *optibootData; + +bool ICACHE_FLASH_ATTR checkHex(char *buf, short len); +uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len); +bool ICACHE_FLASH_ATTR processRecord(char *buf, short len); +bool megaProgramPage(void); +bool optibootProgramPage(void); + +#ifdef OPTIBOOT_DBG +#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) +#else +#define DBG(format, ...) do { } while(0) +#endif + +#endif diff --git a/esp-link/stk500v2.h b/esp-link/stk500v2.h new file mode 100644 index 0000000..b7512d1 --- /dev/null +++ b/esp-link/stk500v2.h @@ -0,0 +1,114 @@ +//**** ATMEL AVR - A P P L I C A T I O N N O T E ************************ +//* +//* Title: AVR068 - STK500 Communication Protocol +//* Filename: command.h +//* Version: 1.0 +//* Last updated: 31.01.2005 +//* +//* Support E-mail: avr@atmel.com +//* +//************************************************************************** + +// *****************[ STK message constants ]*************************** + +#define MESSAGE_START 0x1B //= ESC = 27 decimal +#define TOKEN 0x0E + +// *****************[ STK general command constants ]************************** + +#define CMD_SIGN_ON 0x01 +#define CMD_SET_PARAMETER 0x02 +#define CMD_GET_PARAMETER 0x03 +#define CMD_SET_DEVICE_PARAMETERS 0x04 +#define CMD_OSCCAL 0x05 +#define CMD_LOAD_ADDRESS 0x06 +#define CMD_FIRMWARE_UPGRADE 0x07 + + +// *****************[ STK ISP command constants ]****************************** + +#define CMD_ENTER_PROGMODE_ISP 0x10 +#define CMD_LEAVE_PROGMODE_ISP 0x11 +#define CMD_CHIP_ERASE_ISP 0x12 +#define CMD_PROGRAM_FLASH_ISP 0x13 +#define CMD_READ_FLASH_ISP 0x14 +#define CMD_PROGRAM_EEPROM_ISP 0x15 +#define CMD_READ_EEPROM_ISP 0x16 +#define CMD_PROGRAM_FUSE_ISP 0x17 +#define CMD_READ_FUSE_ISP 0x18 +#define CMD_PROGRAM_LOCK_ISP 0x19 +#define CMD_READ_LOCK_ISP 0x1A +#define CMD_READ_SIGNATURE_ISP 0x1B +#define CMD_READ_OSCCAL_ISP 0x1C +#define CMD_SPI_MULTI 0x1D + +// *****************[ STK PP command constants ]******************************* + +#define CMD_ENTER_PROGMODE_PP 0x20 +#define CMD_LEAVE_PROGMODE_PP 0x21 +#define CMD_CHIP_ERASE_PP 0x22 +#define CMD_PROGRAM_FLASH_PP 0x23 +#define CMD_READ_FLASH_PP 0x24 +#define CMD_PROGRAM_EEPROM_PP 0x25 +#define CMD_READ_EEPROM_PP 0x26 +#define CMD_PROGRAM_FUSE_PP 0x27 +#define CMD_READ_FUSE_PP 0x28 +#define CMD_PROGRAM_LOCK_PP 0x29 +#define CMD_READ_LOCK_PP 0x2A +#define CMD_READ_SIGNATURE_PP 0x2B +#define CMD_READ_OSCCAL_PP 0x2C + +#define CMD_SET_CONTROL_STACK 0x2D + +// *****************[ STK HVSP command constants ]***************************** + +#define CMD_ENTER_PROGMODE_HVSP 0x30 +#define CMD_LEAVE_PROGMODE_HVSP 0x31 +#define CMD_CHIP_ERASE_HVSP 0x32 +#define CMD_PROGRAM_FLASH_HVSP 0x33 +#define CMD_READ_FLASH_HVSP 0x34 +#define CMD_PROGRAM_EEPROM_HVSP 0x35 +#define CMD_READ_EEPROM_HVSP 0x36 +#define CMD_PROGRAM_FUSE_HVSP 0x37 +#define CMD_READ_FUSE_HVSP 0x38 +#define CMD_PROGRAM_LOCK_HVSP 0x39 +#define CMD_READ_LOCK_HVSP 0x3A +#define CMD_READ_SIGNATURE_HVSP 0x3B +#define CMD_READ_OSCCAL_HVSP 0x3C + +// *****************[ STK status constants ]*************************** + +// Success +#define STATUS_CMD_OK 0x00 + +// Warnings +#define STATUS_CMD_TOUT 0x80 +#define STATUS_RDY_BSY_TOUT 0x81 +#define STATUS_SET_PARAM_MISSING 0x82 + +// Errors +#define STATUS_CMD_FAILED 0xC0 +#define STATUS_CKSUM_ERROR 0xC1 +#define STATUS_CMD_UNKNOWN 0xC9 + +// *****************[ STK parameter constants ]*************************** +#define PARAM_BUILD_NUMBER_LOW 0x80 +#define PARAM_BUILD_NUMBER_HIGH 0x81 +#define PARAM_HW_VER 0x90 +#define PARAM_SW_MAJOR 0x91 +#define PARAM_SW_MINOR 0x92 +#define PARAM_VTARGET 0x94 +#define PARAM_VADJUST 0x95 +#define PARAM_OSC_PSCALE 0x96 +#define PARAM_OSC_CMATCH 0x97 +#define PARAM_SCK_DURATION 0x98 +#define PARAM_TOPCARD_DETECT 0x9A +#define PARAM_STATUS 0x9C +#define PARAM_DATA 0x9D +#define PARAM_RESET_POLARITY 0x9E +#define PARAM_CONTROLLER_INIT 0x9F + +// *****************[ STK answer constants ]*************************** + +#define ANSWER_CKSUM_ERROR 0xB0 + diff --git a/include/esp8266.h b/include/esp8266.h index 72b3b4d..90de764 100644 --- a/include/esp8266.h +++ b/include/esp8266.h @@ -5,6 +5,8 @@ #undef MEMLEAK_DEBUG #define USE_OPTIMIZE_PRINTF +#define os_timer_arm_us(a,b,c) os_timer_arm(a,b/1000,c) + #include #include #include diff --git a/megaflash b/megaflash new file mode 100755 index 0000000..5358027 --- /dev/null +++ b/megaflash @@ -0,0 +1,114 @@ +#! /bin/bash +# +# Flash an Arduino Mega with STK500v2 using the esp-link built-in programmer +# Basically we first reset the AVR and get in sync, and then send the hex file +# +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# Thorsten von Eicken 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. +# +# Danny Backx wrote the changes for Arduino Mega. +# ---------------------------------------------------------------------------- + +show_help() { + cat </dev/null; then + echo "ERROR: Cannot find curl: it is required for this script." >&2 + exit 1 +fi + +start=`date +%s` + +# ===== Parse arguments + +verbose= + +while getopts "hvx:" opt; do + case "$opt" in + h) show_help; exit 0 ;; + v) verbose=1 ;; + x) foo="$OPTARG" ;; + '?') show_help >&2; exit 1 ;; + esac +done + +# Shift off the options and optional --. +shift "$((OPTIND-1))" + +# Get the fixed arguments +if [[ $# != 2 ]]; then + show_help >&2 + exit 1 +fi +hostname=$1 +hex=$2 + +re='[-A-Za-z0-9.]+' +if [[ ! "$hostname" =~ $re ]]; then + echo "ERROR: hostname ${hostname} is not a valid hostname or ip address" >&2 + exit 1 +fi + +if [[ ! -r "$hex" ]]; then + echo "ERROR: cannot read hex file ($hex)" >&2 + exit 1 +fi + +# ===== Get AVR in sync + +[[ -n "$verbose" ]] && echo "Resetting AVR with http://$hostname/pgmmega/sync" >&2 +v=; [[ -n "$verbose" ]] && v=-v +sync=`curl -m 10 $v -s -w '%{http_code}' -XPOST "http://$hostname/pgmmega/sync"` +if [[ $? != 0 || "$sync" != 204 ]]; then + echo "Error resetting AVR" >&2 + exit 1 +fi + +while true; do + # sync=`curl -m 10 $v -s "http://$hostname/pgmmega/sync"` + sync=`curl $v -s "http://$hostname/pgmmega/sync"` + if [[ $? != 0 ]]; then + echo "Error checking sync" >&2 + exit 1 + fi + case "$sync" in + SYNC*) + echo "AVR in $sync" >&2 + break;; + "NOT READY"*) + [[ -n "$verbose" ]] && echo " Waiting for sync..." >&2 + ;; + *) + echo "Error checking sync: $sync" >&2 + exit 1 + ;; + esac + sleep 0.1 +done + +# ===== Send HEX file + +[[ -n "$verbose" ]] && echo "Sending HEX file for programming" >&2 +sync=`curl -m 20 $v -s -g -d "@$hex" "http://$hostname/pgmmega/upload"` +echo $sync +if [[ $? != 0 || ! "$sync" =~ ^Success ]]; then + echo "Error programming AVR" >&2 + exit 1 +fi + +sec=$(( `date +%s` - $start )) +echo "Success, took $sec seconds" >&2 +exit 0 diff --git a/rest/rest.c b/rest/rest.c index aa40ccb..2100109 100644 --- a/rest/rest.c +++ b/rest/rest.c @@ -302,8 +302,8 @@ REST_SetHeader(CmdPacket *cmd) { RestClient *client = restClient + (clientNum % MAX_REST); // Get header selector - uint32_t header_index; - if (cmdPopArg(&req, (uint8_t*)&header_index, 4)) return; + uint8_t header_index; + if (cmdPopArg(&req, &header_index, 1)) return; // Get header value uint16_t len = cmdArgLen(&req); diff --git a/serial/serbridge.c b/serial/serbridge.c index 29c03b2..7ba8e8e 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -9,7 +9,6 @@ #include "config.h" #include "console.h" #include "slip.h" -#include "cmd.h" #ifdef SYSLOG #include "syslog.h" #else diff --git a/socket/socket.c b/socket/socket.c index a17f216..1814cc2 100644 --- a/socket/socket.c +++ b/socket/socket.c @@ -7,7 +7,6 @@ #include "c_types.h" #include "ip_addr.h" #include "socket.h" -#include "cmd.h" #define SOCK_DBG @@ -37,6 +36,8 @@ typedef struct { // 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 SOCKET connections as there are slots in the pool. #define MAX_SOCKET 4 +#define MAX_RECEIVE_PACKET_LENGTH 100 + static SocketClient socketClient[MAX_SOCKET]; static uint8_t socketNum = 0xff; // index into socketClient for next slot to allocate @@ -49,12 +50,23 @@ socketclient_recv_cb(void *arg, char *pusrdata, unsigned short length) { uint8_t clientNum = client->conn_num; uint8_t cb_type = USERCB_RECV; DBG_SOCK("SOCKET #%d: Received %d bytes: %s\n", client-socketClient, length, pusrdata); - cmdResponseStart(CMD_RESP_CB, client->resp_cb, 4); - cmdResponseBody(&cb_type, 1); - cmdResponseBody(&clientNum, 1); - cmdResponseBody(&length, 2); - cmdResponseBody(pusrdata, length); - cmdResponseEnd(); + + unsigned short position = 0; + do + { + unsigned short msgLen = length - position; + if( msgLen > MAX_RECEIVE_PACKET_LENGTH ) + msgLen = MAX_RECEIVE_PACKET_LENGTH; + + cmdResponseStart(CMD_RESP_CB, client->resp_cb, 4); + cmdResponseBody(&cb_type, 1); + cmdResponseBody(&clientNum, 1); + cmdResponseBody(&msgLen, 2); + cmdResponseBody(pusrdata + position, msgLen); + cmdResponseEnd(); + + position += msgLen; + }while(position < length ); if (client->sock_mode != SOCKET_TCP_SERVER) { // We don't wait for a response DBG_SOCK("SOCKET #%d: disconnect after receiving\n", client-socketClient);