diff --git a/I2CRTC.cpp b/I2CRTC.cpp index 26c6bea6..6a788273 100644 --- a/I2CRTC.cpp +++ b/I2CRTC.cpp @@ -39,6 +39,10 @@ I2CRTC::I2CRTC() Wire.begin(); } +bool I2CRTC::exists() { + return (addr!=0); +} + bool I2CRTC::detect() { addr = 0; diff --git a/I2CRTC.h b/I2CRTC.h index b9cf53fb..7dac2433 100644 --- a/I2CRTC.h +++ b/I2CRTC.h @@ -24,6 +24,7 @@ class I2CRTC static void read(tmElements_t &tm); static void write(tmElements_t &tm); static bool detect(); + static bool exists(); private: static uint8_t dec2bcd(uint8_t num); diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index c14ff5d7..a70294c5 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -75,8 +75,8 @@ extern char ether_buffer[]; byte OpenSprinkler::state = OS_STATE_INITIAL; byte OpenSprinkler::prev_station_bits[MAX_NUM_BOARDS]; IOEXP* OpenSprinkler::expanders[MAX_NUM_BOARDS/2]; - IOEXP* OpenSprinkler::mainio; - IOEXP* OpenSprinkler::drio; + IOEXP* OpenSprinkler::mainio; // main controller IO expander object + IOEXP* OpenSprinkler::drio; // driver board IO expander object RCSwitch OpenSprinkler::rfswitch; String OpenSprinkler::wifi_ssid=""; @@ -470,14 +470,13 @@ byte OpenSprinkler::start_network() { lcd_print_line_clear_pgm(PSTR("Starting..."), 1); uint16_t httpport = (uint16_t)(iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)iopts[IOPT_HTTPPORT_0]; if(m_server) { delete m_server; m_server = NULL; } - if(udp) { delete udp; udp = NULL; } if (start_ether()) { m_server = new EthernetServer(httpport); m_server->begin(); - udp = new EthernetUDP(); - udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport + //udp = new EthernetUDP(); + //udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport #if defined(ESP8266) // turn off WiFi when ether is active @@ -497,8 +496,8 @@ byte OpenSprinkler::start_network() { wifi_server = new ESP8266WebServer(httpport); } - udp = new WiFiUDP(); - udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport + //udp = new WiFiUDP(); + //udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport return 1; #endif @@ -513,12 +512,8 @@ byte OpenSprinkler::start_ether() { if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 #endif Ethernet.init(PIN_ETHER_CS); // make sure to call this before any Ethernet calls + if(Ethernet.hardwareStatus()==EthernetNoHardware) return 0; load_hardware_mac((uint8_t*)tmp_buffer, true); - // detect if Enc28J60 exists - Enc28J60Network::init((uint8_t*)tmp_buffer); - uint8_t erevid = Enc28J60Network::geterevid(); - // a valid chip must have erevid > 0 and < 255 - if(erevid==0 || erevid==255) return 0; lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); @@ -765,7 +760,8 @@ void OpenSprinkler::begin() { PIN_RFTX = V2_PIN_RFTX; PIN_BOOST = V2_PIN_BOOST; PIN_BOOST_EN = V2_PIN_BOOST_EN; - PIN_LATCH_COM = V2_PIN_LATCH_COM; + PIN_LATCH_COMK = V2_PIN_LATCH_COMK; // os3.2latch uses H-bridge separate cathode and anode design + PIN_LATCH_COMA = V2_PIN_LATCH_COMA; PIN_SENSOR1 = V2_PIN_SENSOR1; PIN_SENSOR2 = V2_PIN_SENSOR2; } @@ -966,6 +962,18 @@ void OpenSprinkler::latch_setallzonepins(byte value) { } } +void OpenSprinkler::latch_disable_alloutputs_v2() { + digitalWriteExt(PIN_LATCH_COMA, LOW); + digitalWriteExt(PIN_LATCH_COMK, LOW); + + // latch v2 has a pca9555 the lowest 8 bits of which control all h-bridge anode pins + drio->i2c_write(NXP_OUTPUT_REG, drio->i2c_read(NXP_OUTPUT_REG) & 0xFF00); + // latch v2 has a 74hc595 which controls all h-bridge cathode pins + drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); + + // todo: handle expander +} + /** Set one zone (for LATCH controller) * This function sets one specified zone pin to a specified value */ @@ -989,30 +997,72 @@ void OpenSprinkler::latch_setzonepin(byte sid, byte value) { } } +void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { + if(A==HIGH && K==HIGH) return; // A and K must not be HIGH at the same time + + if(sid<8) { // on main controller + // v2 latch driver has one PCA9555, the lowest 8-bits of which control all anode pins + // and one 74HC595, which controls all cathod pins + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); + if(A) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + + drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<name[0]='S'; + pdata->name[3]=0; + pdata->name[4]=0; + StationAttrib at; + memset(&at, 0, sizeof(StationAttrib)); + at.mas=1; + at.seq=1; + pdata->attrib=at; // mas:1 seq:1 + pdata->type=STN_TYPE_STANDARD; + pdata->sped[0]='0'; + pdata->sped[1]=0; + for(int i=0; iname[1]='0'+(sid/10); // default station name + pdata->name[2]='0'+(sid%10); + } else { + pdata->name[1]='0'+(sid/100); + pdata->name[2]='0'+((sid%100)/10); + pdata->name[3]='0'+(sid%10); } + file_write_block(STATIONS_FILENAME, pdata, sizeof(StationData)*i, sizeof(StationData)); + } + + attribs_load(); // load and repackage attrib bits (for backward compatibility) + + // 3. write non-volatile controller status + nvdata.reboot_cause = REBOOT_CAUSE_RESET; + nvdata_save(); + last_reboot_cause = nvdata.reboot_cause; + + // 4. write program data: just need to write a program counter: 0 + file_write_byte(PROG_FILENAME, 0, 0); + + // 5. write 'done' file + file_write_byte(DONE_FILENAME, 0, 1); +} - remove_file(DONE_FILENAME); - /*remove_file(IOPTS_FILENAME); - remove_file(SOPTS_FILENAME); - remove_file(STATIONS_FILENAME); - remove_file(NVCON_FILENAME); - remove_file(PROG_FILENAME);*/ +/** Setup function for options */ +void OpenSprinkler::options_setup() { + + // Check reset conditions: + if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<219 || // fw version is invalid (<219) + !file_exists(DONE_FILENAME)) { // done file doesn't exist + + factory_reset(); - // 1. write all options - iopts_save(); - // wipe out sopts file first - memset(tmp_buffer, 0, MAX_SOPTS_SIZE); - for(int i=0; iname[0]='S'; - pdata->name[3]=0; - pdata->name[4]=0; - StationAttrib at; - memset(&at, 0, sizeof(StationAttrib)); - at.mas=1; - at.seq=1; - pdata->attrib=at; // mas:1 seq:1 - pdata->type=STN_TYPE_STANDARD; - pdata->sped[0]='0'; - pdata->sped[1]=0; - for(int i=0; iname[1]='0'+(sid/10); // default station name - pdata->name[2]='0'+(sid%10); - } else { - pdata->name[1]='0'+(sid/100); - pdata->name[2]='0'+((sid%100)/10); - pdata->name[3]='0'+(sid%10); - } - file_write_block(STATIONS_FILENAME, pdata, sizeof(StationData)*i, sizeof(StationData)); - } - - attribs_load(); // load and repackage attrib bits (for backward compatibility) - - // 3. write non-volatile controller status - nvdata.reboot_cause = REBOOT_CAUSE_RESET; - nvdata_save(); - last_reboot_cause = nvdata.reboot_cause; - - // 4. write program data: just need to write a program counter: 0 - file_write_byte(PROG_FILENAME, 0, 0); - - // 5. write 'done' file - file_write_byte(DONE_FILENAME, 0, 1); - } else { iopts_load(); @@ -1891,7 +1941,8 @@ void OpenSprinkler::options_setup() { // if BUTTON_1 is pressed during startup, go to 'reset all options' ui_set_options(IOPT_RESET); if (iopts[IOPT_RESET]) { - reboot_dev(REBOOT_CAUSE_NONE); + pre_factory_reset(); + reboot_dev(REBOOT_CAUSE_RESET); } break; @@ -1932,7 +1983,8 @@ void OpenSprinkler::options_setup() { lcd.clear(); ui_set_options(0); if (iopts[IOPT_RESET]) { - reboot_dev(REBOOT_CAUSE_NONE); + pre_factory_reset(); + reboot_dev(REBOOT_CAUSE_RESET); } break; } @@ -2350,22 +2402,6 @@ void OpenSprinkler::lcd_print_option(int i) { } -void OpenSprinkler::yield_nicely() { - if(m_server) Ethernet.tick(); -#if defined(ESP8266) - delay(0); -#endif -} - -/** A delay function that does not stall Ethernet or WiFi */ -void OpenSprinkler::delay_nicely(uint32_t ms) { - uint32_t start = millis(); - do { - //yield_nicely(); - delay(1); - } while(millis()-start < ms); -} - /** Button functions */ /** wait for button */ byte OpenSprinkler::button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding) { @@ -2379,7 +2415,7 @@ byte OpenSprinkler::button_read_busy(byte pin_butt, byte waitmode, byte butt, by while (digitalReadExt(pin_butt) == 0 && (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) @@ -2395,7 +2431,7 @@ byte OpenSprinkler::button_read(byte waitmode) byte curr = BUTTON_NONE; byte is_holding = (old&BUTTON_FLAG_HOLD); - delay_nicely(BUTTON_DELAY_MS); + delay(BUTTON_DELAY_MS); if (digitalReadExt(PIN_BUTTON_1) == 0) { curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); @@ -2447,10 +2483,6 @@ void OpenSprinkler::ui_set_options(int oid) case BUTTON_3: if (!(button & BUTTON_FLAG_DOWN)) break; if (button & BUTTON_FLAG_HOLD) { - // if IOPT_RESET is set to nonzero, change it to reset condition value - if (iopts[IOPT_RESET]) { - iopts[IOPT_RESET] = 0xAA; - } // long press, save options iopts_save(); finished = true; diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 3c9c1f6f..857c89c5 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -35,7 +35,7 @@ #include #include #include - #include + #include #include "I2CRTC.h" #if defined(ESP8266) @@ -224,6 +224,8 @@ class OpenSprinkler { static void nvdata_save(); static void options_setup(); + static void pre_factory_reset(); + static void factory_reset(); static void iopts_load(); static void iopts_save(); static bool sopt_save(byte oid, const char *buf); @@ -291,8 +293,6 @@ class OpenSprinkler { // return values are 'OR'ed with flags // check defines.h for details - static void yield_nicely(); - static void delay_nicely(uint32_t ms); // -- UI functions -- static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) static void lcd_set_brightness(byte value=1); @@ -327,6 +327,8 @@ class OpenSprinkler { static void latch_close(byte sid); static void latch_setzonepin(byte sid, byte value); static void latch_setallzonepins(byte value); + static void latch_disable_alloutputs_v2(); + static void latch_setzoneoutput_v2(byte sid, byte A, byte K); static void latch_apply_all_station_bits(); static byte prev_station_bits[]; #endif @@ -337,7 +339,6 @@ class OpenSprinkler { // todo #if defined(ARDUINO) extern EthernetServer *m_server; - extern UDP *udp; #if defined(ESP8266) extern ESP8266WebServer *wifi_server; #endif diff --git a/defines.cpp b/defines.cpp index bced9d54..726f628e 100644 --- a/defines.cpp +++ b/defines.cpp @@ -10,6 +10,8 @@ byte PIN_RFTX = 255; byte PIN_BOOST = 255; byte PIN_BOOST_EN = 255; byte PIN_LATCH_COM = 255; +byte PIN_LATCH_COMA = 255; +byte PIN_LATCH_COMK = 255; byte PIN_SENSOR1 = 255; byte PIN_SENSOR2 = 255; byte PIN_IOEXP_INT = 255; diff --git a/defines.h b/defines.h index c6d27585..cbfb8a58 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,7 @@ typedef unsigned long ulong; // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 7 // Firmware minor version +#define OS_FW_MINOR 9 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 @@ -106,6 +106,7 @@ typedef unsigned long ulong; #define REBOOT_CAUSE_WEATHER_FAIL 8 #define REBOOT_CAUSE_NETWORK_FAIL 9 #define REBOOT_CAUSE_NTP 10 +#define REBOOT_CAUSE_PROGRAM 11 #define REBOOT_CAUSE_POWERON 99 @@ -314,6 +315,8 @@ enum { extern byte PIN_BOOST; extern byte PIN_BOOST_EN; extern byte PIN_LATCH_COM; + extern byte PIN_LATCH_COMA; + extern byte PIN_LATCH_COMK; extern byte PIN_SENSOR1; extern byte PIN_SENSOR2; extern byte PIN_IOEXP_INT; @@ -351,15 +354,19 @@ enum { /* OS30 revision 2 pin defines */ // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V2_IO_CONFIG 0x1F00 // config bits - #define V2_IO_OUTPUT 0x1F00 // output bits + #define V2_IO_CONFIG 0x1000 // config bits + #define V2_IO_OUTPUT 0x1E00 // output bits #define V2_PIN_BUTTON_1 2 // button 1 #define V2_PIN_BUTTON_2 0 // button 2 #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 #define V2_PIN_RFTX 15 #define V2_PIN_BOOST IOEXP_PIN+13 #define V2_PIN_BOOST_EN IOEXP_PIN+14 - #define V2_PIN_LATCH_COM IOEXP_PIN+15 + #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) + #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch + #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock + #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data + #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) #define V2_PIN_SENSOR1 3 // sensor 1 #define V2_PIN_SENSOR2 10 // sensor 2 diff --git a/examples/mainArduino/mainArduino.ino b/examples/mainArduino/mainArduino.ino index 318ee60b..1aa649f7 100644 --- a/examples/mainArduino/mainArduino.ino +++ b/examples/mainArduino/mainArduino.ino @@ -1,6 +1,7 @@ #include -#if defined(ESP8266) +//#if defined(ESP8266) +#if 0 struct tcp_pcb; extern struct tcp_pcb* tcp_tw_pcbs; extern "C" void tcp_abort (struct tcp_pcb* pcb); @@ -24,7 +25,8 @@ void setup() { void loop() { do_loop(); -#if defined(ESP8266) +//#if defined(ESP8266) +#if 0 tcpCleanup(); #endif } diff --git a/gpio.cpp b/gpio.cpp index 49c4afef..6b177bd5 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -77,6 +77,34 @@ void PCA9555::i2c_write(uint8_t reg, uint16_t v){ Wire.endTransmission(); } +void PCA9555::shift_out(uint8_t plat, uint8_t pclk, uint8_t pdat, uint8_t v) { + if(plat reboot_timer) { - os.reboot_dev(REBOOT_CAUSE_TIMER); - } - #endif - + #if defined(ARDUINO) if (!ui_state) os.lcd_print_time(os.now_tz()); // print time @@ -736,6 +729,9 @@ void do_loop() pd.read(pid, &prog); // todo future: reduce load time if(prog.check_match(curr_time)) { // program match found + // check and process special program command + if(process_special_program_command(prog.name, curr_time)) continue; + // process all selected stations for(sid=0;sid>3; @@ -971,8 +967,11 @@ void do_loop() #endif } +#endif + + // handle reboot request // check safe_reboot condition - if (os.status.safe_reboot) { + if (os.status.safe_reboot && (curr_time > reboot_timer)) { // if no program is running at the moment if (!os.status.program_busy) { // and if no program is scheduled to run in the next minute @@ -988,8 +987,9 @@ void do_loop() os.reboot_dev(os.nvdata.reboot_cause); } } + } else if(reboot_timer && (curr_time > reboot_timer)) { + os.reboot_dev(REBOOT_CAUSE_TIMER); } -#endif // real-time flow count static ulong flowcount_rt_start = 0; @@ -1036,6 +1036,24 @@ void do_loop() #endif } +/** Check and process special program command */ +bool process_special_program_command(const char* pname, uint32_t curr_time) { + if(pname[0]==':') { // special command start with : + if(strncmp(pname, ":>reboot_now", 12) == 0) { + os.status.safe_reboot = 0; // reboot regardless of program status + reboot_timer = curr_time + 65; // set a timer to reboot in 65 seconds + // this is to avoid the same command being executed again right after reboot + return true; + } else if(strncmp(pname, ":>reboot", 8) == 0) { + os.status.safe_reboot = 1; // by default reboot should only happen when controller is idle + reboot_timer = curr_time + 65; // set a timer to reboot in 65 seconds + // this is to avoid the same command being executed again right after reboot + return true; + } + } + return false; +} + /** Make weather query */ void check_weather() { // do not check weather if diff --git a/mainArduino.ino b/mainArduino.ino index 318ee60b..1aa649f7 100644 --- a/mainArduino.ino +++ b/mainArduino.ino @@ -1,6 +1,7 @@ #include -#if defined(ESP8266) +//#if defined(ESP8266) +#if 0 struct tcp_pcb; extern struct tcp_pcb* tcp_tw_pcbs; extern "C" void tcp_abort (struct tcp_pcb* pcb); @@ -24,7 +25,8 @@ void setup() { void loop() { do_loop(); -#if defined(ESP8266) +//#if defined(ESP8266) +#if 0 tcpCleanup(); #endif } diff --git a/make.lin32 b/make.lin32 index 4406e204..ac514067 100644 --- a/make.lin32 +++ b/make.lin32 @@ -7,7 +7,7 @@ LIBS = . \ $(ESP_LIBS)/ESP8266mDNS \ ~/Arduino/libraries/SSD1306 \ ~/Arduino/libraries/rc-switch \ - ~/Arduino/libraries/UIPEthernet \ + ~/Arduino/libraries/EthernetENC \ ~/Arduino/libraries/pubsubclient \ ESP_ROOT = $(HOME)/esp8266_2.7.4/ @@ -17,7 +17,7 @@ BUILD_ROOT = /tmp/$(MAIN_NAME) UPLOAD_SPEED = 460800 UPLOAD_VERB = -v # for OS3.0 revision 1: reset mode is nodemcu -UPLOAD_RESET = nodemcu +#UPLOAD_RESET = nodemcu # Uncomment the line below for OS3.0 revision 0: reset mode is ck # UPLOAD_RESET = ck diff --git a/make.os23 b/make.os23 index cd2ba189..1acf2a25 100644 --- a/make.os23 +++ b/make.os23 @@ -1,6 +1,6 @@ OFLAG = -Os ARDMK_DIR = . -ARDUINO_DIR = $(HOME)/arduino-1.8.5 +ARDUINO_DIR = $(HOME)/arduino-1.8.15 ALTERNATE_CORE_PATH = $(HOME)/.arduino15/packages/MightyCore/hardware/avr/2.0.5 # If compiling on macOS, use the following instead #ARDUINO_DIR = $(HOME)/Documents/Arduino/libraries @@ -9,7 +9,7 @@ BOARD_TAG = 1284 MCU = atmega1284p VARIANT = sanguino F_CPU = 16000000L -ARDUINO_LIBS = UIPEthernet Wire SdFat SPI pubsubclient +ARDUINO_LIBS = EthernetENC Wire SdFat SPI pubsubclient MONITOR_PORT = /dev/ttyUSB0 MONITOR_BAUDRATE = 115200 include ./Arduino.mk diff --git a/makeEspArduino.mk b/makeEspArduino.mk index b09091e9..c494e603 100644 --- a/makeEspArduino.mk +++ b/makeEspArduino.mk @@ -7,35 +7,54 @@ # General and full license information is available at: # https://github.com/plerup/makeEspArduino # -# Copyright (c) 2016-2018 Peter Lerup. All rights reserved. +# Copyright (c) 2016-2021 Peter Lerup. All rights reserved. # #==================================================================================== -#==================================================================================== -# Project specific values -#==================================================================================== +START_TIME := $(shell date +%s) +__THIS_FILE := $(abspath $(lastword $(MAKEFILE_LIST))) +__TOOLS_DIR := $(dir $(__THIS_FILE))tools +OS ?= $(shell uname -s) + +# Include possible operating system specfic settings +-include $(dir $(__THIS_FILE))/os/$(OS).mk -# Include possible project makefile. This can be used to override the defaults below +# Include possible global user settings +CONFIG_ROOT ?= $(if $(XDG_CONFIG_HOME),$(XDG_CONFIG_HOME),$(HOME)/.config) +-include $(CONFIG_ROOT)/makeEspArduino/config.mk + +# Include possible project specific settings -include $(firstword $(PROJ_CONF) $(dir $(SKETCH))config.mk) -#=== Default values not available in the Arduino configuration files +# Build threads, default is using all the PC cpus +BUILD_THREADS ?= $(shell nproc) +MAKEFLAGS += -j $(BUILD_THREADS) -CHIP ?= esp8266 +# Build verbosity, silent by default +ifndef VERBOSE + MAKEFLAGS += --silent +endif -# Set chip specific default board unless specified -BOARD ?= $(if $(filter $(CHIP), esp32),esp32,generic) +# ESP chip family type +CHIP ?= esp8266 +UC_CHIP := $(shell perl -e "print uc $(CHIP)") +IS_ESP32 := $(if $(filter-out esp32,$(CHIP)),,1) # Serial flashing parameters -UPLOAD_PORT ?= $(shell ls -1tr /dev/tty*USB* 2>/dev/null | tail -1) -UPLOAD_PORT := $(if $(UPLOAD_PORT),$(UPLOAD_PORT),/dev/ttyS0) +UPLOAD_PORT_MATCH ?= /dev/ttyU* +UPLOAD_PORT ?= $(shell ls -1tr $(UPLOAD_PORT_MATCH) 2>/dev/null | tail -1) + +# Monitor definitions +MONITOR_SPEED ?= 115200 +MONITOR_PORT ?= $(UPLOAD_PORT) +MONITOR_PAR ?= --rts=0 --dtr=0 +MONITOR_COM ?= $(if $(NO_PY_WRAP),python3,$(PY_WRAP)) -m serial.tools.miniterm $(MONITOR_PAR) $(MONITOR_PORT) $(MONITOR_SPEED) # OTA parameters OTA_ADDR ?= -OTA_PORT ?= $(if $(filter $(CHIP), esp32),3232,8266) +OTA_PORT ?= $(if $(IS_ESP32),3232,8266) OTA_PWD ?= - OTA_ARGS = --progress --ip="$(OTA_ADDR)" --port="$(OTA_PORT)" - ifneq ($(OTA_PWD),) OTA_ARGS += --auth="$(OTA_PWD)" endif @@ -45,90 +64,88 @@ HTTP_ADDR ?= HTTP_URI ?= /update HTTP_PWD ?= user HTTP_USR ?= password +HTTP_OPT ?= --progress-bar -o /dev/null # Output directory BUILD_ROOT ?= /tmp/mkESP BUILD_DIR ?= $(BUILD_ROOT)/$(MAIN_NAME)_$(BOARD) -# File system source directory +# File system and corresponding disk directories +FS_TYPE ?= spiffs FS_DIR ?= $(dir $(SKETCH))data -FS_REST_DIR ?= $(BUILD_DIR)/file_system - -# Bootloader -BOOT_LOADER ?= $(ESP_ROOT)/bootloaders/eboot/eboot.elf - -#==================================================================================== -# Standard build logic and values -#==================================================================================== - -START_TIME := $(shell date +%s) -OS ?= $(shell uname -s) +FS_RESTORE_DIR ?= $(BUILD_DIR)/file_system # Utility functions git_description = $(shell git -C $(1) describe --tags --always --dirty 2>/dev/null || echo Unknown) time_string = $(shell date +$(1)) -ifeq ($(OS), Darwin) - find_files = $(shell find -E $2 -regex ".*\.($1)" | sed 's/\/\//\//') -else - find_files = $(shell find $2 -regextype posix-egrep -regex ".*\.($1)") -endif +find_files = $(shell find $2 | awk '/.*\.($1)$$/') # ESP Arduino directories ifndef ESP_ROOT # Location not defined, find and use possible version in the Arduino IDE installation - ifeq ($(OS), Windows_NT) - ARDUINO_ROOT = $(shell cygpath -m $(LOCALAPPDATA)/Arduino15) - else ifeq ($(OS), Darwin) - ARDUINO_ROOT = $(HOME)/Library/Arduino15 - else - ARDUINO_ROOT = $(HOME)/.arduino15 - endif + ARDUINO_ROOT ?= $(HOME)/.arduino15 ARDUINO_ESP_ROOT = $(ARDUINO_ROOT)/packages/$(CHIP) - ESP_ROOT := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/hardware/$(CHIP)/*)) + ESP_ROOT := $(if $(ARDUINO_HW_ESP_ROOT),$(ARDUINO_HW_ESP_ROOT),$(lastword $(wildcard $(ARDUINO_ESP_ROOT)/hardware/$(CHIP)/*))) ifeq ($(ESP_ROOT),) $(error No installed version of $(CHIP) Arduino found) endif - ARDUINO_LIBS = $(shell grep -o "sketchbook.path=.*" $(ARDUINO_ROOT)/preferences.txt 2>/dev/null | cut -f2- -d=)/libraries + ARDUINO_LIBS ?= $(shell grep -o "sketchbook.path=.*" $(ARDUINO_ROOT)/preferences.txt 2>/dev/null | cut -f2- -d=)/libraries ESP_ARDUINO_VERSION := $(notdir $(ESP_ROOT)) # Find used version of compiler and tools COMP_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/xtensa-*/*)) - ESPTOOL_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/esptool*/*)) - MKSPIFFS_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/mkspiffs/*/*)) + MK_FS_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/mk$(FS_TYPE)/*/mk$(FS_TYPE))) + PYTHON3_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/python3/*)) else - # Location defined, assume it is a git clone + # Location defined, assume that it is a git clone ESP_ARDUINO_VERSION = $(call git_description,$(ESP_ROOT)) - MKSPIFFS_PATH := $(lastword $(wildcard $(ESP_ROOT)/tools/mkspiffs/*)) + MK_FS_PATH := $(lastword $(wildcard $(ESP_ROOT)/tools/mk$(FS_TYPE)/mk$(FS_TYPE))) + PYTHON3_PATH := $(wildcard $(ESP_ROOT)/tools/python3) endif ESP_LIBS = $(ESP_ROOT)/libraries SDK_ROOT = $(ESP_ROOT)/tools/sdk TOOLS_ROOT = $(ESP_ROOT)/tools -ifeq ($(shell grep -o "$(BOARD).name" $(ESP_ROOT)/boards.txt 2>/dev/null),) - $(error Invalid board: $(BOARD)) -endif +# The esp8266 tools directory contains the python3 executable as well as some modules +# Use these to avoid additional python installation requirements here +PYTHON3_PATH := $(if $(PYTHON3_PATH),$(PYTHON3_PATH),$(dir $(shell which python3 2>/dev/null))) +PY_WRAP = $(PYTHON3_PATH)/python3 $(__TOOLS_DIR)/py_wrap.py $(TOOLS_ROOT) +NO_PY_WRAP ?= $(if $(IS_ESP32),1,) +# Validate the selected version of ESP Arduino ifeq ($(wildcard $(ESP_ROOT)/cores/$(CHIP)),) $(error $(ESP_ROOT) is not a vaild directory for $(CHIP)) endif -ESPTOOL ?= $(shell which esptool.py 2>/dev/null || which esptool 2>/dev/null) -ifneq ($(ESPTOOL),) - # esptool exists in path, overide defaults and use it for esp8266 flash operations - ifeq ($(CHIP),esp8266) - ESPTOOL_COM = $(ESPTOOL) - UPLOAD_COM = $(ESPTOOL_PATTERN) -a soft_reset write_flash 0x00000 $(BUILD_DIR)/$(MAIN_NAME).bin - FS_UPLOAD_COM = $(ESPTOOL_PATTERN) -a soft_reset write_flash $(SPIFFS_START) $(FS_IMAGE) - endif +# Set possible default board variant and validate +BOARD_OP = perl $(__TOOLS_DIR)/board_op.pl $(ESP_ROOT)/boards.txt "$(CPU)" +ifeq ($(BOARD),) + BOARD := $(if $(IS_ESP32),esp32,generic) +else ifeq ($(shell $(BOARD_OP) $(BOARD) check),) + $(error Invalid board: $(BOARD)) endif -ESPTOOL_PATTERN = echo Using: $(UPLOAD_PORT) @ $(UPLOAD_SPEED) && "$(ESPTOOL_COM)" --baud=$(UPLOAD_SPEED) --port $(UPLOAD_PORT) --chip $(CHIP) -ifeq ($(MAKECMDGOALS),help) - DEMO=1 +# Handle esptool variants +ESPTOOL_EXT = $(if $(IS_ESP32),,.py) +ESPTOOL ?= $(if $(NO_PY_WRAP),$(ESP_ROOT)/tools/esptool/esptool$(ESPTOOL_EXT),$(PY_WRAP) esptool) +ESPTOOL_COM ?= $(ESPTOOL) --baud=$(UPLOAD_SPEED) --port $(UPLOAD_PORT) --chip $(CHIP) +ifeq ($(IS_ESP32),) + # esp8266, use esptool directly instead of via tools/upload.py in order to avoid speed restrictions currently implied there + UPLOAD_COM = $(ESPTOOL_COM) $(UPLOAD_RESET) write_flash 0x00000 $(BUILD_DIR)/$(MAIN_NAME).bin + FS_UPLOAD_COM = $(ESPTOOL_COM) $(UPLOAD_RESET) write_flash $(SPIFFS_START) $(FS_IMAGE) +endif + +# Detect if the specified goal involves building or not +GOALS := $(if $(MAKECMDGOALS),$(MAKECMDGOALS),all) +BUILDING := $(if $(filter $(GOALS), monitor list_boards list_flash_defs list_lwip set_git_version install help tools_dir preproc info),,1) + +# Sketch (main program) selection +ifeq ($(BUILDING),) + SKETCH = /dev/null endif ifdef DEMO - SKETCH := $(if $(filter $(CHIP), esp32),$(ESP_LIBS)/WiFi/examples/WiFiScan/WiFiScan.ino,$(ESP_LIBS)/ESP8266WebServer/examples/HelloServer/HelloServer.ino) + SKETCH := $(if $(IS_ESP32),$(ESP_LIBS)/WiFi/examples/WiFiScan/WiFiScan.ino,$(ESP_LIBS)/ESP8266WiFi/examples/WiFiScan/WiFiScan.ino) endif -SKETCH ?= $(wildcard *.ino *.pde) +SKETCH ?= $(abspath $(wildcard *.ino *.pde)) ifeq ($(SKETCH),) $(error No sketch specified or found. Use "DEMO=1" for testing) endif @@ -141,24 +158,12 @@ SRC_GIT_VERSION := $(call git_description,$(dir $(SKETCH))) SKETCH_NAME := $(basename $(notdir $(SKETCH))) MAIN_NAME ?= $(SKETCH_NAME) MAIN_EXE ?= $(BUILD_DIR)/$(MAIN_NAME).bin -FS_IMAGE ?= $(BUILD_DIR)/FS.spiffs - -ifeq ($(OS), Windows_NT) - # Adjust some paths for cygwin - BUILD_DIR := $(shell cygpath -m $(BUILD_DIR)) - SKETCH := $(shell cygpath -m $(SKETCH)) - ifdef ARDUINO_LIBS - ARDUINO_LIBS := $(shell cygpath -m $(ARDUINO_LIBS)) - endif -endif +FS_IMAGE ?= $(BUILD_DIR)/FS.bin # Build file extensions OBJ_EXT = .o DEP_EXT = .d -# Auto generated makefile with Arduino definitions -ARDUINO_MK = $(BUILD_DIR)/arduino.mk - # Special tool definitions OTA_TOOL ?= python $(TOOLS_ROOT)/espota.py HTTP_TOOL ?= curl @@ -168,55 +173,39 @@ CORE_DIR = $(ESP_ROOT)/cores/$(CHIP) CORE_SRC := $(call find_files,S|c|cpp,$(CORE_DIR)) CORE_OBJ := $(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(CORE_SRC))) CORE_LIB = $(BUILD_DIR)/arduino.ar +USER_OBJ_LIB = $(BUILD_DIR)/user_obj.ar -SKETCH_DIR = $(dir $(SKETCH)) -# User defined compilation units and directories -ifeq ($(LIBS),) - # Automatically find directories with header files used by the sketch - LIBS := $(shell perl -e 'use File::Find;@d = split(" ", shift);while (<>) {$$f{"$$1"} = 1 if /^\s*\#include\s+[<"]([^>"]+)/;}find(sub {if ($$f{$$_}){print $$File::Find::dir," ";$$f{$$_}=0;}}, @d);' \ - "$(CUSTOM_LIBS) $(ESP_LIBS) $(ARDUINO_LIBS)" $(SKETCH) $(call find_files,S|c|cpp,$(SKETCH_DIR))) - ifneq ($(findstring /examples/,$(realpath $(SKETCH))),) - # Assume library example sketch, add the library directory unless it is an Arduino basic example - EX_LIB := $(shell perl -e 'print $$ARGV[0] if $$ARGV[0] =~ s/\/examples\/(?!\d\d\.).+//' $(realpath $(SKETCH))) - ifneq ($(EX_LIB),) - ifneq ($(wildcard $(EX_LIB)/src),) - # Library in src sub directory - EX_LIB := $(EX_LIB)/src - else - # Library at root. Avoid getting files from other examples - EXCLUDE_DIRS ?= $(EX_LIB)/examples - endif - LIBS += $(EX_LIB) - endif - endif -endif +# Find project specific source files and include directories +SRC_LIST = $(BUILD_DIR)/src_list.mk +FIND_SRC_CMD = $(__TOOLS_DIR)/find_src.pl +$(SRC_LIST): $(MAKEFILE_LIST) $(FIND_SRC_CMD) | $(BUILD_DIR) + $(if $(BUILDING),echo "- Finding all involved files for the build ...",) + perl $(FIND_SRC_CMD) "$(EXCLUDE_DIRS)" $(SKETCH) "$(CUSTOM_LIBS)" "$(LIBS)" $(ESP_LIBS) $(ARDUINO_LIBS) >$(SRC_LIST) + +-include $(SRC_LIST) -IGNORE_PATTERN := $(foreach dir,$(EXCLUDE_DIRS),$(dir)/%) -USER_INC := $(filter-out $(IGNORE_PATTERN),$(call find_files,h|hpp,$(SKETCH_DIR) $(dir $(LIBS)))) -USER_SRC := $(SKETCH) $(filter-out $(IGNORE_PATTERN),$(call find_files,S|c|cpp$(USER_SRC_PATTERN),$(SKETCH_DIR) $(LIBS))) -# Object file suffix seems to be significant for the linker... -USER_OBJ := $(subst .ino,_.cpp,$(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(USER_SRC)))) +# Use sketch copy with correct C++ extension +SKETCH_CPP = $(BUILD_DIR)/$(notdir $(SKETCH)).cpp +USER_SRC := $(subst $(SKETCH),$(SKETCH_CPP),$(USER_SRC)) + +USER_OBJ := $(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(USER_SRC))) USER_DIRS := $(sort $(dir $(USER_SRC))) -USER_INC_DIRS := $(sort $(dir $(USER_INC))) -USER_LIBS := $(filter-out $(IGNORE_PATTERN),$(call find_files,a,$(SKETCH_DIR) $(LIBS))) # Use first flash definition for the board as default -FLASH_DEF_MATCH = $(if $(filter $(CHIP), esp32),build\.flash_size=(\S+),menu\.(?:FlashSize|eesz)\.([^\.]+)=(.+)) -FLASH_DEF ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.$(FLASH_DEF_MATCH)/){ print "$$1"; exit;}}') +FLASH_DEF ?= $(shell $(BOARD_OP) $(BOARD) first_flash) # Same method for LwIPVariant -LWIP_VARIANT ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.menu\.(?:LwIPVariant|ip)\.([^\.=]+)=/){ print "$$1"; exit;}}') +LWIP_VARIANT ?= $(shell $(BOARD_OP) $(BOARD) first_lwip) # Handle possible changed state i.e. make command line parameters or changed git versions -ifeq ($(OS), Darwin) - CMD_LINE := $(shell ps $$PPID -o command | tail -1) -else - CMD_LINE := $(shell tr "\0" " " $(ARDUINO_MK) +ARDUINO_MK = $(BUILD_DIR)/arduino.mk +OS_NAME ?= linux +ARDUINO_DESC := $(shell find -L $(ESP_ROOT) -maxdepth 1 -name "*.txt" | sort) +$(ARDUINO_MK): $(ARDUINO_DESC) $(MAKEFILE_LIST) $(__TOOLS_DIR)/parse_arduino.pl | $(BUILD_DIR) + $(if $(BUILDING),echo "- Parsing Arduino configuration files ...",) + perl $(__TOOLS_DIR)/parse_arduino.pl $(BOARD) '$(FLASH_DEF)' '$(OS_NAME)' '$(LWIP_VARIANT)' $(ARDUINO_EXTRA_DESC) $(ARDUINO_DESC) >$(ARDUINO_MK) -include $(ARDUINO_MK) @@ -237,79 +229,103 @@ INCLUDE_DIRS += $(CORE_DIR) $(ESP_ROOT)/variants/$(INCLUDE_VARIANT) $(BUILD_DIR) C_INCLUDES := $(foreach dir,$(INCLUDE_DIRS) $(USER_INC_DIRS),-I$(dir)) VPATH += $(shell find $(CORE_DIR) -type d) $(USER_DIRS) -# Automatically generated build information data -# Makes the build date and git descriptions at the actual build event available as string constants in the program +# Automatically generated build information data source file +# Makes the build date and git descriptions at the time of actual build event +# available as string constants in the program BUILD_INFO_H = $(BUILD_DIR)/buildinfo.h BUILD_INFO_CPP = $(BUILD_DIR)/buildinfo.c++ BUILD_INFO_OBJ = $(BUILD_INFO_CPP)$(OBJ_EXT) +BUILD_DATE = $(call time_string,"%Y-%m-%d") +BUILD_TIME = $(call time_string,"%H:%M:%S") $(BUILD_INFO_H): | $(BUILD_DIR) - echo "typedef struct { const char *date, *time, *src_version, *env_version;} _tBuildInfo; extern _tBuildInfo _BuildInfo;" >$@ + @echo "typedef struct { const char *date, *time, *src_version, *env_version; } _tBuildInfo; extern _tBuildInfo _BuildInfo;" >$@ -# ccache? -ifeq ($(USE_CCACHE), 1) +# Use ccache if it is available and not explicitly disabled (USE_CCACHE=0) +USE_CCACHE ?= $(if $(shell which ccache 2>/dev/null),1,0) +ifeq ($(USE_CCACHE),1) C_COM_PREFIX = ccache CPP_COM_PREFIX = $(C_COM_PREFIX) endif -# Build rules for the different source file types -$(BUILD_DIR)/%.cpp$(OBJ_EXT): %.cpp $(BUILD_INFO_H) $(ARDUINO_MK) - echo $(" >$@ + cat $(abspath $<) >>$@ -$(BUILD_DIR)/%.pde$(OBJ_EXT): %.pde $(BUILD_INFO_H) $(ARDUINO_MK) - echo $(' >$(BUILD_INFO_CPP) - echo '_tBuildInfo _BuildInfo = {"$(BUILD_DATE)","$(BUILD_TIME)","$(SRC_GIT_VERSION)","$(ESP_ARDUINO_VERSION)"};' >>$(BUILD_INFO_CPP) +# Linking the executable +$(MAIN_EXE): $(CORE_LIB) $(USER_LIBS) $(USER_OBJ_DEP) + @echo Linking $(MAIN_EXE) + $(PRELINK) + @echo " Versions: $(SRC_GIT_VERSION), $(ESP_ARDUINO_VERSION)" + @echo '#include ' >$(BUILD_INFO_CPP) + @echo '_tBuildInfo _BuildInfo = {"$(BUILD_DATE)","$(BUILD_TIME)","$(SRC_GIT_VERSION)","$(ESP_ARDUINO_VERSION)"};' >>$(BUILD_INFO_CPP) $(CPP_COM) $(BUILD_INFO_CPP) -o $(BUILD_INFO_OBJ) $(LD_COM) $(LD_EXTRA) $(GEN_PART_COM) - $(ELF2BIN_COM) - $(SIZE_COM) | perl -e "$$MEM_USAGE" "$(MEM_FLASH)" "$(MEM_RAM)" + $(OBJCOPY) + $(SIZE_COM) | perl $(__TOOLS_DIR)/mem_use.pl "$(MEM_FLASH)" "$(MEM_RAM)" ifneq ($(LWIP_INFO),) - printf "LwIPVariant: $(LWIP_INFO)\n" + @printf "LwIPVariant: $(LWIP_INFO)\n" endif ifneq ($(FLASH_INFO),) - printf "Flash size: $(FLASH_INFO)\n\n" + @printf "Flash size: $(FLASH_INFO)\n\n" endif - perl -e 'print "Build complete. Elapsed time: ", time()-$(START_TIME), " seconds\n\n"' + @perl -e 'print "Build complete. Elapsed time: ", time()-$(START_TIME), " seconds\n\n"' +# Flashing operations +CHECK_PORT := $(if $(UPLOAD_PORT),\ + @echo === Using upload port: $(UPLOAD_PORT) @ $(UPLOAD_SPEED),\ + @echo "*** Upload port not found or defined" && exit 1) upload flash: all + $(CHECK_PORT) $(UPLOAD_COM) ota: all ifeq ($(OTA_ADDR),) - echo == Error: Address of device must be specified via OTA_ADDR + @echo == Error: Address of device must be specified via OTA_ADDR exit 1 endif $(OTA_PRE_COM) @@ -317,301 +333,237 @@ endif http: all ifeq ($(HTTP_ADDR),) - echo == Error: Address of device must be specified via HTTP_ADDR + @echo == Error: Address of device must be specified via HTTP_ADDR exit 1 endif - $(HTTP_TOOL) --verbose -F image=@$(MAIN_EXE) --user $(HTTP_USR):$(HTTP_PWD) http://$(HTTP_ADDR)$(HTTP_URI) - echo "\n" + $(HTTP_TOOL) $(HTTP_OPT) -F image=@$(MAIN_EXE) --user $(HTTP_USR):$(HTTP_PWD) http://$(HTTP_ADDR)$(HTTP_URI) + @echo "\n" -$(FS_IMAGE): $(ARDUINO_MK) $(wildcard $(FS_DIR)/*) - echo Generating filesystem image: $(FS_IMAGE) - $(MKSPIFFS_COM) +$(FS_IMAGE): $(ARDUINO_MK) $(shell find $(FS_DIR)/ 2>/dev/null) +ifeq ($(SPIFFS_SIZE),) + @echo == Error: No file system specified in FLASH_DEF + exit 1 +endif + @echo Generating file system image: $(FS_IMAGE) + $(MK_FS_COM) fs: $(FS_IMAGE) upload_fs flash_fs: $(FS_IMAGE) + $(CHECK_PORT) $(FS_UPLOAD_COM) ota_fs: $(FS_IMAGE) ifeq ($(OTA_ADDR),) - echo == Error: Address of device must be specified via OTA_ADDR + @echo == Error: Address of device must be specified via OTA_ADDR exit 1 endif $(OTA_TOOL) $(OTA_ARGS) --spiffs --file="$(FS_IMAGE)" run: flash - python -m serial.tools.miniterm --rts=0 --dtr=0 $(UPLOAD_PORT) 115200 + $(MONITOR_COM) + +monitor: +ifeq ($(MONITOR_PORT),) + @echo "*** Monitor port not found or defined" && exit 1 +endif + $(MONITOR_COM) FLASH_FILE ?= $(BUILD_DIR)/esp_flash.bin dump_flash: - echo Dumping flash memory to file: $(FLASH_FILE) - $(ESPTOOL_PATTERN) read_flash 0 $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";$$mem_size=$$1*1024;$$mem_size*=1024 if $$2 eq "M";print $$mem_size;' $(FLASH_DEF)) $(FLASH_FILE) + $(CHECK_PORT) + @echo Dumping flash memory to file: $(FLASH_FILE) + $(ESPTOOL_COM) read_flash 0 $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";$$mem_size=$$1*1024;$$mem_size*=1024 if $$2 eq "M";print $$mem_size;' $(FLASH_DEF)) $(FLASH_FILE) dump_fs: - echo Dumping flash file system to directory: $(FS_REST_DIR) - -$(ESPTOOL_PATTERN) read_flash $(SPIFFS_START) $(SPIFFS_SIZE) $(FS_IMAGE) - mkdir -p $(FS_REST_DIR) - echo - echo == Files == - $(RESTSPIFFS_COM) + $(CHECK_PORT) + @echo Dumping flash file system to directory: $(FS_RESTORE_DIR) + -$(ESPTOOL_COM) read_flash $(SPIFFS_START) $(SPIFFS_SIZE) $(FS_IMAGE) + mkdir -p $(FS_RESTORE_DIR) + @echo + @echo == Files == + $(RESTORE_FS_COM) restore_flash: - echo Restoring flash memory from file: $(FLASH_FILE) - $(ESPTOOL_PATTERN) -a soft_reset write_flash 0 $(FLASH_FILE) + $(CHECK_PORT) + @echo Restoring flash memory from file: $(FLASH_FILE) + $(ESPTOOL_COM) -a soft_reset write_flash 0 $(FLASH_FILE) erase_flash: - $(ESPTOOL_PATTERN) erase_flash + $(CHECK_PORT) + $(ESPTOOL_COM) erase_flash +# Building library instead of executable LIB_OUT_FILE ?= $(BUILD_DIR)/$(MAIN_NAME).a .PHONY: lib lib: $(LIB_OUT_FILE) -$(LIB_OUT_FILE): $(filter-out $(BUILD_DIR)/$(MAIN_NAME)_.cpp$(OBJ_EXT),$(USER_OBJ)) - echo Building library $(LIB_OUT_FILE) +$(LIB_OUT_FILE): $(filter-out $(BUILD_DIR)/$(MAIN_NAME).cpp$(OBJ_EXT),$(USER_OBJ)) + @echo Building library $(LIB_OUT_FILE) rm -f $(LIB_OUT_FILE) $(LIB_COM) cru $(LIB_OUT_FILE) $^ +# Miscellaneous operations clean: - echo Removing all build files + @echo Removing all build files rm -rf "$(BUILD_DIR)" $(FILES_TO_CLEAN) list_boards: - echo === Available boards === - cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^([\w\-]+)\.name=(.+)/){ print sprintf("%-20s %s\n", $$1,$$2);} }' + $(BOARD_OP) $(BOARD) list_names -list_lib: - echo === User specific libraries === - perl -e 'foreach (@ARGV) {print "$$_\n"}' "* Include directories:" $(USER_INC_DIRS) "* Library source files:" $(USER_SRC) +list_lib: $(SRC_LIST) + perl -e 'foreach (@ARGV) {print "$$_\n"}' "===== Include directories =====" $(USER_INC_DIRS) "===== Source files =====" $(USER_SRC) list_flash_defs: - echo === Memory configurations for board: $(BOARD) === - cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.$(FLASH_DEF_MATCH)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' + $(BOARD_OP) $(BOARD) list_flash list_lwip: - echo === lwip configurations for board: $(BOARD) === - cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.menu\.(?:LwIPVariant|ip)\.(\w+)=(.+)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' - -help: $(ARDUINO_MK) - echo - echo "Generic makefile for building Arduino esp8266 and esp32 projects" - echo "This file can either be used directly or included from another makefile" - echo "" - echo "The following targets are available:" - echo " all (default) Build the project application" - echo " clean Remove all intermediate build files" - echo " lib Build a library with all involved object files" - echo " flash Build and and flash the project application" - echo " flash_fs Build and and flash file system (when applicable)" - echo " ota Build and and flash via OTA" - echo " Params: OTA_ADDR, OTA_PORT and OTA_PWD" - echo " ota_fs Build and and flash file system via OTA" - echo " http Build and and flash via http (curl)" - echo " Params: HTTP_ADDR, HTTP_URI, HTTP_PWD and HTTP_USR" - echo " dump_flash Dump the whole board flash memory to a file" - echo " restore_flash Restore flash memory from a previously dumped file" - echo " dump_fs Extract all files from the flash file system" - echo " Params: FS_DUMP_DIR" - echo " erase_flash Erase the whole flash" - echo " list_lib Show a list of used library files and include paths" - echo "Configurable parameters:" - echo " SKETCH Main source file" - echo " If not specified the first sketch in current" - echo " directory will be used." - echo " LIBS Includes in the sketch file of libraries from within" - echo " the ESP Arduino directories are automatically" - echo " detected. If this is not enough, define this" - echo " variable with all libraries or directories needed." - echo " CHIP Set to esp8266 or esp32. Default: '$(CHIP)'" - echo " BOARD Name of the target board. Default: '$(BOARD)'" - echo " Use 'list_boards' to get list of available ones" - echo " FLASH_DEF Flash partitioning info. Default '$(FLASH_DEF)'" - echo " Use 'list_flash_defs' to get list of available ones" - echo " BUILD_DIR Directory for intermediate build files." - echo " Default '$(BUILD_DIR)'" - echo " BUILD_EXTRA_FLAGS Additional parameters for the compilation commands" - echo " FS_DIR File system root directory" - echo " UPLOAD_PORT Serial flashing port name. Default: '$(UPLOAD_PORT)'" - echo " UPLOAD_SPEED Serial flashing baud rate. Default: '$(UPLOAD_SPEED)'" - echo " FLASH_FILE File name for dump and restore flash operations" - echo " Default: '$(FLASH_FILE)'" - echo " LWIP_VARIANT Use specified variant of the lwip library when applicable" - echo " Use 'list_lwip' to get list of available ones" - echo " Default: $(LWIP_VARIANT) ($(LWIP_INFO))" - echo " VERBOSE Set to 1 to get full printout of the build" - echo " BUILD_THREADS Number of parallel build threads" - echo " Default: Maximum possible, based on number of CPUs" - echo " USE_CCACHE Set to 1 to use ccache in the build" - echo + $(BOARD_OP) $(BOARD) list_lwip -$(BUILD_DIR): - mkdir -p $(BUILD_DIR) +# Update the git version of the esp Arduino repo +set_git_version: +ifeq ($(REQ_GIT_VERSION),) + @echo == Error: Version tag must be specified via REQ_GIT_VERSION + exit 1 +endif + @echo == Setting $(ESP_ROOT) to $(REQ_GIT_VERSION) ... + git -C $(ESP_ROOT) checkout -fq --recurse-submodules $(REQ_GIT_VERSION) + git -C $(ESP_ROOT) clean -fdxq -f + git -C $(ESP_ROOT) submodule update --init + git -C $(ESP_ROOT) submodule foreach -q --recursive git clean -xfd + cd $(ESP_ROOT)/tools; ./get.py -q + +# Generate a Visual Studio Code configuration and launch +BIN_DIR = /usr/local/bin +_MAKE_COM = make -f $(__THIS_FILE) ESP_ROOT=$(ESP_ROOT) +ifeq ($(CHIP),esp32) + _MAKE_COM += CHIP=esp32 + _SCRIPT = espmake32 +else + _SCRIPT = espmake +endif +vscode: all + perl $(__TOOLS_DIR)/vscode.pl -n $(MAIN_NAME) -m "$(_MAKE_COM)" -w "$(VS_CODE_DIR)" -i "$(VSCODE_INC_EXTRA)" -p "$(VSCODE_PROJ_NAME)" $(CPP_COM) + +# Create shortcut command for running this file +install: + @echo Creating command \"$(_SCRIPT)\" in $(BIN_DIR) + sudo sh -c 'echo $(_MAKE_COM) "\"\$$@\"" >$(BIN_DIR)/$(_SCRIPT)' + sudo chmod +x $(BIN_DIR)/$(_SCRIPT) + +# Just return the path of the tools directory (intended to be used to find vscode.pl above from othe makefiles) +tools_dir: + @echo $(__TOOLS_DIR) + +# Show ram memory usage per variable +ram_usage: $(MAIN_EXE) + $(shell find $(TOOLS_ROOT) | grep 'gcc-nm') -Clrtd --size-sort $(BUILD_DIR)/$(MAIN_NAME).elf | grep -i ' [b] ' + +# Show ram and flash usage per object files used in the build +OBJ_INFO_FORM ?= 0 +OBJ_INFO_SORT ?= 1 +obj_info: $(MAIN_EXE) + perl $(__TOOLS_DIR)/obj_info.pl "$(shell find $(TOOLS_ROOT) | grep 'elf-size$$')" "$(OBJ_INFO_FORM)" "$(OBJ_INFO_SORT)" $(BUILD_DIR)/*.o + +# Analyze crash log +crash: $(MAIN_EXE) + perl $(__TOOLS_DIR)/crash_tool.pl $(ESP_ROOT) $(BUILD_DIR)/$(MAIN_NAME).elf + +# Run compiler preprocessor to get full expanded source for a file +preproc: +ifeq ($(SRC_FILE),) + $(error SRC_FILE must be defined) +endif + $(CPP_COM) -E $(SRC_FILE) +# Main default rule, build the executable .PHONY: all -all: $(BUILD_DIR) $(ARDUINO_MK) $(BUILD_INFO_H) prebuild $(MAIN_EXE) +all: $(BUILD_DIR) $(ARDUINO_MK) prebuild $(MAIN_EXE) +# Prebuild is currently only mandatory for esp32 +USE_PREBUILD ?= $(if $(IS_ESP32),1,) prebuild: -ifdef USE_PREBUILD - $(CORE_PREBUILD) +ifneq ($(USE_PREBUILD),) + $(PREBUILD) endif - $(SKETCH_PREBUILD) -# Include all available dependencies +help: $(ARDUINO_MK) + @echo + @echo "Generic makefile for building Arduino esp8266 and esp32 projects" + @echo "This file can either be used directly or included from another makefile" + @echo "" + @echo "The following targets are available:" + @echo " all (default) Build the project application" + @echo " clean Remove all intermediate build files" + @echo " lib Build a library with all involved object files" + @echo " flash Build and and flash the project application" + @echo " flash_fs Build and and flash file system (when applicable)" + @echo " ota Build and and flash via OTA" + @echo " Params: OTA_ADDR, OTA_PORT and OTA_PWD" + @echo " ota_fs Build and and flash file system via OTA" + @echo " http Build and and flash via http (curl)" + @echo " Params: HTTP_ADDR, HTTP_URI, HTTP_PWD and HTTP_USR" + @echo " dump_flash Dump the whole board flash memory to a file" + @echo " restore_flash Restore flash memory from a previously dumped file" + @echo " dump_fs Extract all files from the flash file system" + @echo " Params: FS_DUMP_DIR" + @echo " erase_flash Erase the whole flash (use with care!)" + @echo " list_lib Show a list of used solurce files and include directories" + @echo " set_git_version Setup ESP Arduino git repo to a the tag version" + @echo " specified via REQ_GIT_VERSION" + @echo " install Create the commands \"espmake\" and \"espmake32\"" + @echo " vscode Create config file for Visual Studio Code and launch" + @echo " ram_usage Show global variables RAM usage" + @echo " obj_info Show memory usage per object file" + @echo " monitor Start serial monitor on the upload port" + @echo " run Build flash and start serial monitor" + @echo " crash Analyze stack trace from a crash" + @echo " preproc Run compiler preprocessor on source file" + @echo " specified via SRC_FILE" + @echo " info Show location and version of used esp Arduino" + @echo "Configurable parameters:" + @echo " SKETCH Main source file" + @echo " If not specified the first sketch in current" + @echo " directory will be used." + @echo " LIBS Use this variable to declare additional directories" + @echo " and/or files which should be included in the build" + @echo " CHIP Set to esp8266 or esp32. Default: '$(CHIP)'" + @echo " BOARD Name of the target board. Default: '$(BOARD)'" + @echo " Use 'list_boards' to get list of available ones" + @echo " FLASH_DEF Flash partitioning info. Default '$(FLASH_DEF)'" + @echo " Use 'list_flash_defs' to get list of available ones" + @echo " BUILD_DIR Directory for intermediate build files." + @echo " Default '$(BUILD_DIR)'" + @echo " BUILD_EXTRA_FLAGS Additional parameters for the compilation commands" + @echo " COMP_WARNINGS Compilation warning options. Default: $(COMP_WARNINGS)" + @echo " FS_TYPE File system type. Default: $(FS_TYPE)" + @echo " FS_DIR File system root directory" + @echo " UPLOAD_PORT Serial flashing port name. Default: '$(UPLOAD_PORT)'" + @echo " UPLOAD_SPEED Serial flashing baud rate. Default: '$(UPLOAD_SPEED)'" + @echo " MONITOR_SPEED Baud rate for the monitor. Default: '$(MONITOR_SPEED)'" + @echo " FLASH_FILE File name for dump and restore flash operations" + @echo " Default: '$(FLASH_FILE)'" + @echo " LWIP_VARIANT Use specified variant of the lwip library when applicable" + @echo " Use 'list_lwip' to get list of available ones" + @echo " Default: $(LWIP_VARIANT) ($(LWIP_INFO))" + @echo " VERBOSE Set to 1 to get full printout of the build" + @echo " BUILD_THREADS Number of parallel build threads" + @echo " Default: Maximum possible, based on number of CPUs" + @echo " USE_CCACHE Set to 0 to disable ccache when it is available" + @echo " NO_USER_OBJ_LIB Set to 1 to disable putting all object files into an archive" + @echo + +# Show installation information +info: + echo == Build info + echo " CHIP: $(CHIP)" + echo " ESP_ROOT: $(ESP_ROOT)" + echo " Version: $(ESP_ARDUINO_VERSION)" + echo " Threads: $(BUILD_THREADS)" + echo " Upload port: $(UPLOAD_PORT)" + +# Include all available dependencies from the previous compilation -include $(wildcard $(BUILD_DIR)/*$(DEP_EXT)) DEFAULT_GOAL ?= all .DEFAULT_GOAL := $(DEFAULT_GOAL) -ifeq ($(OS), Darwin) - BUILD_THREADS ?= $(shell sysctl -n hw.ncpu) -else - BUILD_THREADS ?= $(shell nproc) -endif -MAKEFLAGS += -j $(BUILD_THREADS) - -ifndef VERBOSE - # Set silent mode as default - MAKEFLAGS += --silent -endif - -# Inline Perl scripts - -# Parse Arduino definitions and build commands from the descriptions -define PARSE_ARDUINO -my $$board = shift; -my $$flashSize = shift; -my $$os = shift; -$$os =~ s/Windows_NT/windows/; -$$os =~ s/Linux/linux/; -$$os =~ s/Darwin/macosx/; -my $$lwipvariant = shift; -my %v; - -sub def_var { - my ($$name, $$var) = @_; - print "$$var ?= $$v{$$name}\n"; - $$v{$$name} = "\$$($$var)"; -} - -$$v{'runtime.platform.path'} = '$$(ESP_ROOT)'; -$$v{'includes'} = '$$(C_INCLUDES)'; -$$v{'runtime.ide.version'} = '10605'; -$$v{'build.arch'} = uc('$(CHIP)'); -$$v{'build.project_name'} = '$$(MAIN_NAME)'; -$$v{'build.path'} = '$$(BUILD_DIR)'; -$$v{'object_files'} = '$$^ $$(BUILD_INFO_OBJ)'; -$$v{'archive_file_path'} = '$$(CORE_LIB)'; - -foreach my $$fn (@ARGV) { - open($$f, $$fn) || die "Failed to open: $$fn\n"; - while (<$$f>) { - s/\s+$$//; - s/\.esptool_py\./.esptool./g; - next unless /^(\w[\w\-\.]+)=(.*)/; - my ($$key, $$val) =($$1, $$2); - $$board_defined = 1 if $$key eq "$$board.name"; - $$key =~ s/$$board\.menu\.(?:FlashSize|eesz)\.$$flashSize\.//; - $$key =~ s/$$board\.menu\.CpuFrequency\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.(?:FlashFreq|xtal)\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.UploadSpeed\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.baud\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.ResetMethod\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.FlashMode\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.(?:LwIPVariant|ip)\.$$lwipvariant\.//; - $$key =~ s/^$$board\.//; - $$v{$$key} ||= $$val; - $$v{$$1} = $$v{$$key} if $$key =~ /(.+)\.$$os$$/; - } - close($$f); -} -$$v{'runtime.tools.xtensa-lx106-elf-gcc.path'} ||= '$$(COMP_PATH)'; -$$v{'runtime.tools.xtensa-esp32-elf-gcc.path'} ||= '$$(COMP_PATH)'; -$$v{'runtime.tools.esptool.path'} ||= '$$(ESPTOOL_PATH)'; - -die "* Unknown board $$board\n" unless $$board_defined; -print "# Board definitions\n"; -def_var('build.code_debug', 'CORE_DEBUG_LEVEL'); -def_var('build.f_cpu', 'F_CPU'); -def_var('build.flash_mode', 'FLASH_MODE'); -def_var('build.flash_freq', 'FLASH_SPEED'); -def_var('upload.resetmethod', 'UPLOAD_RESET'); -def_var('upload.speed', 'UPLOAD_SPEED'); -def_var('compiler.warning_flags', 'COMP_WARNINGS'); -$$v{'serial.port'} = '$$(UPLOAD_PORT)'; -$$v{'recipe.objcopy.hex.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; -$$v{'recipe.objcopy.hex.1.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; -$$v{'recipe.hooks.linking.prelink.1.pattern'} =~ s/\{build.vtable_flags\}/\$$(VTABLE_FLAGS)/; -$$v{'tools.esptool.upload.pattern'} =~ s/\{(cmd|path)\}/\{tools.esptool.$$1\}/g; -$$v{'compiler.cpreprocessor.flags'} .= " \$$(C_PRE_PROC_FLAGS)"; -$$v{'build.extra_flags'} .= " \$$(BUILD_EXTRA_FLAGS)"; - -foreach my $$key (sort keys %v) { - while ($$v{$$key} =~/\{/) { - $$v{$$key} =~ s/\{([\w\-\.]+)\}/$$v{$$1}/; - $$v{$$key} =~ s/""//; - } - $$v{$$key} =~ s/ -o\s+$$//; - $$v{$$key} =~ s/(-D\w+=)"([^"]+)"/$$1\\"$$2\\"/g; -} - -print "INCLUDE_VARIANT = $$v{'build.variant'}\n"; -print "# Commands\n"; -print "C_COM=\$$(C_COM_PREFIX) $$v{'recipe.c.o.pattern'}\n"; -print "CPP_COM=\$$(CPP_COM_PREFIX) $$v{'recipe.cpp.o.pattern'}\n"; -print "S_COM=$$v{'recipe.S.o.pattern'}\n"; -print "LIB_COM=\"$$v{'compiler.path'}$$v{'compiler.ar.cmd'}\"\n"; -print "CORE_LIB_COM=$$v{'recipe.ar.pattern'}\n"; -print "LD_COM=$$v{'recipe.c.combine.pattern'}\n"; -print "PART_FILE?=$$1\n" if $$v{'recipe.objcopy.eep.pattern'} =~ /\"([^\"]+\.csv)\"/; -$$v{'recipe.objcopy.eep.pattern'} =~ s/\"([^\"]+\.csv)\"/\$$(PART_FILE)/; -print "GEN_PART_COM=$$v{'recipe.objcopy.eep.pattern'}\n"; -print "ELF2BIN_COM=", $$v{'recipe.objcopy.hex.pattern'} || $$v{'recipe.objcopy.hex.1.pattern'}, "\n"; -print "SIZE_COM=$$v{'recipe.size.pattern'}\n"; -print "ESPTOOL_COM?=$$v{'tools.esptool.path'}/$$v{'tools.esptool.cmd'}\n"; -print "UPLOAD_COM?=$$v{'tools.esptool.upload.pattern'}\n"; - -if ($$v{'build.spiffs_start'}) { - print "SPIFFS_START?=$$v{'build.spiffs_start'}\n"; - my $$spiffs_size = sprintf("0x%X", hex($$v{'build.spiffs_end'})-hex($$v{'build.spiffs_start'})); - print "SPIFFS_SIZE?=$$spiffs_size\n"; -} elsif ($$v{'build.partitions'}) { - print "COMMA=,\n"; - print "SPIFFS_SPEC:=\$$(subst \$$(COMMA), ,\$$(shell grep spiffs \$$(PART_FILE)))\n"; - print "SPIFFS_START:=\$$(word 4,\$$(SPIFFS_SPEC))\n"; - print "SPIFFS_SIZE:=\$$(word 5,\$$(SPIFFS_SPEC))\n"; -} -$$v{'build.spiffs_blocksize'} ||= "4096"; -print "SPIFFS_BLOCK_SIZE?=$$v{'build.spiffs_blocksize'}\n"; -print "MKSPIFFS_COM?=\"\$$(MKSPIFFS_PATH)\" -b \$$(SPIFFS_BLOCK_SIZE) -s \$$(SPIFFS_SIZE) -c \$$(FS_DIR) \$$(FS_IMAGE)\n"; -print "RESTSPIFFS_COM?=\"\$$(MKSPIFFS_PATH)\" -b \$$(SPIFFS_BLOCK_SIZE) -s \$$(SPIFFS_SIZE) -u \$$(FS_REST_DIR) \$$(FS_IMAGE)\n"; - -my $$fs_upload_com = $$v{'tools.esptool.upload.pattern'}; -$$fs_upload_com =~ s/(.+ -ca) .+/$$1 \$$(SPIFFS_START) -cf \$$(FS_IMAGE)/; -$$fs_upload_com =~ s/(.+ --flash_size detect) .+/$$1 \$$(SPIFFS_START) \$$(FS_IMAGE)/; -print "FS_UPLOAD_COM?=$$fs_upload_com\n"; -my $$val = $$v{'recipe.hooks.core.prebuild.1.pattern'}; -$$val =~ s/bash -c "(.+)"/$$1/; -$$val =~ s/(#define .+0x)(\`)/"\\$$1\"$$2/; -$$val =~ s/(\\)//; -print "CORE_PREBUILD=$$val\n"; -print "SKETCH_PREBUILD=$$v{'recipe.hooks.sketch.prebuild.1.pattern'}\n"; -print "VTABLE_FLAGS?=$$v{'build.vtable_flags'}\n"; -print "LINK_PREBUILD=$$v{'recipe.hooks.linking.prelink.1.pattern'}\n"; -print "MEM_FLASH=$$v{'recipe.size.regex'}\n"; -print "MEM_RAM=$$v{'recipe.size.regex.data'}\n"; -$$flash_info = $$v{'menu.FlashSize.' . $$flashSize} || $$v{'menu.eesz.' . $$flashSize}; -print "FLASH_INFO=$$flash_info\n"; -print "LWIP_INFO=", $$v{'menu.LwIPVariant.' . $$lwipvariant} || $$v{'menu.ip.' . $$lwipvariant}, "\n"; -endef -export PARSE_ARDUINO - -# Convert memory information -define MEM_USAGE -$$fp = shift; -$$rp = shift; -while (<>) { - $$r += $$1 if /$$rp/; - $$f += $$1 if /$$fp/; -} -print "\nMemory usage\n"; -print sprintf(" %-6s %6d bytes\n" x 2 ."\n", "Ram:", $$r, "Flash:", $$f); -endef -export MEM_USAGE diff --git a/makeEspArduino.mk.old b/makeEspArduino.mk.old new file mode 100644 index 00000000..b09091e9 --- /dev/null +++ b/makeEspArduino.mk.old @@ -0,0 +1,617 @@ +#==================================================================================== +# makeESPArduino +# +# A makefile for ESP8286 and ESP32 Arduino projects. +# +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2016-2018 Peter Lerup. All rights reserved. +# +#==================================================================================== + +#==================================================================================== +# Project specific values +#==================================================================================== + +# Include possible project makefile. This can be used to override the defaults below +-include $(firstword $(PROJ_CONF) $(dir $(SKETCH))config.mk) + +#=== Default values not available in the Arduino configuration files + +CHIP ?= esp8266 + +# Set chip specific default board unless specified +BOARD ?= $(if $(filter $(CHIP), esp32),esp32,generic) + +# Serial flashing parameters +UPLOAD_PORT ?= $(shell ls -1tr /dev/tty*USB* 2>/dev/null | tail -1) +UPLOAD_PORT := $(if $(UPLOAD_PORT),$(UPLOAD_PORT),/dev/ttyS0) + +# OTA parameters +OTA_ADDR ?= +OTA_PORT ?= $(if $(filter $(CHIP), esp32),3232,8266) +OTA_PWD ?= + +OTA_ARGS = --progress --ip="$(OTA_ADDR)" --port="$(OTA_PORT)" + +ifneq ($(OTA_PWD),) + OTA_ARGS += --auth="$(OTA_PWD)" +endif + +# HTTP update parameters +HTTP_ADDR ?= +HTTP_URI ?= /update +HTTP_PWD ?= user +HTTP_USR ?= password + +# Output directory +BUILD_ROOT ?= /tmp/mkESP +BUILD_DIR ?= $(BUILD_ROOT)/$(MAIN_NAME)_$(BOARD) + +# File system source directory +FS_DIR ?= $(dir $(SKETCH))data +FS_REST_DIR ?= $(BUILD_DIR)/file_system + +# Bootloader +BOOT_LOADER ?= $(ESP_ROOT)/bootloaders/eboot/eboot.elf + +#==================================================================================== +# Standard build logic and values +#==================================================================================== + +START_TIME := $(shell date +%s) +OS ?= $(shell uname -s) + +# Utility functions +git_description = $(shell git -C $(1) describe --tags --always --dirty 2>/dev/null || echo Unknown) +time_string = $(shell date +$(1)) +ifeq ($(OS), Darwin) + find_files = $(shell find -E $2 -regex ".*\.($1)" | sed 's/\/\//\//') +else + find_files = $(shell find $2 -regextype posix-egrep -regex ".*\.($1)") +endif + +# ESP Arduino directories +ifndef ESP_ROOT + # Location not defined, find and use possible version in the Arduino IDE installation + ifeq ($(OS), Windows_NT) + ARDUINO_ROOT = $(shell cygpath -m $(LOCALAPPDATA)/Arduino15) + else ifeq ($(OS), Darwin) + ARDUINO_ROOT = $(HOME)/Library/Arduino15 + else + ARDUINO_ROOT = $(HOME)/.arduino15 + endif + ARDUINO_ESP_ROOT = $(ARDUINO_ROOT)/packages/$(CHIP) + ESP_ROOT := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/hardware/$(CHIP)/*)) + ifeq ($(ESP_ROOT),) + $(error No installed version of $(CHIP) Arduino found) + endif + ARDUINO_LIBS = $(shell grep -o "sketchbook.path=.*" $(ARDUINO_ROOT)/preferences.txt 2>/dev/null | cut -f2- -d=)/libraries + ESP_ARDUINO_VERSION := $(notdir $(ESP_ROOT)) + # Find used version of compiler and tools + COMP_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/xtensa-*/*)) + ESPTOOL_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/esptool*/*)) + MKSPIFFS_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/mkspiffs/*/*)) +else + # Location defined, assume it is a git clone + ESP_ARDUINO_VERSION = $(call git_description,$(ESP_ROOT)) + MKSPIFFS_PATH := $(lastword $(wildcard $(ESP_ROOT)/tools/mkspiffs/*)) +endif +ESP_LIBS = $(ESP_ROOT)/libraries +SDK_ROOT = $(ESP_ROOT)/tools/sdk +TOOLS_ROOT = $(ESP_ROOT)/tools + +ifeq ($(shell grep -o "$(BOARD).name" $(ESP_ROOT)/boards.txt 2>/dev/null),) + $(error Invalid board: $(BOARD)) +endif + +ifeq ($(wildcard $(ESP_ROOT)/cores/$(CHIP)),) + $(error $(ESP_ROOT) is not a vaild directory for $(CHIP)) +endif + +ESPTOOL ?= $(shell which esptool.py 2>/dev/null || which esptool 2>/dev/null) +ifneq ($(ESPTOOL),) + # esptool exists in path, overide defaults and use it for esp8266 flash operations + ifeq ($(CHIP),esp8266) + ESPTOOL_COM = $(ESPTOOL) + UPLOAD_COM = $(ESPTOOL_PATTERN) -a soft_reset write_flash 0x00000 $(BUILD_DIR)/$(MAIN_NAME).bin + FS_UPLOAD_COM = $(ESPTOOL_PATTERN) -a soft_reset write_flash $(SPIFFS_START) $(FS_IMAGE) + endif +endif +ESPTOOL_PATTERN = echo Using: $(UPLOAD_PORT) @ $(UPLOAD_SPEED) && "$(ESPTOOL_COM)" --baud=$(UPLOAD_SPEED) --port $(UPLOAD_PORT) --chip $(CHIP) + +ifeq ($(MAKECMDGOALS),help) + DEMO=1 +endif +ifdef DEMO + SKETCH := $(if $(filter $(CHIP), esp32),$(ESP_LIBS)/WiFi/examples/WiFiScan/WiFiScan.ino,$(ESP_LIBS)/ESP8266WebServer/examples/HelloServer/HelloServer.ino) +endif +SKETCH ?= $(wildcard *.ino *.pde) +ifeq ($(SKETCH),) + $(error No sketch specified or found. Use "DEMO=1" for testing) +endif +ifeq ($(wildcard $(SKETCH)),) + $(error Sketch $(SKETCH) not found) +endif +SRC_GIT_VERSION := $(call git_description,$(dir $(SKETCH))) + +# Main output definitions +SKETCH_NAME := $(basename $(notdir $(SKETCH))) +MAIN_NAME ?= $(SKETCH_NAME) +MAIN_EXE ?= $(BUILD_DIR)/$(MAIN_NAME).bin +FS_IMAGE ?= $(BUILD_DIR)/FS.spiffs + +ifeq ($(OS), Windows_NT) + # Adjust some paths for cygwin + BUILD_DIR := $(shell cygpath -m $(BUILD_DIR)) + SKETCH := $(shell cygpath -m $(SKETCH)) + ifdef ARDUINO_LIBS + ARDUINO_LIBS := $(shell cygpath -m $(ARDUINO_LIBS)) + endif +endif + +# Build file extensions +OBJ_EXT = .o +DEP_EXT = .d + +# Auto generated makefile with Arduino definitions +ARDUINO_MK = $(BUILD_DIR)/arduino.mk + +# Special tool definitions +OTA_TOOL ?= python $(TOOLS_ROOT)/espota.py +HTTP_TOOL ?= curl + +# Core source files +CORE_DIR = $(ESP_ROOT)/cores/$(CHIP) +CORE_SRC := $(call find_files,S|c|cpp,$(CORE_DIR)) +CORE_OBJ := $(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(CORE_SRC))) +CORE_LIB = $(BUILD_DIR)/arduino.ar + +SKETCH_DIR = $(dir $(SKETCH)) +# User defined compilation units and directories +ifeq ($(LIBS),) + # Automatically find directories with header files used by the sketch + LIBS := $(shell perl -e 'use File::Find;@d = split(" ", shift);while (<>) {$$f{"$$1"} = 1 if /^\s*\#include\s+[<"]([^>"]+)/;}find(sub {if ($$f{$$_}){print $$File::Find::dir," ";$$f{$$_}=0;}}, @d);' \ + "$(CUSTOM_LIBS) $(ESP_LIBS) $(ARDUINO_LIBS)" $(SKETCH) $(call find_files,S|c|cpp,$(SKETCH_DIR))) + ifneq ($(findstring /examples/,$(realpath $(SKETCH))),) + # Assume library example sketch, add the library directory unless it is an Arduino basic example + EX_LIB := $(shell perl -e 'print $$ARGV[0] if $$ARGV[0] =~ s/\/examples\/(?!\d\d\.).+//' $(realpath $(SKETCH))) + ifneq ($(EX_LIB),) + ifneq ($(wildcard $(EX_LIB)/src),) + # Library in src sub directory + EX_LIB := $(EX_LIB)/src + else + # Library at root. Avoid getting files from other examples + EXCLUDE_DIRS ?= $(EX_LIB)/examples + endif + LIBS += $(EX_LIB) + endif + endif +endif + +IGNORE_PATTERN := $(foreach dir,$(EXCLUDE_DIRS),$(dir)/%) +USER_INC := $(filter-out $(IGNORE_PATTERN),$(call find_files,h|hpp,$(SKETCH_DIR) $(dir $(LIBS)))) +USER_SRC := $(SKETCH) $(filter-out $(IGNORE_PATTERN),$(call find_files,S|c|cpp$(USER_SRC_PATTERN),$(SKETCH_DIR) $(LIBS))) +# Object file suffix seems to be significant for the linker... +USER_OBJ := $(subst .ino,_.cpp,$(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(USER_SRC)))) +USER_DIRS := $(sort $(dir $(USER_SRC))) +USER_INC_DIRS := $(sort $(dir $(USER_INC))) +USER_LIBS := $(filter-out $(IGNORE_PATTERN),$(call find_files,a,$(SKETCH_DIR) $(LIBS))) + +# Use first flash definition for the board as default +FLASH_DEF_MATCH = $(if $(filter $(CHIP), esp32),build\.flash_size=(\S+),menu\.(?:FlashSize|eesz)\.([^\.]+)=(.+)) +FLASH_DEF ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.$(FLASH_DEF_MATCH)/){ print "$$1"; exit;}}') +# Same method for LwIPVariant +LWIP_VARIANT ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.menu\.(?:LwIPVariant|ip)\.([^\.=]+)=/){ print "$$1"; exit;}}') + +# Handle possible changed state i.e. make command line parameters or changed git versions +ifeq ($(OS), Darwin) + CMD_LINE := $(shell ps $$PPID -o command | tail -1) +else + CMD_LINE := $(shell tr "\0" " " $(STATE_LOG)) +endif + +# The actual build commands are to be extracted from the Arduino description files +ARDUINO_DESC := $(shell find $(ESP_ROOT) -maxdepth 1 -name "*.txt" | sort) +$(ARDUINO_MK): $(ARDUINO_DESC) $(MAKEFILE_LIST) | $(BUILD_DIR) + perl -e "$$PARSE_ARDUINO" $(BOARD) '$(FLASH_DEF)' '$(OS)' '$(LWIP_VARIANT)' $(ARDUINO_EXTRA_DESC) $(ARDUINO_DESC) >$(ARDUINO_MK) + +-include $(ARDUINO_MK) + +# Compilation directories and path +INCLUDE_DIRS += $(CORE_DIR) $(ESP_ROOT)/variants/$(INCLUDE_VARIANT) $(BUILD_DIR) +C_INCLUDES := $(foreach dir,$(INCLUDE_DIRS) $(USER_INC_DIRS),-I$(dir)) +VPATH += $(shell find $(CORE_DIR) -type d) $(USER_DIRS) + +# Automatically generated build information data +# Makes the build date and git descriptions at the actual build event available as string constants in the program +BUILD_INFO_H = $(BUILD_DIR)/buildinfo.h +BUILD_INFO_CPP = $(BUILD_DIR)/buildinfo.c++ +BUILD_INFO_OBJ = $(BUILD_INFO_CPP)$(OBJ_EXT) + +$(BUILD_INFO_H): | $(BUILD_DIR) + echo "typedef struct { const char *date, *time, *src_version, *env_version;} _tBuildInfo; extern _tBuildInfo _BuildInfo;" >$@ + +# ccache? +ifeq ($(USE_CCACHE), 1) + C_COM_PREFIX = ccache + CPP_COM_PREFIX = $(C_COM_PREFIX) +endif + +# Build rules for the different source file types +$(BUILD_DIR)/%.cpp$(OBJ_EXT): %.cpp $(BUILD_INFO_H) $(ARDUINO_MK) + echo $(' >$(BUILD_INFO_CPP) + echo '_tBuildInfo _BuildInfo = {"$(BUILD_DATE)","$(BUILD_TIME)","$(SRC_GIT_VERSION)","$(ESP_ARDUINO_VERSION)"};' >>$(BUILD_INFO_CPP) + $(CPP_COM) $(BUILD_INFO_CPP) -o $(BUILD_INFO_OBJ) + $(LD_COM) $(LD_EXTRA) + $(GEN_PART_COM) + $(ELF2BIN_COM) + $(SIZE_COM) | perl -e "$$MEM_USAGE" "$(MEM_FLASH)" "$(MEM_RAM)" +ifneq ($(LWIP_INFO),) + printf "LwIPVariant: $(LWIP_INFO)\n" +endif +ifneq ($(FLASH_INFO),) + printf "Flash size: $(FLASH_INFO)\n\n" +endif + perl -e 'print "Build complete. Elapsed time: ", time()-$(START_TIME), " seconds\n\n"' + +upload flash: all + $(UPLOAD_COM) + +ota: all +ifeq ($(OTA_ADDR),) + echo == Error: Address of device must be specified via OTA_ADDR + exit 1 +endif + $(OTA_PRE_COM) + $(OTA_TOOL) $(OTA_ARGS) --file="$(MAIN_EXE)" + +http: all +ifeq ($(HTTP_ADDR),) + echo == Error: Address of device must be specified via HTTP_ADDR + exit 1 +endif + $(HTTP_TOOL) --verbose -F image=@$(MAIN_EXE) --user $(HTTP_USR):$(HTTP_PWD) http://$(HTTP_ADDR)$(HTTP_URI) + echo "\n" + +$(FS_IMAGE): $(ARDUINO_MK) $(wildcard $(FS_DIR)/*) + echo Generating filesystem image: $(FS_IMAGE) + $(MKSPIFFS_COM) + +fs: $(FS_IMAGE) + +upload_fs flash_fs: $(FS_IMAGE) + $(FS_UPLOAD_COM) + +ota_fs: $(FS_IMAGE) +ifeq ($(OTA_ADDR),) + echo == Error: Address of device must be specified via OTA_ADDR + exit 1 +endif + $(OTA_TOOL) $(OTA_ARGS) --spiffs --file="$(FS_IMAGE)" + +run: flash + python -m serial.tools.miniterm --rts=0 --dtr=0 $(UPLOAD_PORT) 115200 + +FLASH_FILE ?= $(BUILD_DIR)/esp_flash.bin +dump_flash: + echo Dumping flash memory to file: $(FLASH_FILE) + $(ESPTOOL_PATTERN) read_flash 0 $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";$$mem_size=$$1*1024;$$mem_size*=1024 if $$2 eq "M";print $$mem_size;' $(FLASH_DEF)) $(FLASH_FILE) + +dump_fs: + echo Dumping flash file system to directory: $(FS_REST_DIR) + -$(ESPTOOL_PATTERN) read_flash $(SPIFFS_START) $(SPIFFS_SIZE) $(FS_IMAGE) + mkdir -p $(FS_REST_DIR) + echo + echo == Files == + $(RESTSPIFFS_COM) + +restore_flash: + echo Restoring flash memory from file: $(FLASH_FILE) + $(ESPTOOL_PATTERN) -a soft_reset write_flash 0 $(FLASH_FILE) + +erase_flash: + $(ESPTOOL_PATTERN) erase_flash + +LIB_OUT_FILE ?= $(BUILD_DIR)/$(MAIN_NAME).a +.PHONY: lib +lib: $(LIB_OUT_FILE) +$(LIB_OUT_FILE): $(filter-out $(BUILD_DIR)/$(MAIN_NAME)_.cpp$(OBJ_EXT),$(USER_OBJ)) + echo Building library $(LIB_OUT_FILE) + rm -f $(LIB_OUT_FILE) + $(LIB_COM) cru $(LIB_OUT_FILE) $^ + +clean: + echo Removing all build files + rm -rf "$(BUILD_DIR)" $(FILES_TO_CLEAN) + +list_boards: + echo === Available boards === + cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^([\w\-]+)\.name=(.+)/){ print sprintf("%-20s %s\n", $$1,$$2);} }' + +list_lib: + echo === User specific libraries === + perl -e 'foreach (@ARGV) {print "$$_\n"}' "* Include directories:" $(USER_INC_DIRS) "* Library source files:" $(USER_SRC) + +list_flash_defs: + echo === Memory configurations for board: $(BOARD) === + cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.$(FLASH_DEF_MATCH)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' + +list_lwip: + echo === lwip configurations for board: $(BOARD) === + cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.menu\.(?:LwIPVariant|ip)\.(\w+)=(.+)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' + +help: $(ARDUINO_MK) + echo + echo "Generic makefile for building Arduino esp8266 and esp32 projects" + echo "This file can either be used directly or included from another makefile" + echo "" + echo "The following targets are available:" + echo " all (default) Build the project application" + echo " clean Remove all intermediate build files" + echo " lib Build a library with all involved object files" + echo " flash Build and and flash the project application" + echo " flash_fs Build and and flash file system (when applicable)" + echo " ota Build and and flash via OTA" + echo " Params: OTA_ADDR, OTA_PORT and OTA_PWD" + echo " ota_fs Build and and flash file system via OTA" + echo " http Build and and flash via http (curl)" + echo " Params: HTTP_ADDR, HTTP_URI, HTTP_PWD and HTTP_USR" + echo " dump_flash Dump the whole board flash memory to a file" + echo " restore_flash Restore flash memory from a previously dumped file" + echo " dump_fs Extract all files from the flash file system" + echo " Params: FS_DUMP_DIR" + echo " erase_flash Erase the whole flash" + echo " list_lib Show a list of used library files and include paths" + echo "Configurable parameters:" + echo " SKETCH Main source file" + echo " If not specified the first sketch in current" + echo " directory will be used." + echo " LIBS Includes in the sketch file of libraries from within" + echo " the ESP Arduino directories are automatically" + echo " detected. If this is not enough, define this" + echo " variable with all libraries or directories needed." + echo " CHIP Set to esp8266 or esp32. Default: '$(CHIP)'" + echo " BOARD Name of the target board. Default: '$(BOARD)'" + echo " Use 'list_boards' to get list of available ones" + echo " FLASH_DEF Flash partitioning info. Default '$(FLASH_DEF)'" + echo " Use 'list_flash_defs' to get list of available ones" + echo " BUILD_DIR Directory for intermediate build files." + echo " Default '$(BUILD_DIR)'" + echo " BUILD_EXTRA_FLAGS Additional parameters for the compilation commands" + echo " FS_DIR File system root directory" + echo " UPLOAD_PORT Serial flashing port name. Default: '$(UPLOAD_PORT)'" + echo " UPLOAD_SPEED Serial flashing baud rate. Default: '$(UPLOAD_SPEED)'" + echo " FLASH_FILE File name for dump and restore flash operations" + echo " Default: '$(FLASH_FILE)'" + echo " LWIP_VARIANT Use specified variant of the lwip library when applicable" + echo " Use 'list_lwip' to get list of available ones" + echo " Default: $(LWIP_VARIANT) ($(LWIP_INFO))" + echo " VERBOSE Set to 1 to get full printout of the build" + echo " BUILD_THREADS Number of parallel build threads" + echo " Default: Maximum possible, based on number of CPUs" + echo " USE_CCACHE Set to 1 to use ccache in the build" + echo + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +.PHONY: all +all: $(BUILD_DIR) $(ARDUINO_MK) $(BUILD_INFO_H) prebuild $(MAIN_EXE) + +prebuild: +ifdef USE_PREBUILD + $(CORE_PREBUILD) +endif + $(SKETCH_PREBUILD) + +# Include all available dependencies +-include $(wildcard $(BUILD_DIR)/*$(DEP_EXT)) + +DEFAULT_GOAL ?= all +.DEFAULT_GOAL := $(DEFAULT_GOAL) + +ifeq ($(OS), Darwin) + BUILD_THREADS ?= $(shell sysctl -n hw.ncpu) +else + BUILD_THREADS ?= $(shell nproc) +endif +MAKEFLAGS += -j $(BUILD_THREADS) + +ifndef VERBOSE + # Set silent mode as default + MAKEFLAGS += --silent +endif + +# Inline Perl scripts + +# Parse Arduino definitions and build commands from the descriptions +define PARSE_ARDUINO +my $$board = shift; +my $$flashSize = shift; +my $$os = shift; +$$os =~ s/Windows_NT/windows/; +$$os =~ s/Linux/linux/; +$$os =~ s/Darwin/macosx/; +my $$lwipvariant = shift; +my %v; + +sub def_var { + my ($$name, $$var) = @_; + print "$$var ?= $$v{$$name}\n"; + $$v{$$name} = "\$$($$var)"; +} + +$$v{'runtime.platform.path'} = '$$(ESP_ROOT)'; +$$v{'includes'} = '$$(C_INCLUDES)'; +$$v{'runtime.ide.version'} = '10605'; +$$v{'build.arch'} = uc('$(CHIP)'); +$$v{'build.project_name'} = '$$(MAIN_NAME)'; +$$v{'build.path'} = '$$(BUILD_DIR)'; +$$v{'object_files'} = '$$^ $$(BUILD_INFO_OBJ)'; +$$v{'archive_file_path'} = '$$(CORE_LIB)'; + +foreach my $$fn (@ARGV) { + open($$f, $$fn) || die "Failed to open: $$fn\n"; + while (<$$f>) { + s/\s+$$//; + s/\.esptool_py\./.esptool./g; + next unless /^(\w[\w\-\.]+)=(.*)/; + my ($$key, $$val) =($$1, $$2); + $$board_defined = 1 if $$key eq "$$board.name"; + $$key =~ s/$$board\.menu\.(?:FlashSize|eesz)\.$$flashSize\.//; + $$key =~ s/$$board\.menu\.CpuFrequency\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.(?:FlashFreq|xtal)\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.UploadSpeed\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.baud\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.ResetMethod\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.FlashMode\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.(?:LwIPVariant|ip)\.$$lwipvariant\.//; + $$key =~ s/^$$board\.//; + $$v{$$key} ||= $$val; + $$v{$$1} = $$v{$$key} if $$key =~ /(.+)\.$$os$$/; + } + close($$f); +} +$$v{'runtime.tools.xtensa-lx106-elf-gcc.path'} ||= '$$(COMP_PATH)'; +$$v{'runtime.tools.xtensa-esp32-elf-gcc.path'} ||= '$$(COMP_PATH)'; +$$v{'runtime.tools.esptool.path'} ||= '$$(ESPTOOL_PATH)'; + +die "* Unknown board $$board\n" unless $$board_defined; +print "# Board definitions\n"; +def_var('build.code_debug', 'CORE_DEBUG_LEVEL'); +def_var('build.f_cpu', 'F_CPU'); +def_var('build.flash_mode', 'FLASH_MODE'); +def_var('build.flash_freq', 'FLASH_SPEED'); +def_var('upload.resetmethod', 'UPLOAD_RESET'); +def_var('upload.speed', 'UPLOAD_SPEED'); +def_var('compiler.warning_flags', 'COMP_WARNINGS'); +$$v{'serial.port'} = '$$(UPLOAD_PORT)'; +$$v{'recipe.objcopy.hex.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; +$$v{'recipe.objcopy.hex.1.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; +$$v{'recipe.hooks.linking.prelink.1.pattern'} =~ s/\{build.vtable_flags\}/\$$(VTABLE_FLAGS)/; +$$v{'tools.esptool.upload.pattern'} =~ s/\{(cmd|path)\}/\{tools.esptool.$$1\}/g; +$$v{'compiler.cpreprocessor.flags'} .= " \$$(C_PRE_PROC_FLAGS)"; +$$v{'build.extra_flags'} .= " \$$(BUILD_EXTRA_FLAGS)"; + +foreach my $$key (sort keys %v) { + while ($$v{$$key} =~/\{/) { + $$v{$$key} =~ s/\{([\w\-\.]+)\}/$$v{$$1}/; + $$v{$$key} =~ s/""//; + } + $$v{$$key} =~ s/ -o\s+$$//; + $$v{$$key} =~ s/(-D\w+=)"([^"]+)"/$$1\\"$$2\\"/g; +} + +print "INCLUDE_VARIANT = $$v{'build.variant'}\n"; +print "# Commands\n"; +print "C_COM=\$$(C_COM_PREFIX) $$v{'recipe.c.o.pattern'}\n"; +print "CPP_COM=\$$(CPP_COM_PREFIX) $$v{'recipe.cpp.o.pattern'}\n"; +print "S_COM=$$v{'recipe.S.o.pattern'}\n"; +print "LIB_COM=\"$$v{'compiler.path'}$$v{'compiler.ar.cmd'}\"\n"; +print "CORE_LIB_COM=$$v{'recipe.ar.pattern'}\n"; +print "LD_COM=$$v{'recipe.c.combine.pattern'}\n"; +print "PART_FILE?=$$1\n" if $$v{'recipe.objcopy.eep.pattern'} =~ /\"([^\"]+\.csv)\"/; +$$v{'recipe.objcopy.eep.pattern'} =~ s/\"([^\"]+\.csv)\"/\$$(PART_FILE)/; +print "GEN_PART_COM=$$v{'recipe.objcopy.eep.pattern'}\n"; +print "ELF2BIN_COM=", $$v{'recipe.objcopy.hex.pattern'} || $$v{'recipe.objcopy.hex.1.pattern'}, "\n"; +print "SIZE_COM=$$v{'recipe.size.pattern'}\n"; +print "ESPTOOL_COM?=$$v{'tools.esptool.path'}/$$v{'tools.esptool.cmd'}\n"; +print "UPLOAD_COM?=$$v{'tools.esptool.upload.pattern'}\n"; + +if ($$v{'build.spiffs_start'}) { + print "SPIFFS_START?=$$v{'build.spiffs_start'}\n"; + my $$spiffs_size = sprintf("0x%X", hex($$v{'build.spiffs_end'})-hex($$v{'build.spiffs_start'})); + print "SPIFFS_SIZE?=$$spiffs_size\n"; +} elsif ($$v{'build.partitions'}) { + print "COMMA=,\n"; + print "SPIFFS_SPEC:=\$$(subst \$$(COMMA), ,\$$(shell grep spiffs \$$(PART_FILE)))\n"; + print "SPIFFS_START:=\$$(word 4,\$$(SPIFFS_SPEC))\n"; + print "SPIFFS_SIZE:=\$$(word 5,\$$(SPIFFS_SPEC))\n"; +} +$$v{'build.spiffs_blocksize'} ||= "4096"; +print "SPIFFS_BLOCK_SIZE?=$$v{'build.spiffs_blocksize'}\n"; +print "MKSPIFFS_COM?=\"\$$(MKSPIFFS_PATH)\" -b \$$(SPIFFS_BLOCK_SIZE) -s \$$(SPIFFS_SIZE) -c \$$(FS_DIR) \$$(FS_IMAGE)\n"; +print "RESTSPIFFS_COM?=\"\$$(MKSPIFFS_PATH)\" -b \$$(SPIFFS_BLOCK_SIZE) -s \$$(SPIFFS_SIZE) -u \$$(FS_REST_DIR) \$$(FS_IMAGE)\n"; + +my $$fs_upload_com = $$v{'tools.esptool.upload.pattern'}; +$$fs_upload_com =~ s/(.+ -ca) .+/$$1 \$$(SPIFFS_START) -cf \$$(FS_IMAGE)/; +$$fs_upload_com =~ s/(.+ --flash_size detect) .+/$$1 \$$(SPIFFS_START) \$$(FS_IMAGE)/; +print "FS_UPLOAD_COM?=$$fs_upload_com\n"; +my $$val = $$v{'recipe.hooks.core.prebuild.1.pattern'}; +$$val =~ s/bash -c "(.+)"/$$1/; +$$val =~ s/(#define .+0x)(\`)/"\\$$1\"$$2/; +$$val =~ s/(\\)//; +print "CORE_PREBUILD=$$val\n"; +print "SKETCH_PREBUILD=$$v{'recipe.hooks.sketch.prebuild.1.pattern'}\n"; +print "VTABLE_FLAGS?=$$v{'build.vtable_flags'}\n"; +print "LINK_PREBUILD=$$v{'recipe.hooks.linking.prelink.1.pattern'}\n"; +print "MEM_FLASH=$$v{'recipe.size.regex'}\n"; +print "MEM_RAM=$$v{'recipe.size.regex.data'}\n"; +$$flash_info = $$v{'menu.FlashSize.' . $$flashSize} || $$v{'menu.eesz.' . $$flashSize}; +print "FLASH_INFO=$$flash_info\n"; +print "LWIP_INFO=", $$v{'menu.LwIPVariant.' . $$lwipvariant} || $$v{'menu.ip.' . $$lwipvariant}, "\n"; +endef +export PARSE_ARDUINO + +# Convert memory information +define MEM_USAGE +$$fp = shift; +$$rp = shift; +while (<>) { + $$r += $$1 if /$$rp/; + $$f += $$1 if /$$fp/; +} +print "\nMemory usage\n"; +print sprintf(" %-6s %6d bytes\n" x 2 ."\n", "Ram:", $$r, "Flash:", $$f); +endef +export MEM_USAGE diff --git a/mqtt.cpp b/mqtt.cpp index 9176e4c3..4bbe81a6 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -26,7 +26,7 @@ #if defined(ESP8266) #include #endif - #include + #include #include struct PubSubClient *mqtt_client = NULL; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 80f5850d..37b43320 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -902,7 +902,7 @@ void server_change_program() { } // process interval day remainder (relative-> absolute) - if (prog.type == PROGRAM_TYPE_INTERVAL && prog.days[1] > 1) { + if (prog.type == PROGRAM_TYPE_INTERVAL && prog.days[1] >= 1) { pd.drem_to_absolute(prog.days); } @@ -1000,7 +1000,7 @@ void server_json_programs_main() { ProgramStruct prog; for(pid=0;pid 1) { + if (prog.type == PROGRAM_TYPE_INTERVAL && prog.days[1] >= 1) { pd.drem_to_relative(prog.days); } @@ -1188,7 +1188,7 @@ void server_change_values() { #if defined(ESP8266) char *p = NULL; - extern unsigned long reboot_timer; + extern uint32_t reboot_timer; if(!process_password()) return; if (m_client) p = get_buffer; @@ -1207,7 +1207,8 @@ void server_change_values() if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rbt"), true) && atoi(tmp_buffer) > 0) { #if defined(ESP8266) - reboot_timer = millis() + 1000; + os.status.safe_reboot = 0; + reboot_timer = os.now_tz() + 2; handle_return(HTML_SUCCESS); #else print_html_standard_header(); @@ -1380,6 +1381,7 @@ void server_change_options() if (os.sopt_save(SOPT_WEATHER_OPTS, tmp_buffer)) { weather_change = true; // if wto has changed } + //DEBUG_PRINTLN(os.sopt_load(SOPT_WEATHER_OPTS)); } keyfound = 0; @@ -2126,10 +2128,29 @@ ulong getNtpTime() { // only proceed if we are connected if(!os.network_connected()) return 0; + uint16_t port = (uint16_t)(os.iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)os.iopts[IOPT_HTTPPORT_0]; + port = (port==8000) ? 8888:8000; // use a different port than http port + UDP *udp = NULL; + #if defined(ESP8266) + if(m_server) udp = new EthernetUDP(); + else udp = new WiFiUDP(); + #else + udp = new EthernetUDP(); + #endif + #define NTP_PACKET_SIZE 48 #define NTP_PORT 123 - #define NTP_NTRIES 3 - + #define NTP_NTRIES 10 + #define N_PUBLIC_SERVERS 5 + + static const char* public_ntp_servers[] = { + "time.google.com", + "time.nist.gov", + "time.windows.com", + "time.cloudflare.com", + "pool.ntp.org" }; + static uint8_t sidx = 0; + static byte packetBuffer[NTP_PACKET_SIZE]; byte ntpip[4] = { os.iopts[IOPT_NTP_IP1], @@ -2138,8 +2159,10 @@ ulong getNtpTime() { os.iopts[IOPT_NTP_IP4]}; byte tries=0; ulong gt = 0; - do { + while(triesbegin(port); + memset(packetBuffer, 0, NTP_PACKET_SIZE); packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock @@ -2150,21 +2173,35 @@ ulong getNtpTime() { packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; - // by default use pool.ntp.org if ntp ip is unset + + // use one of the public NTP servers if ntp ip is unset + DEBUG_PRINT(F("ntp: ")); + int ret; if (!os.iopts[IOPT_NTP_IP1] || os.iopts[IOPT_NTP_IP1] == '0') { - DEBUG_PRINTLN(F("pool.ntp.org")); - udp->beginPacket("pool.ntp.org", NTP_PORT); + DEBUG_PRINT(public_ntp_servers[sidx]); + ret = udp->beginPacket(public_ntp_servers[sidx], NTP_PORT); } else { DEBUG_PRINTLN(IPAddress(ntpip[0],ntpip[1],ntpip[2],ntpip[3])); - udp->beginPacket(ntpip, NTP_PORT); + ret = udp->beginPacket(ntpip, NTP_PORT); + } + if(ret!=1) { + DEBUG_PRINT(F(" not available (ret: ")); + DEBUG_PRINT(ret); + DEBUG_PRINTLN(")"); + udp->stop(); + tries++; + sidx=(sidx+1)%N_PUBLIC_SERVERS; + continue; + } else { + DEBUG_PRINTLN(F(" connected")); } udp->write(packetBuffer, NTP_PACKET_SIZE); udp->endPacket(); // end of sendNtpPacket // process response - ulong timeout = millis()+1000; + ulong timeout = millis()+2000; while(millis() < timeout) { if(udp->parsePacket()) { udp->read(packetBuffer, NTP_PACKET_SIZE); @@ -2174,12 +2211,20 @@ ulong getNtpTime() { ulong seventyYears = 2208988800UL; ulong gt = secsSince1900 - seventyYears; // check validity: has to be larger than 1/1/2020 12:00:00 - if(gt>1577836800UL) return gt; + if(gt>1577836800UL) { + udp->stop(); + delete udp; + return gt; + } } } - tries ++; - } while(triesstop(); + sidx=(sidx+1)%N_PUBLIC_SERVERS; + } + if(tries==NTP_NTRIES) {DEBUG_PRINTLN(F("NTP failed!!"));} + udp->stop(); + delete udp; return 0; } #endif diff --git a/platformio.ini b/platformio.ini index f320e43e..44aa7fc3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,7 @@ board = d1_mini_lite framework = arduino lib_ldf_mode = deep lib_deps = - UIPEthernet=https://github.com/OpenSprinkler/UIPEthernet/archive/fixes/dhcp.zip + EthernetENC=https://github.com/jandrassy/EthernetENC/archive/refs/tags/2.0.1.zip sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 @@ -36,7 +36,7 @@ board_build.variant = sanguino framework = arduino lib_ldf_mode = deep lib_deps = - UIPEthernet=https://github.com/OpenSprinkler/UIPEthernet/archive/fixes/dhcp.zip + EthernetENC=https://github.com/jandrassy/EthernetENC/archive/refs/tags/2.0.1.zip knolleary/PubSubClient @ ^2.8 greiman/SdFat @ 1.0.7 Wire