├── IDE └── mpy-cross ├── LICENSE ├── README.md ├── bin ├── esp8266upython23sept17.bin └── esptool.py ├── blynk-examples ├── P1_simplestgpio.py ├── P2_customgpio.py ├── P2a_customgpio.py ├── P2b_customgpio.py ├── P6_email.py ├── p3_rlogger.py ├── p4_jlogger2oled.py └── p5_lcd.py ├── images ├── 23017.jpg ├── 23017a.png ├── 4051.jpg ├── 4051a.png ├── 4x8x8.png ├── 7seg.jpg ├── 8591.jpg ├── 8x8.png ├── MQTT.png ├── Trig.gif ├── accel.jpg ├── beeper.jpg ├── bl.bmp ├── blynk.jpg ├── browser.jpg ├── d1.jpg ├── dispBox.jpg ├── esp-12.png ├── esp01-uart.png ├── esp01.jpg ├── gpio.bmp ├── i.bmp ├── init.png ├── irrx.jpg ├── kb3x4.png ├── library.png ├── log.jpg ├── nodemcu.png ├── oled.jpg ├── project.jpg ├── putty1.png ├── putty2.png ├── rollpitch.png ├── servo.jpg ├── sr04.jpg ├── star.bmp ├── star1.bmp ├── stepper.jpg ├── telnet.png ├── temptspeak.png ├── tft144.jpg ├── time1.png ├── time2.png ├── wifi.png └── zzzz.png ├── lib ├── Oled.py ├── blynk_0.py ├── blynk_1.py ├── blynk_2.py ├── blynk_3.py ├── blynk_4.py ├── blynk_5.py ├── blynk_6.py ├── boot.py ├── main.py ├── pblynk.py ├── sntp.py ├── ssd1306.py └── wifi.py ├── pblynk.md ├── pblynk.py └── settings.py /IDE/mpy-cross: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/IDE/mpy-cross -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Brian Lavery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## P-Suite 2 | 3 | 4 | An integrated suite of micropython files for ESP8266. 5 | Based on [micropython.org version](http://micropython.org/download). 6 | 7 | ## May 2018 - This is no longer being maintained by the original author. 8 | ## Please feel free to fork, copy, adapt if you find it useable 9 | 10 | ## Overview: 11 | 12 | This suite of files is intended primarily for use with ESP12-based 13 | boards (including NodeMCU and Wemos D1-mini), but it does work for 14 | ESP-01, within its gpio limits. pSuite is modelled on the earlier eLua [eSuite](https://github.com/BLavery/esuite-lua). 15 | 16 | pSuite automates the standard startup including escape time, wifi connection and time 17 | setting. This leaves you to concentrate just on your project scripting: on exactly 18 | what you want to control. Included are libraries for blynk and oled display. More libraries are intended later. 19 | 20 | The pSuite projects are intended to be used as client (“STATION” mode) 21 | in conjunction with a nearby wifi access point. The ESP8266 is a 22 | wifi-capable chip, and merely using an isolated “blink a LED” project 23 | misses its point! 24 | 25 | 26 | The micropython environment uses the SOC native numbers for GPIO pins (0 1 ... 16). This is the chip's native GPIO numbering as used also by the arduino-esp environment. You may need to cross reference from the D0 D1 labelling seen on nodemcu devkit boards. 27 | 28 | ## Disclaimer/Claimer: 29 | 30 | As at October 2017, this project is merely "bare-bones". Please consider it as 0.1 alpha. However, it DOES run. 31 | 32 | It is groundwork for a proposed class starting in 2018. My classes tend to prefer interpreter environments rather than compiler. The nodemcu eLua interpreter based on the "non-OS" SDK has occupied this position for ESP8266 until now. It has been stable, well documented and effective. 33 | 34 | For the ESP32, there are currently at least 3 lua development attempts "out there". Espressif has terminated the "non-OS" SDK used by the original "nodemcu" lua, and is now offering only the free-rtos SDK (a much better long-view choice). I can't find a lua worth using, or usably documented. Maybe the wind is out of the lua sails. 35 | 36 | I believe the interpreter future is probably micropython. The ESP8266 micropython has reached workable status, and its documentation is reasonable if not quite finished. 37 | 38 | *The ESP32 micropython build is published for trying, but clearly a lot of work remains. It seems to be undocumented, but perhaps it will become a fairly faithful clone of the ESP8266 version.* 39 | 40 | And faux-python is a lot more mainstream than lua. And more student friendly than faux-arduino faux-C++ or hardcore SDK/rtos C. 41 | 42 | All that said, where is micropython/esp really headed? At micropython, the esp8266 kickstarter campaign is about done. Adafruit's CircuitPython fork is racing away separately. On ESP32, the trumpetted marriage 9 months ago between Pycom & the main micropython seems marked by silence. 43 | 44 | So the efforts as documented below are laid out in hope they may be useful to some. Will they expand to a class here in 2018? We'll see. 45 | 46 | ## Common startup files: 47 | 48 | 1. boot.py 49 | 1. main.py 50 | 1. wifi.py 51 | 1. sntp.py 52 | 1. settings.py 53 | 54 | These are always used. boot.py and main.py are as mandated by micropython. main.py imports wifi and sntp to start communication and fetch real time. 55 | main then passes control to 56 | your individual “project” file. So the standard minimum is five files, plus your project. 57 | 58 | The compulsory **settings.py** is intended as a general-purpose config file easily importable by other modules. It includes your wifi credentials, i2c pin choice, blynk token, etc. And importantly, you **edit and re-upload the settings file to designate a new project file**. 59 | 60 | 61 | **boot.py** simply ensures that wifi has enabled the auto-reconnect to your wifi router/AP. 62 | 63 | **main.py** turns on (for 2 seconds) the led inbuilt to the ESP12 submodule. At the end of that period, the flash button ("D3" / gpio-0) is sampled. If pressed, processing terminates there. This gives you an escape mechanism in the case a script error is causing repeated reboots, although this is less a problem in micropython than in Nodemcu lua. 64 | 65 | main.py then calls **wifi.py**. If your settings have nominated an AP mode password, then the ESP8266 AP server will start up on 192.168.4.1. 66 | 67 | If wifi has by now auto-connected as client to your local network, there is no more connecting to do. Otherwise, each router credential listed in settings.py (and provided it is seen in a scan) will be tried for login. The successful connected IP will display to terminal. 68 | 69 | wifi.py finishes by starting webrepl on the network(s) active. 70 | 71 | Then main.py calls **sntp.py**, a more robust sync function than inbuilt ntptime module. Sntp makes as many as 4 attempts to fetch internet time. You nominate in your settings file two ntp servers to use. 72 | 73 | This library sets a 3-hour repeating timer for the time-sync operation, correcting any RTC drift of the ESP8266. 74 | 75 | Finally, you are left with a useful asctime() function (readable timestamp) callable anytime: 76 | 77 | import snpt 78 | print(sntp.asctime()) 79 | 80 | main.py has one more job: to launch your nominated **project file**, which you nominate in the settings file. Obviously you must build your own project file(s), but any of the examples files could be a starting point. 81 | 82 | *Maybe you have a single "project" to construct. In my classroom environment, students swap (just) the project file perhaps several times in a class.* 83 | 84 | ## Blynk Library: 85 | 86 | On micropython, a key consideration is shortage of RAM memory, and blynk needs a large and complex library. There is however a blynk library included here in pSuite. 87 | 88 | [For the blynk API see HERE.](pblynk.md) 89 | 90 | 91 | 92 | ## Oled library: 93 | 94 | In the classrom environment where eSuite & pSuite are targetted, 95 | an oled display is used in most projects, usually the 128x64 "0.96 inch" ubiquitous module. It is easy, cheap and versatile. 96 | 97 | **Oled.py** is a wrapper to the usual/official sd1306.py module for ESP8266/ESP32. If an I2C scan shows oled is present, Oled.py initialises the display and presents initial info: IP number and time. Thereafter, you use oled calls as per the framebuf documentation. 98 | 99 | ## "Build" and IDE? 100 | 101 | ESPlorer as used on the nodemcu lua environment does NOT support the current micropython, despite what it may claim. 102 | 103 | The options for communicating with your micropython ESP are: 104 | 105 | 1. plain terminal (puTTY, gtkterm, etc) - no file transfer 106 | 1. uPyCraft on Windows - not featured, temperamental 107 | 1. AMPY cli from Adafruit - I found some commands crashed 108 | 1. rshell cli 109 | 1. mpfshell cli - similar to AMPY and **my preference for CLI** 110 | 1. Browser based using webrepl interface 111 | 1. **uPyLoader - MY CHOICE**, [here](https://github.com/BetaRavener/uPyLoader), altho no folder support at MCU 112 | 113 | The executable I use for uPyLoader (linux version) is here on /IDE folder. 114 | 115 | The [ESP8266 micropython binary](http://micropython.org/download#esp8266) I fetched direct from micropython website. The bin I used is here in the /bin folder. 116 | 117 | You need recent esptool.py to flash the binary to the board. There is a copy here in /bin folder. 118 | 119 | In all cases you ought to fetch your own copies of these tools. 120 | 121 | uPyLoader IDE currently supports folders at PC end but not at the ESP end. So while "library" files in this repository can happily live in a /lib folder at PC, upload them all in together into the base folder at the ESP. Same applies to your selection of example or "project" files. 122 | -------------------------------------------------------------------------------- /bin/esp8266upython23sept17.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/bin/esp8266upython23sept17.bin -------------------------------------------------------------------------------- /bin/esptool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # ESP8266 & ESP32 ROM Bootloader Utility 4 | # Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) PTE LTD, other contributors as noted. 5 | # https://github.com/espressif/esptool 6 | # 7 | # This program is free software; you can redistribute it and/or modify it under 8 | # the terms of the GNU General Public License as published by the Free Software 9 | # Foundation; either version 2 of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License along with 16 | # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin 17 | # Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | from __future__ import print_function, division 20 | 21 | import argparse 22 | import hashlib 23 | import inspect 24 | import os 25 | import serial 26 | import struct 27 | import sys 28 | import time 29 | import base64 30 | import zlib 31 | import shlex 32 | import copy 33 | import io 34 | 35 | __version__ = "2.2-dev" 36 | 37 | MAX_UINT32 = 0xffffffff 38 | MAX_UINT24 = 0xffffff 39 | 40 | DEFAULT_TIMEOUT = 3 # timeout for most flash operations 41 | START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase) 42 | CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase 43 | SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader 44 | 45 | 46 | DETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB', 47 | 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB'} 48 | 49 | 50 | def check_supported_function(func, check_func): 51 | """ 52 | Decorator implementation that wraps a check around an ESPLoader 53 | bootloader function to check if it's supported. 54 | This is used to capture the multidimensional differences in 55 | functionality between the ESP8266 & ESP32 ROM loaders, and the 56 | software stub that runs on both. Not possible to do this cleanly 57 | via inheritance alone. 58 | """ 59 | def inner(*args, **kwargs): 60 | obj = args[0] 61 | if check_func(obj): 62 | return func(*args, **kwargs) 63 | else: 64 | raise NotImplementedInROMError(obj, func) 65 | return inner 66 | 67 | 68 | def stub_function_only(func): 69 | """ Attribute for a function only supported in the software stub loader """ 70 | return check_supported_function(func, lambda o: o.IS_STUB) 71 | 72 | 73 | def stub_and_esp32_function_only(func): 74 | """ Attribute for a function only supported by software stubs or ESP32 ROM """ 75 | return check_supported_function(func, lambda o: o.IS_STUB or o.CHIP_NAME == "ESP32") 76 | 77 | 78 | PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3 79 | 80 | # Function to return nth byte of a bitstring 81 | # Different behaviour on Python 2 vs 3 82 | if PYTHON2: 83 | def byte(bitstr, index): 84 | return ord(bitstr[index]) 85 | else: 86 | def byte(bitstr, index): 87 | return bitstr[index] 88 | 89 | 90 | def esp8266_function_only(func): 91 | """ Attribute for a function only supported on ESP8266 """ 92 | return check_supported_function(func, lambda o: o.CHIP_NAME == "ESP8266") 93 | 94 | 95 | class ESPLoader(object): 96 | """ Base class providing access to ESP ROM & softtware stub bootloaders. 97 | Subclasses provide ESP8266 & ESP32 specific functionality. 98 | Don't instantiate this base class directly, either instantiate a subclass or 99 | call ESPLoader.detect_chip() which will interrogate the chip and return the 100 | appropriate subclass instance. 101 | """ 102 | CHIP_NAME = "Espressif device" 103 | IS_STUB = False 104 | 105 | DEFAULT_PORT = "/dev/ttyUSB0" 106 | 107 | # Commands supported by ESP8266 ROM bootloader 108 | ESP_FLASH_BEGIN = 0x02 109 | ESP_FLASH_DATA = 0x03 110 | ESP_FLASH_END = 0x04 111 | ESP_MEM_BEGIN = 0x05 112 | ESP_MEM_END = 0x06 113 | ESP_MEM_DATA = 0x07 114 | ESP_SYNC = 0x08 115 | ESP_WRITE_REG = 0x09 116 | ESP_READ_REG = 0x0a 117 | 118 | # Some comands supported by ESP32 ROM bootloader (or -8266 w/ stub) 119 | ESP_SPI_SET_PARAMS = 0x0B 120 | ESP_SPI_ATTACH = 0x0D 121 | ESP_CHANGE_BAUDRATE = 0x0F 122 | ESP_FLASH_DEFL_BEGIN = 0x10 123 | ESP_FLASH_DEFL_DATA = 0x11 124 | ESP_FLASH_DEFL_END = 0x12 125 | ESP_SPI_FLASH_MD5 = 0x13 126 | 127 | # Some commands supported by stub only 128 | ESP_ERASE_FLASH = 0xD0 129 | ESP_ERASE_REGION = 0xD1 130 | ESP_READ_FLASH = 0xD2 131 | ESP_RUN_USER_CODE = 0xD3 132 | 133 | # Maximum block sized for RAM and Flash writes, respectively. 134 | ESP_RAM_BLOCK = 0x1800 135 | 136 | FLASH_WRITE_SIZE = 0x400 137 | 138 | # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. 139 | ESP_ROM_BAUD = 115200 140 | 141 | # First byte of the application image 142 | ESP_IMAGE_MAGIC = 0xe9 143 | 144 | # Initial state for the checksum routine 145 | ESP_CHECKSUM_MAGIC = 0xef 146 | 147 | # Flash sector size, minimum unit of erase. 148 | FLASH_SECTOR_SIZE = 0x1000 149 | 150 | UART_DATA_REG_ADDR = 0x60000078 151 | 152 | # Memory addresses 153 | IROM_MAP_START = 0x40200000 154 | IROM_MAP_END = 0x40300000 155 | 156 | # The number of bytes in the UART response that signify command status 157 | STATUS_BYTES_LENGTH = 2 158 | 159 | def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD): 160 | """Base constructor for ESPLoader bootloader interaction 161 | Don't call this constructor, either instantiate ESP8266ROM 162 | or ESP32ROM, or use ESPLoader.detect_chip(). 163 | This base class has all of the instance methods for bootloader 164 | functionality supported across various chips & stub 165 | loaders. Subclasses replace the functions they don't support 166 | with ones which throw NotImplementedInROMError(). 167 | """ 168 | if isinstance(port, serial.Serial): 169 | self._port = port 170 | else: 171 | self._port = serial.serial_for_url(port) 172 | self._slip_reader = slip_reader(self._port) 173 | # setting baud rate in a separate step is a workaround for 174 | # CH341 driver on some Linux versions (this opens at 9600 then 175 | # sets), shouldn't matter for other platforms/drivers. See 176 | # https://github.com/espressif/esptool/issues/44#issuecomment-107094446 177 | self._set_port_baudrate(baud) 178 | 179 | def _set_port_baudrate(self, baud): 180 | try: 181 | self._port.baudrate = baud 182 | except IOError: 183 | raise FatalError("Failed to set baud rate %d. The driver may not support this rate." % baud) 184 | 185 | @staticmethod 186 | def detect_chip(port=DEFAULT_PORT, baud=ESP_ROM_BAUD, connect_mode='default_reset'): 187 | """ Use serial access to detect the chip type. 188 | We use the UART's datecode register for this, it's mapped at 189 | the same address on ESP8266 & ESP32 so we can use one 190 | memory read and compare to the datecode register for each chip 191 | type. 192 | This routine automatically performs ESPLoader.connect() (passing 193 | connect_mode parameter) as part of querying the chip. 194 | """ 195 | detect_port = ESPLoader(port, baud) 196 | detect_port.connect(connect_mode) 197 | print('Detecting chip type...', end='') 198 | sys.stdout.flush() 199 | date_reg = detect_port.read_reg(ESPLoader.UART_DATA_REG_ADDR) 200 | 201 | for cls in [ESP8266ROM, ESP32ROM]: 202 | if date_reg == cls.DATE_REG_VALUE: 203 | # don't connect a second time 204 | inst = cls(detect_port._port, baud) 205 | print(' %s' % inst.CHIP_NAME) 206 | return inst 207 | print('') 208 | raise FatalError("Unexpected UART datecode value 0x%08x. Failed to autodetect chip type." % date_reg) 209 | 210 | """ Read a SLIP packet from the serial port """ 211 | def read(self): 212 | return next(self._slip_reader) 213 | 214 | """ Write bytes to the serial port while performing SLIP escaping """ 215 | def write(self, packet): 216 | buf = b'\xc0' \ 217 | + (packet.replace(b'\xdb',b'\xdb\xdd').replace(b'\xc0',b'\xdb\xdc')) \ 218 | + b'\xc0' 219 | self._port.write(buf) 220 | 221 | """ Calculate checksum of a blob, as it is defined by the ROM """ 222 | @staticmethod 223 | def checksum(data, state=ESP_CHECKSUM_MAGIC): 224 | for b in data: 225 | if type(b) is int: # python 2/3 compat 226 | state ^= b 227 | else: 228 | state ^= ord(b) 229 | 230 | return state 231 | 232 | """ Send a request and read the response """ 233 | def command(self, op=None, data=b"", chk=0, wait_response=True): 234 | if op is not None: 235 | pkt = struct.pack(b' self.STATUS_BYTES_LENGTH: 279 | return data[:-self.STATUS_BYTES_LENGTH] 280 | else: # otherwise, just return the 'val' field which comes from the reply header (this is used by read_reg) 281 | return val 282 | 283 | def flush_input(self): 284 | self._port.flushInput() 285 | self._slip_reader = slip_reader(self._port) 286 | 287 | def sync(self): 288 | self.command(self.ESP_SYNC, b'\x07\x07\x12\x20' + 32 * b'\x55') 289 | for i in range(7): 290 | self.command() 291 | 292 | def _connect_attempt(self, mode='default_reset', esp32r0_delay=False): 293 | """ A single connection attempt, with esp32r0 workaround options """ 294 | # esp32r0_delay is a workaround for bugs with the most common auto reset 295 | # circuit and Windows, if the EN pin on the dev board does not have 296 | # enough capacitance. 297 | # 298 | # Newer dev boards shouldn't have this problem (higher value capacitor 299 | # on the EN pin), and ESP32 revision 1 can't use this workaround as it 300 | # relies on a silicon bug. 301 | # 302 | # Details: https://github.com/espressif/esptool/issues/136 303 | last_error = None 304 | 305 | # issue reset-to-bootloader: 306 | # RTS = either CH_PD/EN or nRESET (both active low = chip in reset 307 | # DTR = GPIO0 (active low = boot to flasher) 308 | # 309 | # DTR & RTS are active low signals, 310 | # ie True = pin @ 0V, False = pin @ VCC. 311 | if mode != 'no_reset': 312 | self._port.setDTR(False) # IO0=HIGH 313 | self._port.setRTS(True) # EN=LOW, chip in reset 314 | time.sleep(0.1) 315 | if esp32r0_delay: 316 | # Some chips are more likely to trigger the esp32r0 317 | # watchdog reset silicon bug if they're held with EN=LOW 318 | # for a longer period 319 | time.sleep(1.2) 320 | self._port.setDTR(True) # IO0=LOW 321 | self._port.setRTS(False) # EN=HIGH, chip out of reset 322 | if esp32r0_delay: 323 | # Sleep longer after reset. 324 | # This workaround only works on revision 0 ESP32 chips, 325 | # it exploits a silicon bug spurious watchdog reset. 326 | time.sleep(0.4) # allow watchdog reset to occur 327 | time.sleep(0.05) 328 | self._port.setDTR(False) # IO0=HIGH, done 329 | 330 | self._port.timeout = SYNC_TIMEOUT 331 | for _ in range(5): 332 | try: 333 | self.flush_input() 334 | self._port.flushOutput() 335 | self.sync() 336 | self._port.timeout = DEFAULT_TIMEOUT 337 | return None 338 | except FatalError as e: 339 | if esp32r0_delay: 340 | print('_', end='') 341 | else: 342 | print('.', end='') 343 | sys.stdout.flush() 344 | time.sleep(0.05) 345 | last_error = e 346 | return last_error 347 | 348 | def connect(self, mode='default_reset'): 349 | """ Try connecting repeatedly until successful, or giving up """ 350 | print('Connecting...', end='') 351 | sys.stdout.flush() 352 | last_error = None 353 | 354 | try: 355 | for _ in range(10): 356 | last_error = self._connect_attempt(mode=mode, esp32r0_delay=False) 357 | if last_error is None: 358 | return 359 | last_error = self._connect_attempt(mode=mode, esp32r0_delay=True) 360 | if last_error is None: 361 | return 362 | finally: 363 | print('') # end 'Connecting...' line 364 | raise FatalError('Failed to connect to %s: %s' % (self.CHIP_NAME, last_error)) 365 | 366 | """ Read memory address in target """ 367 | def read_reg(self, addr): 368 | # we don't call check_command here because read_reg() function is called 369 | # when detecting chip type, and the way we check for success (STATUS_BYTES_LENGTH) is different 370 | # for different chip types (!) 371 | val, data = self.command(self.ESP_READ_REG, struct.pack(' length: 570 | raise FatalError('Read more than expected') 571 | digest_frame = self.read() 572 | if len(digest_frame) != 16: 573 | raise FatalError('Expected digest, got: %s' % hexify(digest_frame)) 574 | expected_digest = hexify(digest_frame).upper() 575 | digest = hashlib.md5(data).hexdigest().upper() 576 | if digest != expected_digest: 577 | raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) 578 | return data 579 | 580 | def flash_spi_attach(self, hspi_arg): 581 | """Send SPI attach command to enable the SPI flash pins 582 | ESP8266 ROM does this when you send flash_begin, ESP32 ROM 583 | has it as a SPI command. 584 | """ 585 | # last 3 bytes in ESP_SPI_ATTACH argument are reserved values 586 | arg = struct.pack(' 0: 641 | self.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1) 642 | if miso_bits > 0: 643 | self.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1) 644 | else: 645 | 646 | def set_data_lengths(mosi_bits, miso_bits): 647 | SPI_DATA_LEN_REG = SPI_USR1_REG 648 | SPI_MOSI_BITLEN_S = 17 649 | SPI_MISO_BITLEN_S = 8 650 | mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1) 651 | miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1) 652 | self.write_reg(SPI_DATA_LEN_REG, 653 | (miso_mask << SPI_MISO_BITLEN_S) | ( 654 | mosi_mask << SPI_MOSI_BITLEN_S)) 655 | 656 | # SPI peripheral "command" bitmasks for SPI_CMD_REG 657 | SPI_CMD_USR = (1 << 18) 658 | 659 | # shift values 660 | SPI_USR2_DLEN_SHIFT = 28 661 | 662 | if read_bits > 32: 663 | raise FatalError("Reading more than 32 bits back from a SPI flash operation is unsupported") 664 | if len(data) > 64: 665 | raise FatalError("Writing more than 64 bytes of data with one SPI command is unsupported") 666 | 667 | data_bits = len(data) * 8 668 | old_spi_usr = self.read_reg(SPI_USR_REG) 669 | old_spi_usr2 = self.read_reg(SPI_USR2_REG) 670 | flags = SPI_USR_COMMAND 671 | if read_bits > 0: 672 | flags |= SPI_USR_MISO 673 | if data_bits > 0: 674 | flags |= SPI_USR_MOSI 675 | set_data_lengths(data_bits, read_bits) 676 | self.write_reg(SPI_USR_REG, flags) 677 | self.write_reg(SPI_USR2_REG, 678 | (7 << SPI_USR2_DLEN_SHIFT) | spiflash_command) 679 | if data_bits == 0: 680 | self.write_reg(SPI_W0_REG, 0) # clear data register before we read it 681 | else: 682 | data = pad_to(data, 4, b'\00') # pad to 32-bit multiple 683 | words = struct.unpack("I" * (len(data) // 4), data) 684 | next_reg = SPI_W0_REG 685 | for word in words: 686 | self.write_reg(next_reg, word) 687 | next_reg += 4 688 | self.write_reg(SPI_CMD_REG, SPI_CMD_USR) 689 | 690 | def wait_done(): 691 | for _ in range(10): 692 | if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0: 693 | return 694 | raise FatalError("SPI command did not complete in time") 695 | wait_done() 696 | 697 | status = self.read_reg(SPI_W0_REG) 698 | # restore some SPI controller registers 699 | self.write_reg(SPI_USR_REG, old_spi_usr) 700 | self.write_reg(SPI_USR2_REG, old_spi_usr2) 701 | return status 702 | 703 | def read_status(self, num_bytes=2): 704 | """Read up to 24 bits (num_bytes) of SPI flash status register contents 705 | via RDSR, RDSR2, RDSR3 commands 706 | Not all SPI flash supports all three commands. The upper 1 or 2 707 | bytes may be 0xFF. 708 | """ 709 | SPIFLASH_RDSR = 0x05 710 | SPIFLASH_RDSR2 = 0x35 711 | SPIFLASH_RDSR3 = 0x15 712 | 713 | status = 0 714 | shift = 0 715 | for cmd in [SPIFLASH_RDSR, SPIFLASH_RDSR2, SPIFLASH_RDSR3][0:num_bytes]: 716 | status += self.run_spiflash_command(cmd, read_bits=8) << shift 717 | shift += 8 718 | return status 719 | 720 | def write_status(self, new_status, num_bytes=2, set_non_volatile=False): 721 | """Write up to 24 bits (num_bytes) of new status register 722 | num_bytes can be 1, 2 or 3. 723 | Not all flash supports the additional commands to write the 724 | second and third byte of the status register. When writing 2 725 | bytes, esptool also sends a 16-byte WRSR command (as some 726 | flash types use this instead of WRSR2.) 727 | If the set_non_volatile flag is set, non-volatile bits will 728 | be set as well as volatile ones (WREN used instead of WEVSR). 729 | """ 730 | SPIFLASH_WRSR = 0x01 731 | SPIFLASH_WRSR2 = 0x31 732 | SPIFLASH_WRSR3 = 0x11 733 | SPIFLASH_WEVSR = 0x50 734 | SPIFLASH_WREN = 0x06 735 | SPIFLASH_WRDI = 0x04 736 | 737 | enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR 738 | 739 | # try using a 16-bit WRSR (not supported by all chips) 740 | # this may be redundant, but shouldn't hurt 741 | if num_bytes == 2: 742 | self.run_spiflash_command(enable_cmd) 743 | self.run_spiflash_command(SPIFLASH_WRSR, struct.pack(">= 8 750 | 751 | self.run_spiflash_command(SPIFLASH_WRDI) 752 | 753 | def hard_reset(self): 754 | self._port.setRTS(True) # EN->LOW 755 | time.sleep(0.1) 756 | self._port.setRTS(False) 757 | 758 | def soft_reset(self, stay_in_bootloader): 759 | if not self.IS_STUB: 760 | if stay_in_bootloader: 761 | return # ROM bootloader is already in bootloader! 762 | else: 763 | # 'run user code' is as close to a soft reset as we can do 764 | self.flash_begin(0, 0) 765 | self.flash_finish(False) 766 | else: 767 | if stay_in_bootloader: 768 | # soft resetting from the stub loader 769 | # will re-load the ROM bootloader 770 | self.flash_begin(0, 0) 771 | self.flash_finish(True) 772 | elif self.CHIP_NAME != "ESP8266": 773 | raise FatalError("Soft resetting is currently only supported on ESP8266") 774 | else: 775 | # running user code from stub loader requires some hacks 776 | # in the stub loader 777 | self.command(self.ESP_RUN_USER_CODE, wait_response=False) 778 | 779 | 780 | class ESP8266ROM(ESPLoader): 781 | """ Access class for ESP8266 ROM bootloader 782 | """ 783 | CHIP_NAME = "ESP8266" 784 | IS_STUB = False 785 | 786 | DATE_REG_VALUE = 0x00062000 787 | 788 | # OTP ROM addresses 789 | ESP_OTP_MAC0 = 0x3ff00050 790 | ESP_OTP_MAC1 = 0x3ff00054 791 | ESP_OTP_MAC3 = 0x3ff0005c 792 | 793 | SPI_REG_BASE = 0x60000200 794 | SPI_W0_OFFS = 0x40 795 | SPI_HAS_MOSI_DLEN_REG = False 796 | 797 | FLASH_SIZES = { 798 | '512KB':0x00, 799 | '256KB':0x10, 800 | '1MB':0x20, 801 | '2MB':0x30, 802 | '4MB':0x40, 803 | '2MB-c1': 0x50, 804 | '4MB-c1':0x60, 805 | '8MB':0x80, 806 | '16MB':0x90, 807 | } 808 | 809 | BOOTLOADER_FLASH_OFFSET = 0 810 | 811 | def get_efuses(self): 812 | # Return the 128 bits of ESP8266 efuse as a single Python integer 813 | return (self.read_reg(0x3ff0005c) << 96 | 814 | self.read_reg(0x3ff00058) << 64 | 815 | self.read_reg(0x3ff00054) << 32 | 816 | self.read_reg(0x3ff00050)) 817 | 818 | def get_chip_description(self): 819 | efuses = self.get_efuses() 820 | is_8285 = (efuses & ((1 << 4) | 1 << 80)) != 0 # One or the other efuse bit is set for ESP8285 821 | return "ESP8285" if is_8285 else "ESP8266EX" 822 | 823 | def flash_spi_attach(self, hspi_arg): 824 | if self.IS_STUB: 825 | super(ESP8266ROM, self).flash_spi_attach(hspi_arg) 826 | else: 827 | # ESP8266 ROM has no flash_spi_attach command in serial protocol, 828 | # but flash_begin will do it 829 | self.flash_begin(0, 0) 830 | 831 | def flash_set_parameters(self, size): 832 | # not implemented in ROM, but OK to silently skip for ROM 833 | if self.IS_STUB: 834 | super(ESP8266ROM, self).flash_set_parameters(size) 835 | 836 | def chip_id(self): 837 | """ Read Chip ID from OTP ROM - see http://esp8266-re.foogod.com/wiki/System_get_chip_id_%28IoT_RTOS_SDK_0.9.9%29 """ 838 | id0 = self.read_reg(self.ESP_OTP_MAC0) 839 | id1 = self.read_reg(self.ESP_OTP_MAC1) 840 | return (id0 >> 24) | ((id1 & MAX_UINT24) << 8) 841 | 842 | def read_mac(self): 843 | """ Read MAC from OTP ROM """ 844 | mac0 = self.read_reg(self.ESP_OTP_MAC0) 845 | mac1 = self.read_reg(self.ESP_OTP_MAC1) 846 | mac3 = self.read_reg(self.ESP_OTP_MAC3) 847 | if (mac3 != 0): 848 | oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff) 849 | elif ((mac1 >> 16) & 0xff) == 0: 850 | oui = (0x18, 0xfe, 0x34) 851 | elif ((mac1 >> 16) & 0xff) == 1: 852 | oui = (0xac, 0xd0, 0x74) 853 | else: 854 | raise FatalError("Unknown OUI") 855 | return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) 856 | 857 | def get_erase_size(self, offset, size): 858 | """ Calculate an erase size given a specific size in bytes. 859 | Provides a workaround for the bootloader erase bug.""" 860 | 861 | sectors_per_block = 16 862 | sector_size = self.FLASH_SECTOR_SIZE 863 | num_sectors = (size + sector_size - 1) // sector_size 864 | start_sector = offset // sector_size 865 | 866 | head_sectors = sectors_per_block - (start_sector % sectors_per_block) 867 | if num_sectors < head_sectors: 868 | head_sectors = num_sectors 869 | 870 | if num_sectors < 2 * head_sectors: 871 | return (num_sectors + 1) // 2 * sector_size 872 | else: 873 | return (num_sectors - head_sectors) * sector_size 874 | 875 | 876 | class ESP8266StubLoader(ESP8266ROM): 877 | """ Access class for ESP8266 stub loader, runs on top of ROM. 878 | """ 879 | FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c 880 | IS_STUB = True 881 | 882 | def __init__(self, rom_loader): 883 | self._port = rom_loader._port 884 | self.flush_input() # resets _slip_reader 885 | 886 | def get_erase_size(self, offset, size): 887 | return size # stub doesn't have same size bug as ROM loader 888 | 889 | 890 | ESP8266ROM.STUB_CLASS = ESP8266StubLoader 891 | 892 | 893 | class ESP32ROM(ESPLoader): 894 | """Access class for ESP32 ROM bootloader 895 | """ 896 | CHIP_NAME = "ESP32" 897 | IS_STUB = False 898 | 899 | DATE_REG_VALUE = 0x15122500 900 | 901 | IROM_MAP_START = 0x400d0000 902 | IROM_MAP_END = 0x40400000 903 | DROM_MAP_START = 0x3F400000 904 | DROM_MAP_END = 0x3F800000 905 | 906 | # ESP32 uses a 4 byte status reply 907 | STATUS_BYTES_LENGTH = 4 908 | 909 | SPI_REG_BASE = 0x60002000 910 | EFUSE_REG_BASE = 0x6001a000 911 | 912 | SPI_W0_OFFS = 0x80 913 | SPI_HAS_MOSI_DLEN_REG = True 914 | 915 | FLASH_SIZES = { 916 | '1MB':0x00, 917 | '2MB':0x10, 918 | '4MB':0x20, 919 | '8MB':0x30, 920 | '16MB':0x40 921 | } 922 | 923 | BOOTLOADER_FLASH_OFFSET = 0x1000 924 | 925 | def get_chip_description(self): 926 | blk3 = self.read_efuse(3) 927 | chip_version = (blk3 >> 12) & 0xF 928 | pkg_version = (blk3 >> 9) & 0x07 929 | 930 | silicon_rev = { 931 | 0: "0", 932 | 8: "1" 933 | }.get(chip_version, "(unknown 0x%x)" % chip_version) 934 | 935 | chip_name = { 936 | 0: "ESP32D0WDQ6", 937 | 1: "ESP32D0WDQ5", 938 | 2: "ESP32D2WDQ5", 939 | 5: "ESP32-PICO-D4", 940 | }.get(pkg_version, "unknown ESP32") 941 | 942 | return "%s (revision %s)" % (chip_name, silicon_rev) 943 | 944 | def read_efuse(self, n): 945 | """ Read the nth word of the ESP3x EFUSE region. """ 946 | return self.read_reg(self.EFUSE_REG_BASE + (4 * n)) 947 | 948 | def chip_id(self): 949 | word16 = self.read_efuse(1) 950 | word17 = self.read_efuse(2) 951 | return ((word17 & MAX_UINT24) << 24) | (word16 >> 8) & MAX_UINT24 952 | 953 | def read_mac(self): 954 | """ Read MAC from EFUSE region """ 955 | words = [self.read_efuse(2), self.read_efuse(1)] 956 | bitstring = struct.pack(">II", *words) 957 | bitstring = bitstring[2:8] # trim the 2 byte CRC 958 | try: 959 | return tuple(ord(b) for b in bitstring) 960 | except TypeError: # Python 3, bitstring elements are already bytes 961 | return tuple(bitstring) 962 | 963 | def get_erase_size(self, offset, size): 964 | return size 965 | 966 | 967 | class ESP32StubLoader(ESP32ROM): 968 | """ Access class for ESP32 stub loader, runs on top of ROM. 969 | """ 970 | FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c 971 | STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM 972 | IS_STUB = True 973 | 974 | def __init__(self, rom_loader): 975 | self._port = rom_loader._port 976 | self.flush_input() # resets _slip_reader 977 | 978 | 979 | ESP32ROM.STUB_CLASS = ESP32StubLoader 980 | 981 | 982 | class ESPBOOTLOADER(object): 983 | """ These are constants related to software ESP bootloader, working with 'v2' image files """ 984 | 985 | # First byte of the "v2" application image 986 | IMAGE_V2_MAGIC = 0xea 987 | 988 | # First 'segment' value in a "v2" application image, appears to be a constant version value? 989 | IMAGE_V2_SEGMENT = 4 990 | 991 | 992 | def LoadFirmwareImage(chip, filename): 993 | """ Load a firmware image. Can be for ESP8266 or ESP32. ESP8266 images will be examined to determine if they are 994 | original ROM firmware images (ESPFirmwareImage) or "v2" OTA bootloader images. 995 | Returns a BaseFirmwareImage subclass, either ESPFirmwareImage (v1) or OTAFirmwareImage (v2). 996 | """ 997 | with open(filename, 'rb') as f: 998 | if chip == 'esp32': 999 | return ESP32FirmwareImage(f) 1000 | else: # Otherwise, ESP8266 so look at magic to determine the image type 1001 | magic = ord(f.read(1)) 1002 | f.seek(0) 1003 | if magic == ESPLoader.ESP_IMAGE_MAGIC: 1004 | return ESPFirmwareImage(f) 1005 | elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC: 1006 | return OTAFirmwareImage(f) 1007 | else: 1008 | raise FatalError("Invalid image magic number: %d" % magic) 1009 | 1010 | 1011 | class ImageSegment(object): 1012 | """ Wrapper class for a segment in an ESP image 1013 | (very similar to a section in an ELFImage also) """ 1014 | def __init__(self, addr, data, file_offs=None): 1015 | self.addr = addr 1016 | # pad all ImageSegments to at least 4 bytes length 1017 | self.data = pad_to(data, 4, b'\x00') 1018 | self.file_offs = file_offs 1019 | self.include_in_checksum = True 1020 | 1021 | def copy_with_new_addr(self, new_addr): 1022 | """ Return a new ImageSegment with same data, but mapped at 1023 | a new address. """ 1024 | return ImageSegment(new_addr, self.data, 0) 1025 | 1026 | def split_image(self, split_len): 1027 | """ Return a new ImageSegment which splits "split_len" bytes 1028 | from the beginning of the data. Remaining bytes are kept in 1029 | this segment object (and the start address is adjusted to match.) """ 1030 | result = copy.copy(self) 1031 | result.data = self.data[:split_len] 1032 | self.data = self.data[split_len:] 1033 | self.addr += split_len 1034 | self.file_offs = None 1035 | result.file_offs = None 1036 | return result 1037 | 1038 | def __repr__(self): 1039 | r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr) 1040 | if self.file_offs is not None: 1041 | r += " file_offs 0x%08x" % (self.file_offs) 1042 | return r 1043 | 1044 | 1045 | class ELFSection(ImageSegment): 1046 | """ Wrapper class for a section in an ELF image, has a section 1047 | name as well as the common properties of an ImageSegment. """ 1048 | def __init__(self, name, addr, data): 1049 | super(ELFSection, self).__init__(addr, data) 1050 | self.name = name.decode("utf-8") 1051 | 1052 | def __repr__(self): 1053 | return "%s %s" % (self.name, super(ELFSection, self).__repr__()) 1054 | 1055 | 1056 | class BaseFirmwareImage(object): 1057 | SEG_HEADER_LEN = 8 1058 | 1059 | """ Base class with common firmware image functions """ 1060 | def __init__(self): 1061 | self.segments = [] 1062 | self.entrypoint = 0 1063 | 1064 | def load_common_header(self, load_file, expected_magic): 1065 | (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack(' 16: 1068 | raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments)) 1069 | return segments 1070 | 1071 | def load_segment(self, f, is_irom_segment=False): 1072 | """ Load the next segment from the image file """ 1073 | file_offs = f.tell() 1074 | (offset, size) = struct.unpack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536: 1086 | print('WARNING: Suspicious segment 0x%x, length %d' % (offset, size)) 1087 | 1088 | def save_segment(self, f, segment, checksum=None): 1089 | """ Save the next segment to the image file, return next checksum value if provided """ 1090 | f.write(struct.pack(' 0: 1130 | if len(irom_segments) != 1: 1131 | raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments)) 1132 | return irom_segments[0] 1133 | return None 1134 | 1135 | def get_non_irom_segments(self): 1136 | irom_segment = self.get_irom_segment() 1137 | return [s for s in self.segments if s != irom_segment] 1138 | 1139 | 1140 | class ESPFirmwareImage(BaseFirmwareImage): 1141 | """ 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """ 1142 | 1143 | ROM_LOADER = ESP8266ROM 1144 | 1145 | def __init__(self, load_file=None): 1146 | super(ESPFirmwareImage, self).__init__() 1147 | self.flash_mode = 0 1148 | self.flash_size_freq = 0 1149 | self.version = 1 1150 | 1151 | if load_file is not None: 1152 | segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC) 1153 | 1154 | for _ in range(segments): 1155 | self.load_segment(load_file) 1156 | self.checksum = self.read_checksum(load_file) 1157 | 1158 | def default_output_name(self, input_file): 1159 | """ Derive a default output name from the ELF name. """ 1160 | return input_file + '-' 1161 | 1162 | def save(self, basename): 1163 | """ Save a set of V1 images for flashing. Parameter is a base filename. """ 1164 | # IROM data goes in its own plain binary file 1165 | irom_segment = self.get_irom_segment() 1166 | if irom_segment is not None: 1167 | with open("%s0x%05x.bin" % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START), "wb") as f: 1168 | f.write(irom_segment.data) 1169 | 1170 | # everything but IROM goes at 0x00000 in an image file 1171 | normal_segments = self.get_non_irom_segments() 1172 | with open("%s0x00000.bin" % basename, 'wb') as f: 1173 | self.write_common_header(f, normal_segments) 1174 | checksum = ESPLoader.ESP_CHECKSUM_MAGIC 1175 | for segment in normal_segments: 1176 | checksum = self.save_segment(f, segment, checksum) 1177 | self.append_checksum(f, checksum) 1178 | 1179 | 1180 | class OTAFirmwareImage(BaseFirmwareImage): 1181 | """ 'Version 2' firmware image, segments loaded by software bootloader stub 1182 | (ie Espressif bootloader or rboot) 1183 | """ 1184 | 1185 | ROM_LOADER = ESP8266ROM 1186 | 1187 | def __init__(self, load_file=None): 1188 | super(OTAFirmwareImage, self).__init__() 1189 | self.version = 2 1190 | if load_file is not None: 1191 | segments = self.load_common_header(load_file, ESPBOOTLOADER.IMAGE_V2_MAGIC) 1192 | if segments != ESPBOOTLOADER.IMAGE_V2_SEGMENT: 1193 | # segment count is not really segment count here, but we expect to see '4' 1194 | print('Warning: V2 header has unexpected "segment" count %d (usually 4)' % segments) 1195 | 1196 | # irom segment comes before the second header 1197 | # 1198 | # the file is saved in the image with a zero load address 1199 | # in the header, so we need to calculate a load address 1200 | irom_segment = self.load_segment(load_file, True) 1201 | # for actual mapped addr, add ESP8266ROM.IROM_MAP_START + flashing_Addr + 8 1202 | irom_segment.addr = 0 1203 | irom_segment.include_in_checksum = False 1204 | 1205 | first_flash_mode = self.flash_mode 1206 | first_flash_size_freq = self.flash_size_freq 1207 | first_entrypoint = self.entrypoint 1208 | # load the second header 1209 | 1210 | segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC) 1211 | 1212 | if first_flash_mode != self.flash_mode: 1213 | print('WARNING: Flash mode value in first header (0x%02x) disagrees with second (0x%02x). Using second value.' 1214 | % (first_flash_mode, self.flash_mode)) 1215 | if first_flash_size_freq != self.flash_size_freq: 1216 | print('WARNING: Flash size/freq value in first header (0x%02x) disagrees with second (0x%02x). Using second value.' 1217 | % (first_flash_size_freq, self.flash_size_freq)) 1218 | if first_entrypoint != self.entrypoint: 1219 | print('WARNING: Entrypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.' 1220 | % (first_entrypoint, self.entrypoint)) 1221 | 1222 | # load all the usual segments 1223 | for _ in range(segments): 1224 | self.load_segment(load_file) 1225 | self.checksum = self.read_checksum(load_file) 1226 | 1227 | def default_output_name(self, input_file): 1228 | """ Derive a default output name from the ELF name. """ 1229 | irom_segment = self.get_irom_segment() 1230 | if irom_segment is not None: 1231 | irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START 1232 | else: 1233 | irom_offs = 0 1234 | return "%s-0x%05x.bin" % (os.path.splitext(input_file)[0], 1235 | irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) 1236 | 1237 | def save(self, filename): 1238 | with open(filename, 'wb') as f: 1239 | # Save first header for irom0 segment 1240 | f.write(struct.pack(b' 0: 1342 | last_addr = flash_segments[0].addr 1343 | for segment in flash_segments[1:]: 1344 | if segment.addr // IROM_ALIGN == last_addr // IROM_ALIGN: 1345 | raise FatalError(("Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " + 1346 | "Can't generate binary. Suggest changing linker script or ELF to merge sections.") % 1347 | (segment.addr, last_addr)) 1348 | last_addr = segment.addr 1349 | 1350 | def get_alignment_data_needed(segment): 1351 | # Actual alignment (in data bytes) required for a segment header: positioned so that 1352 | # after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN 1353 | # 1354 | # (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned 1355 | # IROM_ALIGN+0x18 to account for the binary file header 1356 | align_past = (segment.addr % IROM_ALIGN) - self.SEG_HEADER_LEN 1357 | pad_len = (IROM_ALIGN - (f.tell() % IROM_ALIGN)) + align_past 1358 | if pad_len == 0 or pad_len == IROM_ALIGN: 1359 | return 0 # already aligned 1360 | 1361 | # subtract SEG_HEADER_LEN a second time, as the padding block has a header as well 1362 | pad_len -= self.SEG_HEADER_LEN 1363 | if pad_len < 0: 1364 | pad_len += IROM_ALIGN 1365 | return pad_len 1366 | 1367 | # try to fit each flash segment on a 64kB aligned boundary 1368 | # by padding with parts of the non-flash segments... 1369 | while len(flash_segments) > 0: 1370 | segment = flash_segments[0] 1371 | pad_len = get_alignment_data_needed(segment) 1372 | if pad_len > 0: # need to pad 1373 | if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN: 1374 | pad_segment = ram_segments[0].split_image(pad_len) 1375 | if len(ram_segments[0].data) == 0: 1376 | ram_segments.pop(0) 1377 | else: 1378 | pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell()) 1379 | checksum = self.save_segment(f, pad_segment, checksum) 1380 | total_segments += 1 1381 | else: 1382 | # write the flash segment 1383 | assert (f.tell() + 8) % IROM_ALIGN == segment.addr % IROM_ALIGN 1384 | checksum = self.save_segment(f, segment, checksum) 1385 | flash_segments.pop(0) 1386 | total_segments += 1 1387 | 1388 | # flash segments all written, so write any remaining RAM segments 1389 | for segment in ram_segments: 1390 | checksum = self.save_segment(f, segment, checksum) 1391 | total_segments += 1 1392 | 1393 | # done writing segments 1394 | self.append_checksum(f, checksum) 1395 | # kinda hacky: go back to the initial header and write the new segment count 1396 | # that includes padding segments. This header is not checksummed 1397 | image_length = f.tell() 1398 | f.seek(1) 1399 | try: 1400 | f.write(chr(total_segments)) 1401 | except TypeError: # Python 3 1402 | f.write(bytes([total_segments])) 1403 | 1404 | if self.append_digest: 1405 | # calculate the SHA256 of the whole file and append it 1406 | f.seek(0) 1407 | digest = hashlib.sha256() 1408 | digest.update(f.read(image_length)) 1409 | f.write(digest.digest()) 1410 | 1411 | with open(filename, 'wb') as real_file: 1412 | real_file.write(f.getvalue()) 1413 | 1414 | def load_extended_header(self, load_file): 1415 | def split_byte(n): 1416 | return (n & 0x0F, (n >> 4) & 0x0F) 1417 | 1418 | fields = list(struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16))) 1419 | 1420 | self.wp_pin = fields[0] 1421 | 1422 | # SPI pin drive stengths are two per byte 1423 | self.clk_drv, self.q_drv = split_byte(fields[1]) 1424 | self.d_drv, self.cs_drv = split_byte(fields[2]) 1425 | self.hd_drv, self.wp_drv = split_byte(fields[3]) 1426 | 1427 | if fields[15] in [0, 1]: 1428 | self.append_digest = (fields[15] == 1) 1429 | else: 1430 | raise RuntimeError("Invalid value for append_digest field (0x%02x). Should be 0 or 1.", fields[15]) 1431 | 1432 | # remaining fields in the middle should all be zero 1433 | if any(f for f in fields[4:15] if f != 0): 1434 | print("Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?") 1435 | 1436 | def save_extended_header(self, save_file): 1437 | def join_byte(ln,hn): 1438 | return (ln & 0x0F) + ((hn & 0x0F) << 4) 1439 | 1440 | append_digest = 1 if self.append_digest else 0 1441 | 1442 | fields = [self.wp_pin, 1443 | join_byte(self.clk_drv, self.q_drv), 1444 | join_byte(self.d_drv, self.cs_drv), 1445 | join_byte(self.hd_drv, self.wp_drv)] 1446 | fields += [0] * 11 1447 | fields += [append_digest] 1448 | 1449 | packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields) 1450 | save_file.write(packed) 1451 | 1452 | 1453 | class ELFFile(object): 1454 | SEC_TYPE_PROGBITS = 0x01 1455 | SEC_TYPE_STRTAB = 0x03 1456 | 1457 | LEN_SEC_HEADER = 0x28 1458 | 1459 | def __init__(self, name): 1460 | # Load sections from the ELF file 1461 | self.name = name 1462 | with open(self.name, 'rb') as f: 1463 | self._read_elf_file(f) 1464 | 1465 | def get_section(self, section_name): 1466 | for s in self.sections: 1467 | if s.name == section_name: 1468 | return s 1469 | raise ValueError("No section %s in ELF file" % section_name) 1470 | 1471 | def _read_elf_file(self, f): 1472 | # read the ELF file header 1473 | LEN_FILE_HEADER = 0x34 1474 | try: 1475 | (ident,_type,machine,_version, 1476 | self.entrypoint,_phoff,shoff,_flags, 1477 | _ehsize, _phentsize,_phnum, shentsize, 1478 | shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER)) 1479 | except struct.error as e: 1480 | raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e)) 1481 | 1482 | if byte(ident, 0) != 0x7f or ident[1:4] != b'ELF': 1483 | raise FatalError("%s has invalid ELF magic header" % self.name) 1484 | if machine != 0x5e: 1485 | raise FatalError("%s does not appear to be an Xtensa ELF file. e_machine=%04x" % (self.name, machine)) 1486 | if shentsize != self.LEN_SEC_HEADER: 1487 | raise FatalError("%s has unexpected section header entry size 0x%x (not 0x28)" % (self.name, shentsize, self.LEN_SEC_HEADER)) 1488 | if shnum == 0: 1489 | raise FatalError("%s has 0 section headers" % (self.name)) 1490 | self._read_sections(f, shoff, shnum, shstrndx) 1491 | 1492 | def _read_sections(self, f, section_header_offs, section_header_count, shstrndx): 1493 | f.seek(section_header_offs) 1494 | len_bytes = section_header_count * self.LEN_SEC_HEADER 1495 | section_header = f.read(len_bytes) 1496 | if len(section_header) == 0: 1497 | raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs) 1498 | if len(section_header) != (len_bytes): 1499 | raise FatalError("Only read 0x%x bytes from section header (expected 0x%x.) Truncated ELF file?" % (len(section_header), len_bytes)) 1500 | 1501 | # walk through the section header and extract all sections 1502 | section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER) 1503 | 1504 | def read_section_header(offs): 1505 | name_offs,sec_type,_flags,lma,sec_offs,size = struct.unpack_from(", ) or a single 1662 | # argument. 1663 | 1664 | 1665 | def load_ram(esp, args): 1666 | image = LoadFirmwareImage(esp, args.filename) 1667 | 1668 | print('RAM boot...') 1669 | for (offset, size, data) in image.segments: 1670 | print('Downloading %d bytes at %08x...' % (size, offset), end=' ') 1671 | sys.stdout.flush() 1672 | esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, offset) 1673 | 1674 | seq = 0 1675 | while len(data) > 0: 1676 | esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq) 1677 | data = data[esp.ESP_RAM_BLOCK:] 1678 | seq += 1 1679 | print('done!') 1680 | 1681 | print('All segments done, executing at %08x' % image.entrypoint) 1682 | esp.mem_finish(image.entrypoint) 1683 | 1684 | 1685 | def read_mem(esp, args): 1686 | print('0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))) 1687 | 1688 | 1689 | def write_mem(esp, args): 1690 | esp.write_reg(args.address, args.value, args.mask, 0) 1691 | print('Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)) 1692 | 1693 | 1694 | def dump_mem(esp, args): 1695 | f = open(args.filename, 'wb') 1696 | for i in range(args.size // 4): 1697 | d = esp.read_reg(args.address + (i * 4)) 1698 | f.write(struct.pack(b'> 16 1711 | args.flash_size = DETECTED_FLASH_SIZES.get(size_id) 1712 | if args.flash_size is None: 1713 | print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4MB' % (flash_id, size_id)) 1714 | args.flash_size = '4MB' 1715 | else: 1716 | print('Auto-detected Flash size:', args.flash_size) 1717 | 1718 | 1719 | def _update_image_flash_params(esp, address, args, image): 1720 | """ Modify the flash mode & size bytes if this looks like an executable bootloader image """ 1721 | if len(image) < 8: 1722 | return image # not long enough to be a bootloader image 1723 | 1724 | # unpack the (potential) image header 1725 | magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4]) 1726 | if address != esp.BOOTLOADER_FLASH_OFFSET or magic != esp.ESP_IMAGE_MAGIC: 1727 | return image # not flashing a bootloader, so don't modify this 1728 | 1729 | if args.flash_mode != 'keep': 1730 | flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] 1731 | 1732 | flash_freq = flash_size_freq & 0x0F 1733 | if args.flash_freq != 'keep': 1734 | flash_freq = {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] 1735 | 1736 | flash_size = flash_size_freq & 0xF0 1737 | if args.flash_size != 'keep': 1738 | flash_size = esp.parse_flash_size_arg(args.flash_size) 1739 | 1740 | flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq) 1741 | if flash_params != image[2:4]: 1742 | print('Flash params set to 0x%04x' % struct.unpack(">H", flash_params)) 1743 | image = image[0:2] + flash_params + image[4:] 1744 | return image 1745 | 1746 | 1747 | def write_flash(esp, args): 1748 | # set args.compress based on default behaviour: 1749 | # -> if either --compress or --no-compress is set, honour that 1750 | # -> otherwise, set --compress unless --no-stub is set 1751 | if args.compress is None and not args.no_compress: 1752 | args.compress = not args.no_stub 1753 | 1754 | # verify file sizes fit in flash 1755 | flash_end = flash_size_bytes(args.flash_size) 1756 | for address, argfile in args.addr_filename: 1757 | argfile.seek(0,2) # seek to end 1758 | if address + argfile.tell() > flash_end: 1759 | raise FatalError(("File %s (length %d) at offset %d will not fit in %d bytes of flash. " + 1760 | "Use --flash-size argument, or change flashing address.") 1761 | % (argfile.name, argfile.tell(), address, flash_end)) 1762 | argfile.seek(0) 1763 | 1764 | for address, argfile in args.addr_filename: 1765 | if args.no_stub: 1766 | print('Erasing flash...') 1767 | image = pad_to(argfile.read(), 4) 1768 | image = _update_image_flash_params(esp, address, args, image) 1769 | calcmd5 = hashlib.md5(image).hexdigest() 1770 | uncsize = len(image) 1771 | if args.compress: 1772 | uncimage = image 1773 | image = zlib.compress(uncimage, 9) 1774 | ratio = uncsize / len(image) 1775 | blocks = esp.flash_defl_begin(uncsize, len(image), address) 1776 | else: 1777 | ratio = 1.0 1778 | blocks = esp.flash_begin(uncsize, address) 1779 | argfile.seek(0) # in case we need it again 1780 | seq = 0 1781 | written = 0 1782 | t = time.time() 1783 | esp._port.timeout = min(DEFAULT_TIMEOUT * ratio, 1784 | CHIP_ERASE_TIMEOUT * 2) 1785 | while len(image) > 0: 1786 | print('\rWriting at 0x%08x... (%d %%)' % (address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks), end='') 1787 | sys.stdout.flush() 1788 | block = image[0:esp.FLASH_WRITE_SIZE] 1789 | if args.compress: 1790 | esp.flash_defl_block(block, seq) 1791 | else: 1792 | # Pad the last block 1793 | block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) 1794 | esp.flash_block(block, seq) 1795 | image = image[esp.FLASH_WRITE_SIZE:] 1796 | seq += 1 1797 | written += len(block) 1798 | t = time.time() - t 1799 | speed_msg = "" 1800 | if args.compress: 1801 | if t > 0.0: 1802 | speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000) 1803 | print('\rWrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, written, address, t, speed_msg)) 1804 | else: 1805 | if t > 0.0: 1806 | speed_msg = " (%.1f kbit/s)" % (written / t * 8 / 1000) 1807 | print('\rWrote %d bytes at 0x%08x in %.1f seconds%s...' % (written, address, t, speed_msg)) 1808 | try: 1809 | res = esp.flash_md5sum(address, uncsize) 1810 | if res != calcmd5: 1811 | print('File md5: %s' % calcmd5) 1812 | print('Flash md5: %s' % res) 1813 | print('MD5 of 0xFF is %s' % (hashlib.md5(b'\xFF' * uncsize).hexdigest())) 1814 | raise FatalError("MD5 of file does not match data in flash!") 1815 | else: 1816 | print('Hash of data verified.') 1817 | except NotImplementedInROMError: 1818 | pass 1819 | esp._port.timeout = DEFAULT_TIMEOUT 1820 | 1821 | print('\nLeaving...') 1822 | 1823 | if esp.IS_STUB: 1824 | # skip sending flash_finish to ROM loader here, 1825 | # as it causes the loader to exit and run user code 1826 | esp.flash_begin(0, 0) 1827 | if args.compress: 1828 | esp.flash_defl_finish(False) 1829 | else: 1830 | esp.flash_finish(False) 1831 | 1832 | if args.verify: 1833 | print('Verifying just-written flash...') 1834 | print('(This option is deprecated, flash contents are now always read back after flashing.)') 1835 | verify_flash(esp, args) 1836 | 1837 | 1838 | def image_info(args): 1839 | image = LoadFirmwareImage(args.chip, args.filename) 1840 | print('Image version: %d' % image.version) 1841 | print('Entry point: %08x' % image.entrypoint if image.entrypoint != 0 else 'Entry point not set') 1842 | print('%d segments' % len(image.segments)) 1843 | print 1844 | idx = 0 1845 | for seg in image.segments: 1846 | idx += 1 1847 | print('Segment %d: %r' % (idx, seg)) 1848 | calc_checksum = image.calculate_checksum() 1849 | print('Checksum: %02x (%s)' % (image.checksum, 1850 | 'valid' if image.checksum == calc_checksum else 'invalid - calculated %02x' % calc_checksum)) 1851 | try: 1852 | digest_msg = 'Not appended' 1853 | if image.append_digest: 1854 | is_valid = image.stored_digest == image.calc_digest 1855 | digest_msg = "%s (%s)" % (hexify(image.calc_digest).lower(), 1856 | "valid" if is_valid else "invalid") 1857 | print('Validation Hash: %s' % digest_msg) 1858 | except AttributeError: 1859 | pass # ESP8266 image has no append_digest field 1860 | 1861 | 1862 | def make_image(args): 1863 | image = ESPFirmwareImage() 1864 | if len(args.segfile) == 0: 1865 | raise FatalError('No segments specified') 1866 | if len(args.segfile) != len(args.segaddr): 1867 | raise FatalError('Number of specified files does not match number of specified addresses') 1868 | for (seg, addr) in zip(args.segfile, args.segaddr): 1869 | data = open(seg, 'rb').read() 1870 | image.segments.append(ImageSegment(addr, data)) 1871 | image.entrypoint = args.entrypoint 1872 | image.save(args.output) 1873 | 1874 | 1875 | def elf2image(args): 1876 | e = ELFFile(args.input) 1877 | if args.chip == 'auto': # Default to ESP8266 for backwards compatibility 1878 | print("Creating image for ESP8266...") 1879 | args.chip = 'esp8266' 1880 | 1881 | if args.chip == 'esp32': 1882 | image = ESP32FirmwareImage() 1883 | elif args.version == '1': # ESP8266 1884 | image = ESPFirmwareImage() 1885 | else: 1886 | image = OTAFirmwareImage() 1887 | image.entrypoint = e.entrypoint 1888 | image.segments = e.sections # ELFSection is a subclass of ImageSegment 1889 | image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] 1890 | image.flash_size_freq = image.ROM_LOADER.FLASH_SIZES[args.flash_size] 1891 | image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] 1892 | 1893 | if args.output is None: 1894 | args.output = image.default_output_name(args.input) 1895 | image.save(args.output) 1896 | 1897 | 1898 | def read_mac(esp, args): 1899 | mac = esp.read_mac() 1900 | 1901 | def print_mac(label, mac): 1902 | print('%s: %s' % (label, ':'.join(map(lambda x: '%02x' % x, mac)))) 1903 | print_mac("MAC", mac) 1904 | 1905 | 1906 | def chip_id(esp, args): 1907 | chipid = esp.chip_id() 1908 | print('Chip ID: 0x%08x' % chipid) 1909 | 1910 | 1911 | def erase_flash(esp, args): 1912 | print('Erasing flash (this may take a while)...') 1913 | t = time.time() 1914 | esp.erase_flash() 1915 | print('Chip erase completed successfully in %.1fs' % (time.time() - t)) 1916 | 1917 | 1918 | def erase_region(esp, args): 1919 | print('Erasing region (may be slow depending on size)...') 1920 | t = time.time() 1921 | esp.erase_region(args.address, args.size) 1922 | print('Erase completed successfully in %.1f seconds.' % (time.time() - t)) 1923 | 1924 | 1925 | def run(esp, args): 1926 | esp.run() 1927 | 1928 | 1929 | def flash_id(esp, args): 1930 | flash_id = esp.flash_id() 1931 | print('Manufacturer: %02x' % (flash_id & 0xff)) 1932 | flid_lowbyte = (flash_id >> 16) & 0xFF 1933 | print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, flid_lowbyte)) 1934 | print('Detected flash size: %s' % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))) 1935 | 1936 | 1937 | def read_flash(esp, args): 1938 | if args.no_progress: 1939 | flash_progress = None 1940 | else: 1941 | def flash_progress(progress, length): 1942 | msg = '%d (%d %%)' % (progress, progress * 100.0 / length) 1943 | padding = '\b' * len(msg) 1944 | if progress == length: 1945 | padding = '\n' 1946 | sys.stdout.write(msg + padding) 1947 | sys.stdout.flush() 1948 | t = time.time() 1949 | data = esp.read_flash(args.address, args.size, flash_progress) 1950 | t = time.time() - t 1951 | print('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' 1952 | % (len(data), args.address, t, len(data) / t * 8 / 1000)) 1953 | open(args.filename, 'wb').write(data) 1954 | 1955 | 1956 | def verify_flash(esp, args): 1957 | differences = False 1958 | 1959 | for address, argfile in args.addr_filename: 1960 | image = pad_to(argfile.read(), 4) 1961 | argfile.seek(0) # rewind in case we need it again 1962 | 1963 | image = _update_image_flash_params(esp, address, args, image) 1964 | 1965 | image_size = len(image) 1966 | print('Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name)) 1967 | # Try digest first, only read if there are differences. 1968 | digest = esp.flash_md5sum(address, image_size) 1969 | expected_digest = hashlib.md5(image).hexdigest() 1970 | if digest == expected_digest: 1971 | print('-- verify OK (digest matched)') 1972 | continue 1973 | else: 1974 | differences = True 1975 | if getattr(args, 'diff', 'no') != 'yes': 1976 | print('-- verify FAILED (digest mismatch)') 1977 | continue 1978 | 1979 | flash = esp.read_flash(address, image_size) 1980 | assert flash != image 1981 | diff = [i for i in range(image_size) if flash[i] != image[i]] 1982 | print('-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0])) 1983 | for d in diff: 1984 | flash_byte = flash[d] 1985 | image_byte = image[d] 1986 | if PYTHON2: 1987 | flash_byte = ord(flash_byte) 1988 | image_byte = ord(image_byte) 1989 | print(' %08x %02x %02x' % (address + d, flash_byte, image_byte)) 1990 | if differences: 1991 | raise FatalError("Verify failed.") 1992 | 1993 | 1994 | def read_flash_status(esp, args): 1995 | print('Status value: 0x%04x' % esp.read_status(args.bytes)) 1996 | 1997 | 1998 | def write_flash_status(esp, args): 1999 | fmt = "0x%%0%dx" % (args.bytes * 2) 2000 | args.value = args.value & ((1 << (args.bytes * 8)) - 1) 2001 | print(('Initial flash status: ' + fmt) % esp.read_status(args.bytes)) 2002 | print(('Setting flash status: ' + fmt) % args.value) 2003 | esp.write_status(args.value, args.bytes, args.non_volatile) 2004 | print(('After flash status: ' + fmt) % esp.read_status(args.bytes)) 2005 | 2006 | 2007 | def version(args): 2008 | print(__version__) 2009 | 2010 | # 2011 | # End of operations functions 2012 | # 2013 | 2014 | 2015 | def main(): 2016 | parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool') 2017 | 2018 | parser.add_argument('--chip', '-c', 2019 | help='Target chip type', 2020 | choices=['auto', 'esp8266', 'esp32'], 2021 | default=os.environ.get('ESPTOOL_CHIP', 'auto')) 2022 | 2023 | parser.add_argument( 2024 | '--port', '-p', 2025 | help='Serial port device', 2026 | default=os.environ.get('ESPTOOL_PORT', ESPLoader.DEFAULT_PORT)) 2027 | 2028 | parser.add_argument( 2029 | '--baud', '-b', 2030 | help='Serial port baud rate used when flashing/reading', 2031 | type=arg_auto_int, 2032 | default=os.environ.get('ESPTOOL_BAUD', ESPLoader.ESP_ROM_BAUD)) 2033 | 2034 | parser.add_argument( 2035 | '--before', 2036 | help='What to do before connecting to the chip', 2037 | choices=['default_reset', 'no_reset'], 2038 | default=os.environ.get('ESPTOOL_BEFORE', 'default_reset')) 2039 | 2040 | parser.add_argument( 2041 | '--after', '-a', 2042 | help='What to do after esptool.py is finished', 2043 | choices=['hard_reset', 'soft_reset', 'no_reset'], 2044 | default=os.environ.get('ESPTOOL_AFTER', 'hard_reset')) 2045 | 2046 | parser.add_argument( 2047 | '--no-stub', 2048 | help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.", 2049 | action='store_true') 2050 | 2051 | subparsers = parser.add_subparsers( 2052 | dest='operation', 2053 | help='Run esptool {command} -h for additional help') 2054 | 2055 | def add_spi_connection_arg(parent): 2056 | parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. Override default SPI Flash connection. ' + 2057 | 'Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS).', 2058 | action=SpiConnectionAction) 2059 | 2060 | parser_load_ram = subparsers.add_parser( 2061 | 'load_ram', 2062 | help='Download an image to RAM and execute') 2063 | parser_load_ram.add_argument('filename', help='Firmware image') 2064 | 2065 | parser_dump_mem = subparsers.add_parser( 2066 | 'dump_mem', 2067 | help='Dump arbitrary memory to disk') 2068 | parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int) 2069 | parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int) 2070 | parser_dump_mem.add_argument('filename', help='Name of binary dump') 2071 | 2072 | parser_read_mem = subparsers.add_parser( 2073 | 'read_mem', 2074 | help='Read arbitrary memory location') 2075 | parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int) 2076 | 2077 | parser_write_mem = subparsers.add_parser( 2078 | 'write_mem', 2079 | help='Read-modify-write to arbitrary memory location') 2080 | parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int) 2081 | parser_write_mem.add_argument('value', help='Value', type=arg_auto_int) 2082 | parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int) 2083 | 2084 | def add_spi_flash_subparsers(parent, is_elf2image): 2085 | """ Add common parser arguments for SPI flash properties """ 2086 | extra_keep_args = [] if is_elf2image else ['keep'] 2087 | auto_detect = not is_elf2image 2088 | 2089 | parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency', 2090 | choices=extra_keep_args + ['40m', '26m', '20m', '80m'], 2091 | default=os.environ.get('ESPTOOL_FF', '40m' if is_elf2image else 'keep')) 2092 | parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode', 2093 | choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'], 2094 | default=os.environ.get('ESPTOOL_FM', 'qio' if is_elf2image else 'keep')) 2095 | parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16M)' 2096 | ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)', 2097 | action=FlashSizeAction, auto_detect=auto_detect, 2098 | default=os.environ.get('ESPTOOL_FS', 'detect' if auto_detect else '1MB')) 2099 | add_spi_connection_arg(parent) 2100 | 2101 | parser_write_flash = subparsers.add_parser( 2102 | 'write_flash', 2103 | help='Write a binary blob to flash') 2104 | parser_write_flash.add_argument('addr_filename', metavar='
', help='Address followed by binary filename, separated by space', 2105 | action=AddrFilenamePairAction) 2106 | add_spi_flash_subparsers(parser_write_flash, is_elf2image=False) 2107 | parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") 2108 | parser_write_flash.add_argument('--verify', help='Verify just-written data on flash ' + 2109 | '(mostly superfluous, data is read back during flashing)', action='store_true') 2110 | compress_args = parser_write_flash.add_mutually_exclusive_group(required=False) 2111 | compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)',action="store_true", default=None) 2112 | compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)',action="store_true") 2113 | 2114 | subparsers.add_parser( 2115 | 'run', 2116 | help='Run application code in flash') 2117 | 2118 | parser_image_info = subparsers.add_parser( 2119 | 'image_info', 2120 | help='Dump headers from an application image') 2121 | parser_image_info.add_argument('filename', help='Image file to parse') 2122 | 2123 | parser_make_image = subparsers.add_parser( 2124 | 'make_image', 2125 | help='Create an application image from binary files') 2126 | parser_make_image.add_argument('output', help='Output image file') 2127 | parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file') 2128 | parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int) 2129 | parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0) 2130 | 2131 | parser_elf2image = subparsers.add_parser( 2132 | 'elf2image', 2133 | help='Create an application image from ELF file') 2134 | parser_elf2image.add_argument('input', help='Input ELF file') 2135 | parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str) 2136 | parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2'], default='1') 2137 | 2138 | add_spi_flash_subparsers(parser_elf2image, is_elf2image=True) 2139 | 2140 | subparsers.add_parser( 2141 | 'read_mac', 2142 | help='Read MAC address from OTP ROM') 2143 | 2144 | subparsers.add_parser( 2145 | 'chip_id', 2146 | help='Read Chip ID from OTP ROM') 2147 | 2148 | parser_flash_id = subparsers.add_parser( 2149 | 'flash_id', 2150 | help='Read SPI flash manufacturer and device ID') 2151 | add_spi_connection_arg(parser_flash_id) 2152 | 2153 | parser_read_status = subparsers.add_parser( 2154 | 'read_flash_status', 2155 | help='Read SPI flash status register') 2156 | 2157 | add_spi_connection_arg(parser_read_status) 2158 | parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1,2,3], default=2) 2159 | 2160 | parser_write_status = subparsers.add_parser( 2161 | 'write_flash_status', 2162 | help='Write SPI flash status register') 2163 | 2164 | add_spi_connection_arg(parser_write_status) 2165 | parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true') 2166 | parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1,2,3], default=2) 2167 | parser_write_status.add_argument('value', help='New value', type=arg_auto_int) 2168 | 2169 | parser_read_flash = subparsers.add_parser( 2170 | 'read_flash', 2171 | help='Read SPI flash content') 2172 | add_spi_connection_arg(parser_read_flash) 2173 | parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int) 2174 | parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int) 2175 | parser_read_flash.add_argument('filename', help='Name of binary dump') 2176 | parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") 2177 | 2178 | parser_verify_flash = subparsers.add_parser( 2179 | 'verify_flash', 2180 | help='Verify a binary blob against flash') 2181 | parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space', 2182 | action=AddrFilenamePairAction) 2183 | parser_verify_flash.add_argument('--diff', '-d', help='Show differences', 2184 | choices=['no', 'yes'], default='no') 2185 | add_spi_flash_subparsers(parser_verify_flash, is_elf2image=False) 2186 | 2187 | parser_erase_flash = subparsers.add_parser( 2188 | 'erase_flash', 2189 | help='Perform Chip Erase on SPI flash') 2190 | add_spi_connection_arg(parser_erase_flash) 2191 | 2192 | parser_erase_region = subparsers.add_parser( 2193 | 'erase_region', 2194 | help='Erase a region of the flash') 2195 | add_spi_connection_arg(parser_erase_region) 2196 | parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int) 2197 | parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int) 2198 | 2199 | subparsers.add_parser( 2200 | 'version', help='Print esptool version') 2201 | 2202 | # internal sanity check - every operation matches a module function of the same name 2203 | for operation in subparsers.choices.keys(): 2204 | assert operation in globals(), "%s should be a module function" % operation 2205 | 2206 | expand_file_arguments() 2207 | 2208 | args = parser.parse_args() 2209 | 2210 | print('esptool.py v%s' % __version__) 2211 | 2212 | # operation function can take 1 arg (args), 2 args (esp, arg) 2213 | # or be a member function of the ESPLoader class. 2214 | 2215 | if args.operation is None: 2216 | parser.print_help() 2217 | sys.exit(1) 2218 | 2219 | operation_func = globals()[args.operation] 2220 | 2221 | if PYTHON2: 2222 | # This function is depreciated in Python3 2223 | operation_args = inspect.getargspec(operation_func).args 2224 | else: 2225 | operation_args = inspect.getfullargspec(operation_func).args 2226 | 2227 | if operation_args[0] == 'esp': # operation function takes an ESPLoader connection object 2228 | initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate 2229 | if args.chip == 'auto': 2230 | esp = ESPLoader.detect_chip(args.port, initial_baud, args.before) 2231 | else: 2232 | chip_class = { 2233 | 'esp8266': ESP8266ROM, 2234 | 'esp32': ESP32ROM, 2235 | }[args.chip] 2236 | esp = chip_class(args.port, initial_baud) 2237 | esp.connect(args.before) 2238 | 2239 | print("Chip is %s" % (esp.get_chip_description())) 2240 | 2241 | if not args.no_stub: 2242 | esp = esp.run_stub() 2243 | 2244 | if args.baud > initial_baud: 2245 | try: 2246 | esp.change_baud(args.baud) 2247 | except NotImplementedInROMError: 2248 | print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d" % initial_baud) 2249 | 2250 | # override common SPI flash parameter stuff if configured to do so 2251 | if hasattr(args, "spi_connection") and args.spi_connection is not None: 2252 | if esp.CHIP_NAME != "ESP32": 2253 | raise FatalError("Chip %s does not support --spi-connection option." % esp.CHIP_NAME) 2254 | print("Configuring SPI flash mode...") 2255 | esp.flash_spi_attach(args.spi_connection) 2256 | elif args.no_stub: 2257 | print("Enabling default SPI flash mode...") 2258 | # ROM loader doesn't enable flash unless we explicitly do it 2259 | esp.flash_spi_attach(0) 2260 | 2261 | if hasattr(args, "flash_size"): 2262 | print("Configuring flash size...") 2263 | detect_flash_size(esp, args) 2264 | esp.flash_set_parameters(flash_size_bytes(args.flash_size)) 2265 | 2266 | operation_func(esp, args) 2267 | 2268 | # finish execution based on args.after 2269 | if args.after == 'hard_reset': 2270 | print('Hard resetting...') 2271 | esp.hard_reset() 2272 | elif args.after == 'soft_reset': 2273 | print('Soft resetting...') 2274 | # flash_finish will trigger a soft reset 2275 | esp.soft_reset(False) 2276 | else: 2277 | print('Staying in bootloader.') 2278 | if esp.IS_STUB: 2279 | esp.soft_reset(True) # exit stub back to ROM loader 2280 | 2281 | else: 2282 | operation_func(args) 2283 | 2284 | 2285 | def expand_file_arguments(): 2286 | """ Any argument starting with "@" gets replaced with all values read from a text file. 2287 | Text file arguments can be split by newline or by space. 2288 | Values are added "as-is", as if they were specified in this order on the command line. 2289 | """ 2290 | new_args = [] 2291 | expanded = False 2292 | for arg in sys.argv: 2293 | if arg.startswith("@"): 2294 | expanded = True 2295 | with open(arg[1:],"r") as f: 2296 | for line in f.readlines(): 2297 | new_args += shlex.split(line) 2298 | else: 2299 | new_args.append(arg) 2300 | if expanded: 2301 | print("esptool.py %s" % (" ".join(new_args[1:]))) 2302 | sys.argv = new_args 2303 | 2304 | 2305 | class FlashSizeAction(argparse.Action): 2306 | """ Custom flash size parser class to support backwards compatibility with megabit size arguments. 2307 | (At next major relase, remove deprecated sizes and this can become a 'normal' choices= argument again.) 2308 | """ 2309 | def __init__(self, option_strings, dest, nargs=1, auto_detect=False, **kwargs): 2310 | super(FlashSizeAction, self).__init__(option_strings, dest, nargs, **kwargs) 2311 | self._auto_detect = auto_detect 2312 | 2313 | def __call__(self, parser, namespace, values, option_string=None): 2314 | try: 2315 | value = { 2316 | '2m': '256KB', 2317 | '4m': '512KB', 2318 | '8m': '1MB', 2319 | '16m': '2MB', 2320 | '32m': '4MB', 2321 | '16m-c1': '2MB-c1', 2322 | '32m-c1': '4MB-c1', 2323 | }[values[0]] 2324 | print("WARNING: Flash size arguments in megabits like '%s' are deprecated." % (values[0])) 2325 | print("Please use the equivalent size '%s'." % (value)) 2326 | print("Megabit arguments may be removed in a future release.") 2327 | except KeyError: 2328 | value = values[0] 2329 | 2330 | known_sizes = dict(ESP8266ROM.FLASH_SIZES) 2331 | known_sizes.update(ESP32ROM.FLASH_SIZES) 2332 | if self._auto_detect: 2333 | known_sizes['detect'] = 'detect' 2334 | if value not in known_sizes: 2335 | raise argparse.ArgumentError(self, '%s is not a known flash size. Known sizes: %s' % (value, ", ".join(known_sizes.keys()))) 2336 | setattr(namespace, self.dest, value) 2337 | 2338 | 2339 | class SpiConnectionAction(argparse.Action): 2340 | """ Custom action to parse 'spi connection' override. Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas. 2341 | """ 2342 | def __call__(self, parser, namespace, value, option_string=None): 2343 | if value.upper() == "SPI": 2344 | value = 0 2345 | elif value.upper() == "HSPI": 2346 | value = 1 2347 | elif "," in value: 2348 | values = value.split(",") 2349 | if len(values) != 5: 2350 | raise argparse.ArgumentError(self, '%s is not a valid list of comma-separate pin numbers. Must be 5 numbers - CLK,Q,D,HD,CS.' % value) 2351 | try: 2352 | values = tuple(int(v,0) for v in values) 2353 | except ValueError: 2354 | raise argparse.ArgumentError(self, '%s is not a valid argument. All pins must be numeric values' % values) 2355 | if any([v for v in values if v > 33 or v < 0]): 2356 | raise argparse.ArgumentError(self, 'Pin numbers must be in the range 0-33.') 2357 | # encode the pin numbers as a 32-bit integer with packed 6-bit values, the same way ESP32 ROM takes them 2358 | # TODO: make this less ESP32 ROM specific somehow... 2359 | clk,q,d,hd,cs = values 2360 | value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk 2361 | else: 2362 | raise argparse.ArgumentError(self, '%s is not a valid spi-connection value. ' + 2363 | 'Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS).' % value) 2364 | setattr(namespace, self.dest, value) 2365 | 2366 | 2367 | class AddrFilenamePairAction(argparse.Action): 2368 | """ Custom parser class for the address/filename pairs passed as arguments """ 2369 | def __init__(self, option_strings, dest, nargs='+', **kwargs): 2370 | super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs) 2371 | 2372 | def __call__(self, parser, namespace, values, option_string=None): 2373 | # validate pair arguments 2374 | pairs = [] 2375 | for i in range(0,len(values),2): 2376 | try: 2377 | address = int(values[i],0) 2378 | except ValueError as e: 2379 | raise argparse.ArgumentError(self,'Address "%s" must be a number' % values[i]) 2380 | try: 2381 | argfile = open(values[i + 1], 'rb') 2382 | except IOError as e: 2383 | raise argparse.ArgumentError(self, e) 2384 | except IndexError: 2385 | raise argparse.ArgumentError(self,'Must be pairs of an address and the binary filename to write there') 2386 | pairs.append((address, argfile)) 2387 | 2388 | # Sort the addresses and check for overlapping 2389 | end = 0 2390 | for address, argfile in sorted(pairs): 2391 | argfile.seek(0,2) # seek to end 2392 | size = argfile.tell() 2393 | argfile.seek(0) 2394 | sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1) 2395 | sector_end = ((address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) - 1 2396 | if sector_start < end: 2397 | message = 'Detected overlap at address: 0x%x for file: %s' % (address, argfile.name) 2398 | raise argparse.ArgumentError(self, message) 2399 | end = sector_end 2400 | setattr(namespace, self.dest, pairs) 2401 | 2402 | 2403 | # Binary stub code (see flasher_stub dir for source & details) 2404 | ESP8266ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" 2405 | eNrNPWtj00a2f8VSQkhMaDWSrEcIxXaCSSlsA5QUet020kiCsoVNjHdDWfrfr85rZiQ7BNrt3vsh1CONZs6cc+a8Z/rv68v63fL63qC8Pn9XZPN3Kpi/C4Jx+4+av2sa+JsdwqPuX9b+NfWdb48mX7ffxe1fCV3v\ 2406 | tG81N+o71C1zPivbniqHWcbUk16c9iZQ638rpw+B5gCkuzPRDD2o7UfjtcuZv8v1DV5HEcivdtrrbvd/0hqCqLfyXkM+LzvY6SBksOPA1iI/qxCMZw5AQBPzdQ6N2mnkBtGx8wY+VqUdugjmix4yMgPCfCk/j9t/\ 2407 | aqehQmcI7YBRBk5DNWYR++3jnAEKXFCBOEXlQBc40AWdl5rmMvOokYMi1aV5EDishg2ZvQTWEkJnmdMobOMZfjXefYD/CW7hf94dGfa4z7/K+Gv+pfUX/Eu1E9QhN6osx18vzbN2kEpmzFvAauTi8YMtAYmH9NrR\ 2408 | K1pU3n5ZKGJy+ES1v3XgFxs+EpAWHBYH7dOwmLbjh8UE5iva4ZqwuENbpU5pNG1QBFNE8FARKyICAT3t7yBxNxiAFH7jpyEwI8+a6aEH/Q/uEjkC1TYL3kZaCY2VPBzuwtwDGlIDWsKpwC8LGdGkVbEG1AIfMioC\ 2409 | ZQYDqoRBPDAPcOhd+IdHi/ujXfYcYEWETOTHI9q7TXMuPxhFYcmA8GC6WTcYcmULew7c1+yFBsZtEhIqWchngpiM3tAk5sWOfaqicAJsEnIn5T86wCcjv/CBW2BcIBK+jUI7VgZ4VirwB810vtze7UATtl8zGTSv\ 2410 | qSz7axLSg6yGRgbDFiw1spZ6BeO8kWkj352fxlNqAD8GHm1AFL0tc5ZtF5XeBUmLy6KR65qBUZcAI9AWzSqZdcvFVcV8Q9ii/1YhCG5AFSI5IeiDIA0fNQJt+zBnodckbzca7Dz7UZ4czN80G9L1Ac8J31SuGGoO\ 2411 | 8Ktz/P3EmS6yEOZZV+TlHeCC5D1BBlhu3jpDiIZKuHPQyFezd3a00hlt9hqez9pvWFYq3UNETnNtNyQbaHVj6QwPWkDe0MhND84vLBKy1H6iXACm8tx3Hj4XqEKHrpWCHirabSdcyrOAngGHA7S/93ihS9Ysuea+\ 2412 | i7rvGAkjF1eqAkkOmgtGf84SKTF6sOGPwtkP8u7cFSq/rbJg3YqtuiKmVjjxa6Ngdyfw0b/6H52QyFcFLogYGBf/BfXK9MMBsGB88gSUGsvLEHTJVyyiEpbi1p6ALYfi9c3KdI8JnXVAc2mc60v+kX5Fa4A5UIWt\ 2413 | Dk3LaC5bRrJ2GSes2mvA9PeWBGBnVKFs7S2D8GfC7D8z5oUgZSMU/VFocmvl1T690snfXGJvSZfZQ3icPfCGjoh3AQmF75ztPst4Phgr5ulU2I6y+WmjzEQEpDQQYCiIZxMXuR4xox0u5N0WOOMmpN3tLkbzJdsF\ 2414 | ++MxsQbqJ2AaUCPayk4eJgc9gZwv6r1uZnamujiYWrKDAVnXYx+GS7+A8R8zdKNSLIgp/0A59RRkvkjg9PQpKp1DeHh8OIAOALgaRwOAS4tO0UrWPMDd2wI5u+HgBcYGvFg0OLhRSTRfgjwxb7MTFmlZyEIIkaOT\ 2415 | gRVVGckP4gkSB0JhtH+Ra2YDw33fYkdP+I72bIf1gsQX1ut5L/w1MILXncayP8kplXY+M0xLrGGmxsEnTObaJfs7op3WaZdpGv0PRRq2cafNX5K10aTJLnw+AP7ugpUbdiHDPGeRGiCo7S4Cuxet6+wbn1rBaPj9\ 2416 | gHpp5BwQWM1zAg07RDP0OEZGyvLylbPPKoumVYyShBKTWOBFHgkGy7sLNoTb3zDiwrEytONPrkgxsMN1KYaMkinAZlFhyzER7v+pELztjBu5Ke0TgCWIt0Ki23X0nS1hld5ICYdIVSBTEIei0nCa26xLo5S9LxzY\ 2417 | Y2hXmMQjS7KprTmGktZ0yHY9mqipg8lkuMmoafr9bsNUN+eCuqr/3idLuc64Q97vwMwZu/6Zj9Y52ujRFP6Nn83nYIL9E0aZkRtG3dfbkREakdEAuBAYFeUGsuPdH5iQCfBS1yX8uE3so4Cm3WOFYXGDGa92DK3Y\ 2418 | Y1cUnDfNSy5Dn/lOie9ZMqdEPhqKC+9GWVwjb4kV7fWUxarspRA7hAXw++gBm76hNzpnj6QGPvFunH3D6rk4JJcZTPKq+HtZ7OAgu+Nn7C8WZBnDJlUohn6FGS/Iuidt8SvBAGZorRe79GEG5FGj7V9hwmJrUWzi\ 2419 | wMP9JyA4P8BWgw7xSxAjwNFlZNkdgyCxInS1HIa7bJveI0ZCZtJMmNSVsBHxEewH+G8W5rANcvIuivROjs7t5mv6pP25xcYLKIoWO2D16Y0Ag0ntHCkzNVhguThR8dflj4Q53KwNUvcNIaJIScTW6GMhC3m/0Rig\ 2420 | +nSTk6mKAmjELic+4KGsjWQG6H26Aj/q/hrm3t+ZOQ4MjrigJV07TnFJr+16VPSc1jH7wgl8CPwjC//LHvx5aNS77IbAq51OGjt5mh/BfyOIPoUvWdoH12DjvPKnRfC4iMviS6AP8ChzryKmW5EGCPXUCx57cenh\ 2421 | R15EPE6bribffdAQH2r9r+NBLJszG6DvdPwdOLzlUx4uPH/ObIRhIXRX0D9/kVr8B6PxGGIuYlUkY9g2UdAA0+gZD5WsIN+7B2PvHuSMaLJFgsiVLMxKTXJ7TCRk5nBfGj5jBvUH27xFNS0u0JG3D3PlpTApDaMl\ 2422 | pCh0DYWQviyP57BRG98GUYmc/Lsai9QGJ7yGHZOVzH4JKwAc8ZxVRqstN9vllOnAm+yQyoC9VkcT0mqwrKY6ASpsoJ7dRGFQ3UJEbH4EEblBxA4HcPWTE9JuTcHmRNzj6Gz9ymeuJfAHlp51ls7L4LjamBcMlFIf\ 2423 | gH4D+kgF1sZVyUOSBQ3wZ5Xj4jcuXfxCVo4OAY6jT1hNx7NPX/efpDgsG/UdxKiCjBUa2FtN/crf84HVpqSYDKnZUayziay8F3VN9yefzv0wVUV/uZaw0WJH0whlTSSxAhkMHtC31YRN1NqIuFf+fV+wRFDz1NYu\ 2424 | nbBhXL2K7kf+AfQxyPYYFTAysF9TGbkIIwseBLUK4GmSWdfEw2itAmsJlP8y91sfBEQlBb2a1sZY3grnyz3X7oBIJAIV+BjdyBN2mxKOuHRRuPt2JrFkiUQBx+0j5q99KsftRyekTuuEP4n7ZGSUJ3+ZrGkCu+Fq\ 2425 | TH4s0cTYcjjMsp3jjQbB/sGMFh6Eq0KllSY5QA+7MVh4EFgvm8VdAfCpNf5IFz2kRhUcgH1wuoFWwnUyqSCOhYSoeiG6CKhQrgEgQwAyAwAjiq1zI/N2wWZaosH/3hrxawi3C0qqnazM1uwmpBKEM1v67hwD0Bqd\ 2426 | mffwzwWFVAMFUbIQWom0kpsgrcC217D6lBT4Au2rng6vctqeVocvvFB0N2lt0OAwo37/9Kzh2A4Qp8k4NoHobvf2Ytb32HrrwYRPxFFRZI0t6+6Qn8z85L+A7bMrxjiIgmr6ghwyziV8ZFE6XrMoCXxkwwtFhjN0\ 2427 | sUtsV9yuGzCXSuIimx4IW0UXbO2CX6vDM0rZSdA+j6cS+Sx99vWa1JccgiR4gMZ62Xdtp0fmU9phWSZDgIUHeZY622N/G6IYqYzXDI8Wh/ZjttIxDohhSOXE3QC7EARvpSMkAnizObr82ucYNRh5FiGj9f+ZkMHg\ 2428 | RuO9JWLShnfWNznnFzBUiEOBoaXGM6Yw4KtGfzMY2PDoGsvgiFhxDuPdjwZb8D0kiUCborIokwTx9MKC0nq/CPdjzA/3jeekz6NCqJYLXbN5xozjZrOT2/tX0myN/QV23QmpFx3+P1AM3reokb/qc2WL82BzuDl5\ 2429 | QCiydqhV1MZIbZHvpajPfYyfJKRilY6QPBHOCVl8dX7okuZsAeTCnT4+ewfqaQHm7zNwRdShuDytvlIu7RYFjfU/qwJmfn2yB09p3cP5dSdWFaiT/gce078lEw6JQA13ppSIxOSPerTKIi2HqZZBtiEAhDIaCy14\ 2430 | qiyE7an84iZC/oVVha4XliVuiF2AH2IGKnJFEe6VYLohXH441fSzBeILUoLoxMcyDzCaglymeuhqYNb/GNVZIHrbVkFqPM9ATUIrQ6nsc7iHZox2iNp5ccgZq3pkl4vmZbLJ6sOgm/PHTOrlni9wDEiEwx9gLccI\ 2431 | wgcyS8HObXL2SS9R1REYkeAHanDhMEeXyeRIxLzIOMsleEx486K3C4URmM+WBHA6LXCbHUICPloU2Vk64Bi+UmGRhl4y9VIEJV54SVhsj6PB1MvOzpHxDxdFMi2yexTbqRJWsGDTpGlwdkRYzINDG5Afj9W02Lb+\ 2432 | JwKZcQIQS0kwPofgT9s1jd/CAFNvG2gUTb+H1kKy5yAqUrKT0Ihq5KvgDFTi+AK/ZTqjr1/5OAJyh/j8dZBPBB+Me6wRCduVn8HKzw5hCwCd0vE95FV4giNX8XzZrgfmiCWeABuoBdj/DoX61AT+6oWHyL5osdb+\ 2433 | DNSkxTDsfG30WcqmZbnwUpgvO0PUY3A9CLKAN2eOQIyRjc4iWlsePrGSMM/dPYLRMha3tubDu7m1Y4PgRWqTvVlltQGI6oqT59RXEssQhQfIi8SXVzsSGh5h0A6kKCDmnJPnerhx5jPiTLqG2QI2TC7xTzX+Wsme\ 2434 | uUcwS2LdrV3ADumY0q4pP0Nv6ZpdjKn30WQr0GJ2KFADRm+jP5Ap6ShdjOPAdtEBxHSC+D5guYgI2aGN8wHP1xF7quX0RmsDgdWLVlzMOjYRJxOiFd26l4877eidRScw3ei/57RbqoeO85k6ToTJBBgaTm31Q1be\ 2435 | 7MwR2ToWNoA2cdaNAU18g6ASFYsRVdhLLv/87jJP6DIPCqpgIMyD2jvIFYydUZXRG6tncgkZamaHmCmvSjFCYkt+sDQN+X1UDQSAzxMh+SEzieKEvYomFdvCB0ez3YRbppiBpog6Zp2TE0ezWik04LQ6g38pSIJW\ 2436 | gpKggKb0M27hrb6vOeV8QYA+GQjPLDoNZX+kJHORr7LXojmKravcJqWNITNHc/cfhESCIMfCHsptGBPKLzhOd+eKON0art/gKF0Wr5qIhvnTKyJ1DudrdhECshw2V/eA5b8gM/shvQ1Tef9DayVK5vS5pnxYVnBK\ 2437 | RuU41gWMtYoLgOsCCH4BKxxeeHdhSDTW9HDrPo2x7LAFyXTiDB5QaZGADQquJdg4LYsc7h2GhCN1hbP9M63EWnOt/UbO9vumZ/B3HYLzLfhyihRS6OapcEj0TaTsp2le8L7g+BKtfYWTnoBU9LFyD4UvRDMavRrh\ 2438 | X1Cp1/0+zC3AHphBXOygpgxO9B6sI0ijNZrTdogxddAfYdXn9jCiBglNjJOXUU6/wCLEuLvepnwniHuAq0Qr9ondNkYb1smUJaQmzYD6JuKiymRktu7OcS76c0Pk7Lc2odlwiaLRrjrZxrA517OVGDkH6dhRqeVf\ 2439 | pFIzUan6UpU6ZGGK1aQx12z+QZWKIKUdlTpao1I5Hnztc4TLW85Vsj2ERZfVOu2a/He0q/6LtCtLk3BVwU64bMOy0Fj0mmUh9IAGp8xCaEgFgx11espxy8QUb2NI7QNpvsyhvSo3OhoP5drpC7RkUdXuQVynaMZg\ 2440 | jQJe80QYYGYqA/pKdSUsMnCkJBVjvBBjdfa13U6mbEeTH4hhw3h9oKkTXi3duK6ETIxxDInjFJy23FbLWp6QOoKNwWV8sUIb9iHLwNBmwrXl5Z7fIVPgkClQLWlYOkV2IwMlVLn5gUPZPfsGC0nMXlxYwqH2ATla\ 2441 | vse9ubpBn4jRI/QZCX1eO5nLSwjFxZumlmyz72yArsuna/GJgm2w+en4nK7g85z2YlPe9y91RuCxwrqE1sv6mjHb5A5mUxM3UeW9D451l+Kiz0SHY73ZGXqR7316iOUCpcH3e2NTWsPSxyBPhaoELTnI2TVY4uK3\ 2442 | k4Z3HQUdd7A87mFZ6iXKp68tuTgtLyOpG09nL4SAtMEbyX5AYqAQJGOocwi5PBib5EdDSXqu8N6UGG/5qoRHSQnmkkrATk1gY1aHDIdkFgLc7NllknsDtQAnsQvW6XkEJXFN9TGrMOwJbRvxDvtC25g7Nzkx1jcR\ 2443 | WciSCA865mHRMQmjjjUostkJ8bpm3RqrTyJFrt1ARIvFzNjrGQ06GaJZcCQEQY/JKFUsJWgkDqm5vk+p0zFLdmDCQJ3+IsZB/IKNA3t4omslqPQUS0Hv2T2cIX3XGQdmHUNY3vcAo+tsk2I4RcY9goUB9wXxq0MM\ 2444 | uJkgSx6L4DkUs0DfiNLbkmNK+lbBCo+5qvVKNsuYzQrMwbG/aDgs/w9wWJ+3gnVGAuLOOUBQjT5qJJSukfDwUiMhHnMtfIH1sOul5jmHIy9lqKnLUF1rkw4etFJTAnrWVMjVkAZF3kiM8y3oSSw7kKHI7HCpoXgg\ 2445 | xI7X2AmXCMYJFzyXsinV3iHmVqbF5oQSsfAOhMBejIdlMGOskpf25EFZTT+dxVZKxrpcVnL0qEz+44xWXi3HXoAQ04sVCZZ2PqBAgyPHJBloNZ7g+cxRQIi4XI6GFSU60NALKrFaRw7q+sEIa4pT9IXOcGz0C8Ji\ 2446 | GBwMvhcReHx3QKbZ8PBedpcLUqXGDzdGsfuEYuJ4oHP/iccFE6C89GZezn5eTxgnmV/nC6jPN47+LkbpMeetqOoSRiMDLBhuOO69Ki981MmLYojKVvZ5WUrBUetimIOQUzGSSg4VVNO77nOFTSXN8FaED8JbT2Hb\ 2447 | JBJfFLGXiqfI4X50S3B8AqoB4o3kocKH6tVTttaVFd14Aq7un96DaD0a1CnXhYSc3QVutfVYX6ErhpmkTw5x7hJM5I/N/s6BlssiPqO/xCHTJon7h0uzpCBbamVuIyq2Pj2dvcs5IcLDySoernJK/2x9mu7Vp1EJ\ 2448 | +5qarJlV8J+X/d0la8C63hiW+U/53erftDlV2RzxRKyWcHrMFqGhmXI4u5S1dqD2/omSZCMv7UHV7HJuBgUWexCoLbAMiAzHduFDKGtZwHYByyv+DX79Qk5Cho8wBAQ/Rh+4ZAw4j6NU4KzoEYeTUjZ/EGdIwGdA\ 2449 | GMV2dnFMpGlQIujjY3DGqGLWzfWD1AX82CDY72y6STpa/0YlsxwB3O4l5Z2UIh44K5xn6oefSHbANjJPIS9bPF3zIrrsRXzZi9FlL5LLXqS9F9jI0Awtogu0oc82JoBqn/CN58qD004RmHvap/T3zEjDPTGRL2At\ 2450 | DZoMkPEO6hbzGLekrH2rxb4kKjwmM/t1nwotwhWG1PkQHxyF4FPKv/T7LjzK83LB/hmpYDos4315/h30b2n4IxNWv7xNvFrC4V4tx7giYXw8MwUZQpy7ePqc9nvNkqzi+AEkmYrwA4l+FALCmZz2B8GFH2W8Q6Ne\ 2451 | 9FhEWdArfJYDaZB+q2p1j0yEG9tY5dvIMUFfziHwORsFoehiZyObLzAqDSiCv+hs3yZS1aiUcxVYI1OhQDi9xtZp+dPxT4NjPuen8/ni2AdZqBcMWXqTWCgbReY0ORZreDfo1JiKeGZ9RLGBrJaDJfjF9j6ohamj\ 2452 | l2vZwjWZl9qEeOWAULoDjKAdDGJVUw1jaSM1QW4V8knGmDUcC31ZifzsdszbseFgMVpDARzM5l6Q2yg81l3wUt3ssx2fSOo/1MmJGa8D4XdOXwRs9kQ6rh1/QPcVSFCu4LlqOl24D4x2cwD5PMjRZeaQKxOBUYkH\ 2453 | 4YvtXejfis65446G/bs4nMNBQP5R7zSXPUNgTgWKMcfOCwkH/N7nawAqPmtEhYFfchEIbJUmb/ekWA14gEmjF3UDVMTG4G+08kyKkXENRw+ezecvf333ASHho1FCos7Z38qJ2vP+rEILMpY+F86FKvlKudeCe6aW\ 2454 | OxCJzlUHzsFBPkQSiLddiJmdH83n2S5e4+DhMer2UW7jT+SaQ+C55kAabs1nzjk0nUH1AIQ7YT83+XHIuxVP5eE7Xp0grBaYQ++ge4mFDj28psLDayo8vKbCu0M1l4rKp52rT4hFTtmqC91bYcJ1V8RAVqjRnXsV\ 2455 | 8LqFwWDzZMpXUmAReTOxeRz3ngXPGu38+OvOZQx448TJstPDubZBheTfDjYCs5TMuc9Grb/cBvxqczNMFruXwYzNnTQPqJoGbzJRnU8z+tSBHfe7Ytsll2tDBnL2FZe0xANsmLCL+jdRyHk2Kilegsj8gB80qtMV\ 2456 | D7eZ04PNAEzTbPSt1LXK6Su46iKI2L9t8on8aPuPD2i1POAzZrQaL4e4H0k0A/HQYNC8gtBDE5tPSgx5379rgTrk+elLjy5aaRpkqpjyZ/seWHfoOSA8aKi5t3TgOdhHpGzbBUtxPgd5mmaFdSxAz+k8q5FvzuO4\ 2457 | y21Kjg7s+TaSxx3wYDVfioMnRPDUISdwMejdyHZumPMthaWqKPOI3FgxxzyhAneXuXcddYXx2C7TBm7cLenwmA4LnlH9KuH4Ak8/7nL+m/VHAJd81GziK0nI6hHZrfSQ65theUUmmzbuIDzvbErk5xOo8222H3BJ\ 2458 | RzS/vmGzvSqcfM9wGS2+vTsYxHLypp44EJAA3C24ErwZP+3soBBDqkGweSI36KA2adBuM8ql6fKD4tIZ+3iqO+yBtX8kWbIHA+SD/EG2u+fv7Ap5gZqXUfGB3EskHWq0LkArqbu09Cp/dARKu+X35Tlyv1VTqD4g\ 2459 | q4zlNuBdV3CtjsKLFWKOBmo5HsN1unrStR/R7YmEDDUFgt7Q86rpk7dvIbH9c53kRs3uYGPcQXDHNRjO9UNzJlmvXrqRaanm13FrW+FXw5vg6GqwhirWVGi55nA3RHYOqIVrLrJHfPSkycx+GQiaEQhzphjOw2FJ\ 2460 | qN0ELE/FzH2MUiiYzyfPNx+JsoEv4p2YLj37wTfbXuNFGjr/bge+goLyFXsgU7e5MhLXNgp22TL7GFe4AMpdKCVCGqg7/UmkA4aq99eC8GkztROY+wLkcPvoLjOOnJEdiZJAmKQbniWPLoMMKyPUZ0PWg46oxGoR\ 2461 | qYabYI8Z42MRK0qgsb+T8sG12N2fV0vb1Q1auYRtsXceFvZ2KbViHGZsrANw2gDUu81AlevQJKze423q8My1K166jTO3sXQb79zGh+5lfFnvcr6833ZvYMNK7szco/YN/orNs+qWcz9fnsslasacc9HKN+KgYAD8\ 2462 | gsBDaQdSrxWC7k1qcG0HXlUHR2Ak1ohuANurqvBR2cgNCA27FQoDV45jQt75Y2aYJvyVbwRZK6agELfk6F2ngveBZAi0c6lC/1M8MwgWLIUMho9gqkOxL/DCG+xayA1YuMqX9oRFlcgNP+TbYBAdHQg59dVY0weW\ 2463 | KneWFU9/k8yeMlK4WlleYetm2onfcUBc2xtifuXYbCJG2kQsoZvMvsUhzBTfnsPpPbIuES29qcP+1I/49ovGBiZaqJ9xUKOoH/Geoee/wPOCb8NwoPX4cgl8RBl0DKVDeR94MihJmrMvhYWiTNRnaOu2q9pmBWtB\ 2464 | qpxrbSef1s4dcuEaHJZmAQ1HcED+0GUvj7hCurHZoXbII1pnidlYcxwO6+NHg6cBBlZivnIowEih5Y/7iW9OtB2656IhwhSxgC7zrUNwGQo0QG/z5FhrufaiNjmgJ2YVgglpDrKRnKv6SrYyene4YX4SL6YozMmO\ 2465 | C7kGxyxwwmUxjXW4Ww6aYrbgXuqI1yAYbLAUSkuHVDFfy1JX5iQvXaPZGNf0ky0F/Fb1pdInCaNaVIGIHy0HCZ0xXQw16h/msCWnciVLrTlqbzY3bqC6u4GyNRZUgxg0QNzmIYKHA/4mX/2m4mhuJcGx4AQPaHnv\ 2466 | +QY+R57yKKMVXrlYYRTarCRLbvYDlkhSqDlRyz9GKGsfPBbliVb492yMlJabcjiaaQ4yBRFFXXGcLT4tM9ra5BNQptRiKuYNHMMJh5hz2RHvsHNuT3pq7DlfyszYmwLPHaClf7Wuv7X0zDdUB4XN67sDvN7357fL\ 2467 | YgGX/KogjdM8aK3S9k39Zrn4zX0Ytw+rYlnAbcC+e4MtypCRkxB0IvxUQsJ/iApOPsIFvHP7k3Kc1PiZC0jwJlQlCdMxFzLLG0Qb3kg8/sn86nzwaL7khy0zys+sAUdkdWyncYNAXe2DC6xlzMZpqFSOb31kXOLh\ 2468 | 9d00n0xqG39nuxEnYV4lFEUs3oPxBz4k8PHpLm8UJd0E1H9zZn49YUYKxjsO0pvITHxhUIqGMuH/jXmIF8bQDEefD9+fbtSNgemawLR01ty/M2wlBB312r0TsN2jkW6xD5WMdVq925VVb248+uUqysC9P9Y2OncC\ 2469 | Fr2oTW9Mrdbcma16/fv3aIe9dtRrx7120mtnvbbutlUPns6BZTVwG52e7mXc6nT12u6/7E9d0Q4/k4eu4qmreKzfTq5op1e0s4+2lx9pvflIq3t797q2/mh78bG9c+Xf5+7b5LNwtPyMdfchb66QAj3IVQ8S1cOi\ 2470 | 6oy34TZuuI3OsLfcxoHbeOI2OgR525M0PTiLXlv32nW0Zpeo/+Iu/qulwJ+VEn9WivxZKfNnpdBV7c/8U4GNm5kdmOLOo4OiI95pscmULBhrHMAwO01d/v+KWF2pz+ataw1Hadj6ltnv/wsZwgm9\ 2471 | """))) 2472 | ESP32ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" 2473 | eNqNWnt31LgV/yqOgbxItpbtseW0WxKgQwjblkAJgc7pjiXbCbSkkM4h4Szbz17dlyTPTNr+MWDL0tXVffzuQ/lla9HfLrYOErM1ux104v4pH8FThk/V0ew2c4+Ncq+d+w2zW5slNKj1bOH+hafs/tkxfcWZ7f8z\ 2474 | UwG9jCbIT2XCQRY9RT8tHPUT99kSJQ17GnrO3FiW+713YE3M1cYKe+kHosiv27PF2fUq/0gGiKtcTuLey2Q7W3+SLDskVvvAp6ocJ23gue8imdmlPZuG9gwDyMTZt7ul538qPOssWg1K7R4RAflldI5I3RvCTZ7C\ 2475 | J+D7iXuYwEl0OEnf0td2ItI/f0wiGkRUxRGQhU9v3TwYNecp8PQGVO3OZicwI2eioLoCpJ0enrtX9cCNF5GKM36GY02AwmkYDLoCuU1oRZePPj6+HGn6GOW5YKL68XHKqrbZQQmEHjfpkqBFiGCXyG62ZKT40mSR\ 2476 | rGENepN9FBmOaKeM3w8P5ekYmOA1akS69KSDslDCIKAaHo7kwR/0HFzi5AlYnhp92Hb/VEmyYIXm4KW2ugef3OS+p8lGhWcv5jbf5Af8LcgOWmavceQs2wdYdx9bvYnImkilDf1v4MfPfjvLHtg4jSqztHPDUgVB\ 2477 | wEewdRhreCy4UP5XlrvjR9vRcY75S7QBcq4iDpleU8Mev4+Xazorni8LXt2hh9fk9qqZEjZl2Xc3DezCfVEgafdlMfZhJr0CAjBQejp340UTGOqUh8YFH70YCeWctzXpJhMGAHUq7BrWOjyXghfO5OzYHN7RkjEm\ 2478 | vdwH62IUPCLbcBhRZhPL9laAv4Onl+9fv5zN3BxdyeqeJESu+dStdl+USFnfJ9kh9OQUDETsMciCWlQJUioSx6jJE0YSxoU+8mltD1KyKVvu/AW5OnjzDv4DluGo4HtjNBiHKPTez+hHLsY8Or6P54f5KUmiDaFL\ 2479 | JNt2BNc6CgGBqx9nVyHo9Jb8CJFckaW2eUB58B8lAKhIOF0fRZg8gtJi2Z1DhG6TGE1zjo4m3+CZYOLDKirHsmyze2Rky1EKDUiCTiaxvFW3EZscFPF4mRz1BGbmH+EVdXWUwnOBnIFHthBWmt1d2lbTnJAN6OZk\ 2480 | 5z3bC1rV/uyK6dsJ89uM+N0jrPFhPDAEwIxqy8kIcVpPuoDvnRlnJCPByBzLtl6MaeNaoamZTv1f6HQ8p1yds5oh0EkOgHYVEEToybsyKadXRsArZ2xdm7HJ83n84gCuA3OfOPvv9A/sCY2NhiF+wnndS7WxQTwA\ 2481 | VKGCoyxy5KRVwEkI/hWHEvSil08A0TvOSkRDKkyLKYE3wQ4y37AjrnBRLK89SQ8RPs8sGSs6BLuuiVYDsLctx4R+jQ5hvIlSHyNrHgQrRQshuSM62WUteB+KrBN2tN3/sojLWF+f45dF/HIbvwBIXTDWAZ6zi8AW\ 2482 | l+wsG6C2JsIHOWI70Pm0fg7mcB0khV5a7c2uII5oc8Hz7tAcng4B6JkTOAzmUw4bqPQ6nhIv/SPsciroBbo3wtqbj7RIkses2o+SNK+oQ0sCpmOEuoCMZ34p9lcloBAONgAwq8GGsTzKMFAKk1dw+uoTsdG0p9PZ\ 2483 | tXDyQOQxdzviFmyzGP7YAhHieEfihWRsVOJ03NbMlFlxro/px6c1oam1nDjgbi+79YfWtfvcFrRFH1cTLLOuXK88yUcUp9uY9OXbEhy/sKHVKCIoxarp7ArOPKG5bf5CkGqg3Qkiv1M+aTjd6urN6WyL/AylMnyG\ 2484 | j8nYM9sVdO3DaXBpts5FWeiTIHRy8T2I2Kj14i7/ewVMKdgXDtSptyR03VOe5auhOwo8VxP8+fjoOfjfQ6wDSjSCxSFbEFUK+lAq+SXFLNeIYIl6hOeHoyJ2LQMEmVlUoWxFsYT5isolmGRCUfT9kkn3DCbnkvkd\ 2485 | /mMXQ4XOOWJYu4dPP9F/JVV+uBhNR17AZKnsOsxIfy6snHuA+olCLE5VJdGCEkVxPhv54FUKAUo/5uwjuwvANyiKY+HVMdhiWEWaj7GEeJHWOVknWkgum7wgvLYq6UMqiAaluILDh14466la6iYRUthdyC/te5+s\ 2486 | PQ8BT44TuPVul5JVw0BUHRbObpMHyahtUjAkTu7w4naNROy4taK5lLAEOOBCNk/ugcWk8Bi3ChICE0jfMnY13GaNHw2Rf3XqNW8ZDYKwoGAAwYHUW2mIWO+e/RoJIUr0r2DThxtMrT8ieB+GEzOumwGnx42iW7aA\ 2487 | 1RYUj6rKHMHhDYWpTKWnswUOqZ3TgcpmVaf8tZBp2HGCVoSap8kATRa9DR4XcZPvnHJhEdmINmJnBLZwPmvZCmgL9ovxLpxA58kGlVAYymvKbpQa5xqwp2UMlXFwltaP96LEqbiAPz6x2eVwmEZS0orCYpZd5H/C\ 2488 | k3Ax6gGk+poMlYz3Mgg1sR42ZRxDhBtrEfsGqZirX/D1jM/ryOjBp5a3rfF2AsixxBVB1AWm5sPXwJi2EQWcM8jCKbV4hKaJaE6/CYeSd9vRjrZqaLttMGg/+EQmw4DjhQvEYYnbH3hTLoZliRpN05UEUptGjFlm\ 2489 | rEFw5aLYoQeErWrf7bmQsYzGqDIHUTQ+TsiUpQ0fxN+KaFMKeJmaktmKxKDwpQ1+Jzn9Pvqw18cmZySiThY3htJR5GIQdenTNJmnZOOq2uUK002vozCJvSWoILX6uowUZ5QEKPBEzHEwG87JH6y9T9SGJqY2h3B8\ 2490 | RmcZ+nbOThWVgC3/BuxdXq3ZtEMInT+FjfyOz7gTu0KKmL9YofMe8qZVzs+4QdNPubPr83LpigRJT/8eDN7PzdjwTaSMXosZPlvztStEVW1kFZnaDNOyai5NKkwxsKN1whjSxoZmuRpXjXjK37hxoan3vEVY1ZrQ\ 2491 | 0rJmTV4ERlcip3uw7hnseBTF3Wp5wxZFciY3BXW0ZUISU1xZWAXA2PA1BCZ4PXec1Qeourv8IczEHcARin0+c3USOpLga7COuEH9LWJBVBfs8yjm51TqbW2/I6iHRuqAHaOBDLbxYD7fgSaIIdyGXRquH4zgOstN\ 2492 | ckfZmbuuDc0yBqwdjom1aL1LAdvHmQlDHjyXEgoqjjksrRZvLBI2GhjBHm+VyPw6YlzxaTJ6SOYEIbh7tkONHRLWtBxXKJgcd0GU0nZZEqY3qd/IXYqW7/oimBwXjQY7yduQVlUPA/RqQkLNaNeY2McE7KYhkR6O\ 2493 | ZabAUrWx5ESofRXwLr73EQIclrK1Hk0fVb/WhWHrPoQRxb6MOXMZOwS3v210PxYZmhiRZlsDbbSAJQ2kfE3DyWAk7VcM1JCi9Z/AxDq8OpJZcDdQ//gHIgnIpsqVkwGh/sDb8j616Kxve+1S4HHqWLxIkY2rF1TX\ 2494 | 4RmahG7nyIJzelHYEd8g24Zp0BTAPLWSSiQS4igoNeOIJnJbVdoyrA1DwpccAF5NzeW/v3BpJMVqMcU6YzjXSynWIJcNlboAF5tQQjQ9RWYxyZq+xWjqo7EJJY2qo/pwlCq8WAn1qvptnGqoKuRJqgzQq6rQBOtq\ 2495 | BkLFCWIfB6xLIOOLmn5cCWAvtsXMGIzmiN6MgTdDGB03ArtulQQmrQXfxygux5bmoIPqUEjTUfFWwMclyu0gBAPy9+Q8cZCXxUbAr0jjzJs4VgruKUxC07BBxTySIfeUpmAyPfBpMl9PrKTzg6TzQ5IKCRMlR5bR\ 2496 | y6MAVDtgL/0X7hF1oJP7uElLlPvsBgR/Da/VLSR0trmpgq0BQXDrjttmFisruYjQ8OELtTwGvuTBGdCUKrjWZZU1fJMxSA2jSYXAhZUx7lWB8JbV5qDnCtDsAejki0QnshrTwKgEQcM3Sy3cI7S6FTsi/245srUA\ 2497 | C4Zv1nAA+wyfA1VsQ7TEHfbyRchduDfAe5GKwSZqWpLM+q+xoL5x/IfaoboF9dobioX1gEHxhoN4dUN9hs6jjUBhvfMaFC6WDImeS02vt5kLgCNT+nAWX8F5vCUKXDv2ZOBnlPoZ9iw09n61wa274NuNXp2D42tU\ 2498 | B1xg4xStcrsOSGL4ilWuK3BmLjMl0WLK1RrKYnB87YLxCFSgp6TtYXDF9WU9xwo4XPyaknnQ1BnAyq/3bX/xfXrKCZ11Li2XOLusfACc0nUspiH6BpzhAoLdW9AENrdrSV19ZFW/Lh/pE9eAV3O+/ZJalW9At+KG\ 2499 | ys0acLMhm6h9txBl4FsMH3iKlSwHBFH9GzPZC0lj3xJUYza0gfbwIVCWTn6jbJS+6ktfTFo93wb+P+Hyn5log9eSTdhe5TQNqP4TlYWG/IpQAo7d1MEY1t+VNBISfOq7zeDJboi5gs2D2DFQV7G8sEP2nQIW7bLJ\ 2500 | sX2ymfJTFVqyowu+bOee7AVkbMrtNgkzIaRajLVN/NdHOHefRuLj+R267ywLOYwZLU0DI2E5LxmLa2svwb8O+/lfi/Ya/kZMZXVZFKoqtfvSXy2uv/nBIstLN9i1i5b/mCxq/m7xl5hQMZlUhda//gfTQr5T\ 2501 | """))) 2502 | 2503 | 2504 | def _main(): 2505 | try: 2506 | main() 2507 | except FatalError as e: 2508 | print('\nA fatal error occurred: %s' % e) 2509 | sys.exit(2) 2510 | 2511 | 2512 | if __name__ == '__main__': 2513 | _main() 2514 | -------------------------------------------------------------------------------- /blynk-examples/P1_simplestgpio.py: -------------------------------------------------------------------------------- 1 | 2 | import gc, micropython 3 | from Oled import oled 4 | import settings 5 | settings.BlynkGpioAuto=True 6 | from pblynk import Blynk 7 | 8 | b=Blynk() 9 | def cnct_cb(): 10 | print ("Connected: ") 11 | b.on_connect(cnct_cb) 12 | 13 | b.gpio_auto() 14 | 15 | def Tfunc(st): 16 | print(st, gc.mem_free()) 17 | if oled: 18 | oled.invert(st & 1) 19 | return st+1 20 | 21 | b.Ticker(Tfunc,200,5) 22 | 23 | print( "Mem: ", gc.mem_free()) 24 | b.run() 25 | 26 | 27 | 28 | # Automatically sets any GPIO writes from APP as OUTPUT, and all GPIO reads (poll) from APP 29 | 30 | ###################################################################################### 31 | 32 | 33 | # at APP: 34 | # 3 buttons writing to GPIOs 21, 20, 16 (leds on rpi) 35 | # 3 LEDs polling gpios 6, 26 19 (rpi buttons) and 14 (rpi pir) 36 | 37 | 38 | -------------------------------------------------------------------------------- /blynk-examples/P2_customgpio.py: -------------------------------------------------------------------------------- 1 | 2 | # manually configured GPIOs 3 | 4 | 5 | from Oled import oled 6 | from pblynk import Blynk 7 | b=Blynk() 8 | from machine import Pin 9 | from micropython import schedule 10 | import utime as time 11 | 12 | 13 | 14 | ###################################################################################### 15 | 16 | # Button1 poll mode 17 | # Buttons or other inputs by manual coding. Direct GPIO (not Vpin) settings at app. 18 | # Flexible. can make Button, InputDevice etc pulldown etc as desired. 19 | # Slower latency 20 | 21 | # generic gpio read by POLL from app 22 | 23 | def gpioRead_h(pin, gpioObj): 24 | v = gpioObj() 25 | return (1 if v else 0) 26 | 27 | # set up any buttons (or whatever) and connect to gpioRead_h - say D3 gpio 0 flash button 28 | button1 = Pin(0, Pin.IN, Pin.PULL_UP) 29 | b.add_digital_hw_pin(0, gpioRead_h, None, button1) # note the button object is payload as "state" 30 | 31 | ###################################################################################### 32 | 33 | # interrupt button reads, debounced, sent to APP via vpin 34 | ts=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] # timestamp array for gpio 0 to gpio 15 35 | 36 | def i_cb(pin): # this generic fn is set to send to VPin number corresponding to GPIO number 37 | def i2_cb(data): 38 | time.sleep_ms(80) # wait till after any bounce 39 | b.virtual_write(data[1], data[0].value()*100) 40 | 41 | global ts 42 | pinnum = int(str(pin)[4:-1]) # ie turn 'Pin(14)' into 14 43 | ts1 = time.ticks_ms() 44 | if time.ticks_diff(ts1, ts[pinnum])< 100: # toss away keybounce spikes 45 | return 46 | ts[pinnum] = ts1 47 | try: 48 | schedule(i2_cb, [pin, pinnum]) # sched has small stack (4 !) and seems fragile anyway 49 | except: 50 | pass 51 | 52 | # Button D5 - push mode from 8266 gpio 14 - so send to APP on VP14 53 | buttonD5 = Pin(14, Pin.IN, Pin.PULL_UP) 54 | buttonD5.irq(i_cb, Pin.IRQ_FALLING|Pin.IRQ_RISING) 55 | 56 | 57 | 58 | ###################################################################################### 59 | 60 | # 3 LEDs + inbuilt Led on D4: 61 | def gpioWrite_h(val, pin, gpioObj): # generic gpio write (GPIO settings at app) 62 | gpioObj.value(val) 63 | 64 | # set up the RPi LEDs or other outputs and connect to generic gpioOut 65 | ledR = Pin(12, Pin.OUT) 66 | ledG = Pin(13, Pin.OUT) 67 | 68 | ledD4 = Pin(2, Pin.OUT) 69 | b.add_digital_hw_pin(12, None, gpioWrite_h, ledR) 70 | b.add_digital_hw_pin(13, None, gpioWrite_h, ledG) 71 | 72 | b.add_digital_hw_pin(2, None, gpioWrite_h, ledD4) 73 | 74 | 75 | #------------------------------ 76 | 77 | def cnct_cb(): 78 | print ("Connected: ") 79 | b.on_connect(cnct_cb) 80 | 81 | 82 | 83 | 84 | ###################################################################################### 85 | 86 | b.run() 87 | 88 | ###################################################################################### 89 | 90 | # at APP: 91 | # 3 buttons writing to GPIOs 92 | # 3 led widgets listening to gpios 93 | # one value display widget polling gpio 94 | -------------------------------------------------------------------------------- /blynk-examples/P2a_customgpio.py: -------------------------------------------------------------------------------- 1 | 2 | # manually configured GPIOs 3 | 4 | 5 | from Oled import oled 6 | from pblynk import Blynk 7 | b=Blynk() 8 | from machine import Pin 9 | from micropython import schedule 10 | 11 | 12 | 13 | ###################################################################################### 14 | 15 | # Button1 poll mode 16 | # Buttons or other inputs by manual coding. Direct GPIO (not Vpin) settings at app. 17 | # Flexible. can make Button, InputDevice etc pulldown etc as desired. 18 | # Slower latency 19 | 20 | # generic gpio read by POLL from app 21 | 22 | def gpioRead_h(pin, gpioObj): 23 | v = gpioObj() 24 | return (1 if v else 0) 25 | 26 | # set up any buttons (or whatever) and connect to gpioRead_h - say D3 gpio 0 flash button 27 | button1 = Pin(0, Pin.IN, Pin.PULL_UP) 28 | b.add_digital_hw_pin(0, gpioRead_h, None, button1) # note the button object is payload as "state" 29 | 30 | ###################################################################################### 31 | 32 | # pin.irq() allows only a callback functionname, with one automatic argument, the Pin object. 33 | # partial() function from functools library allows us to send more arguments from .irq(), 34 | # namely the gpio pin number that we need for the blynk virtual write. 35 | def partial(func, *args): 36 | # hacked from https://github.com/micropython/micropython-lib/blob/master/functools/functools.py 37 | def _partial(*more_args): 38 | return func(*(args + more_args)) 39 | return _partial 40 | 41 | def i2_cb(data): # this generic fn set to send to VPin number corresponding to GPIO number 42 | # data[0] = pinnumber, data[1] = pin.value(), 43 | b.virtual_write(data[0], data[1]*100) 44 | 45 | def i_cb(pinnumber, pin): # pin (obj) was auto from .irq(). pinnumber added due to partial() 46 | # we are in interrupt. Reschedule real processing to later. 47 | schedule(i2_cb, [pinnumber, pin.value()]) # read value asap. Toss pin obj, just use pin gpio# 48 | 49 | # Button D5 - push mode from 8266 gpio 14 - send to APP on VP14 50 | buttonD5 = Pin(14, Pin.IN, Pin.PULL_UP) 51 | buttonD5.irq(partial(i_cb, 14), Pin.IRQ_FALLING|Pin.IRQ_RISING) 52 | 53 | 54 | 55 | ###################################################################################### 56 | 57 | # 3 LEDs + inbuilt Led on D4: 58 | def gpioWrite_h(val, pin, gpioObj): # generic gpio write (GPIO settings at app) 59 | gpioObj.value(val) 60 | 61 | # set up the RPi LEDs or other outputs and connect to generic gpioOut 62 | ledR = Pin(12, Pin.OUT) 63 | ledG = Pin(13, Pin.OUT) 64 | 65 | ledD4 = Pin(2, Pin.OUT) 66 | b.add_digital_hw_pin(12, None, gpioWrite_h, ledR) 67 | b.add_digital_hw_pin(13, None, gpioWrite_h, ledG) 68 | 69 | b.add_digital_hw_pin(2, None, gpioWrite_h, ledD4) 70 | 71 | 72 | #------------------------------ 73 | 74 | def cnct_cb(): 75 | print ("Connected: ") 76 | b.on_connect(cnct_cb) 77 | 78 | 79 | 80 | 81 | ###################################################################################### 82 | 83 | b.run() 84 | 85 | ###################################################################################### 86 | 87 | # at APP: 88 | # 3 buttons writing to GPIOs 89 | # 3 led widgets listening to gpios 90 | # one value display widget polling gpio 91 | -------------------------------------------------------------------------------- /blynk-examples/P2b_customgpio.py: -------------------------------------------------------------------------------- 1 | 2 | # manually configured GPIOs 3 | 4 | 5 | from Oled import oled 6 | from pblynk import Blynk 7 | b=Blynk() 8 | from machine import Pin 9 | from micropython import schedule 10 | import utime as time 11 | 12 | 13 | 14 | ###################################################################################### 15 | 16 | # Button1 poll mode 17 | # Buttons or other inputs by manual coding. Direct GPIO (not Vpin) settings at app. 18 | # Flexible. can make Button, InputDevice etc pulldown etc as desired. 19 | # Slower latency 20 | 21 | # generic gpio read by POLL from app 22 | 23 | def gpioRead_h(pin, gpioObj): 24 | v = gpioObj() 25 | return (1 if v else 0) 26 | 27 | # set up any buttons (or whatever) and connect to gpioRead_h - say D3 gpio 0 flash button 28 | button1 = Pin(0, Pin.IN, Pin.PULL_UP) 29 | b.add_digital_hw_pin(0, gpioRead_h, None, button1) # note the button object is payload as "state" 30 | 31 | ###################################################################################### 32 | 33 | # interrupt button reads, debounced, sent to APP via vpin 34 | 35 | 36 | ########## UNTESTED ################ 37 | 38 | class blynkButton(object): 39 | def __init__(self, gpiopin, vpin): 40 | self.butn = Pin(gpiopin, Pin.IN, Pin.PULL_UP) 41 | self.butn.irq(self.i_cb, Pin.IRQ_FALLING|Pin.IRQ_RISING) 42 | self.ts=time.ticks_ms() 43 | self.vpin = vpin 44 | self.gpiopin = gpiopin 45 | 46 | def i_cb(self, pinObj): 47 | ts1 = time.ticks_ms() 48 | if time.ticks_diff(ts1, self.ts)< 100: # toss away keybounce spikes 49 | return 50 | self.ts = ts1 51 | try: 52 | schedule(self.i2_cb, None) # sched has small stack (4 !) and seems fragile anyway 53 | except: 54 | pass 55 | 56 | def i2_cb(self,dummy): 57 | time.sleep_ms(80) # wait till after any bounce 58 | b.virtual_write(self.vpin, self.butn.value()*100) 59 | 60 | buttonD5 = blynkButton(14, 3) # gpio 14 to send to vpin 3 61 | buttonD1 = blynkButton(5, 4) 62 | 63 | ###################################################################################### 64 | 65 | # 3 LEDs + inbuilt Led on D4: 66 | def gpioWrite_h(val, pin, gpioObj): # generic gpio write (GPIO settings at app) 67 | gpioObj.value(val) 68 | 69 | # set up the RPi LEDs or other outputs and connect to generic gpioOut 70 | ledR = Pin(12, Pin.OUT) 71 | ledG = Pin(13, Pin.OUT) 72 | 73 | ledD4 = Pin(2, Pin.OUT) 74 | b.add_digital_hw_pin(12, None, gpioWrite_h, ledR) 75 | b.add_digital_hw_pin(13, None, gpioWrite_h, ledG) 76 | 77 | b.add_digital_hw_pin(2, None, gpioWrite_h, ledD4) 78 | 79 | 80 | #------------------------------ 81 | 82 | def cnct_cb(): 83 | print ("Connected: ") 84 | b.on_connect(cnct_cb) 85 | 86 | 87 | 88 | 89 | ###################################################################################### 90 | 91 | b.run() 92 | 93 | ###################################################################################### 94 | 95 | # at APP: 96 | # 3 buttons writing to GPIOs 97 | # 3 led widgets listening to gpios 98 | # one value display widget polling gpio 99 | -------------------------------------------------------------------------------- /blynk-examples/P6_email.py: -------------------------------------------------------------------------------- 1 | 2 | import settings, sys, time 3 | settings.BlynkExtras = True # load the email functions 4 | from pblynk import Blynk 5 | import sntp 6 | b=Blynk() 7 | 8 | 9 | 10 | def cnct_cb(): 11 | print ("Connected: "+ sntp.asctime()) 12 | 13 | b.email("bl@blavery.com", "subject 44", "Test. Body") 14 | print("sent email via blynk server. nothing more to do in this demo. Exit!") 15 | time.sleep(3) 16 | sys.exit() 17 | b.on_connect(cnct_cb) 18 | 19 | 20 | 21 | ###################################################################################### 22 | 23 | b.run() 24 | 25 | ###################################################################################### 26 | 27 | # At APP: 28 | # nothing except put an email widget to screen. 29 | -------------------------------------------------------------------------------- /blynk-examples/p3_rlogger.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from Oled import oled 4 | from pblynk import Blynk 5 | import sntp, network 6 | b=Blynk() 7 | 8 | 9 | 10 | # rlogger 11 | 12 | rlctr = 0 13 | 14 | def Tfunc(st): 15 | # write to (terminal style) rlogger at APP on vp 18 16 | global rlctr 17 | rlctr = rlctr+1 18 | b.virtual_write(18, sntp.asctime()+ " "+str(rlctr) + "\n"); 19 | print(sntp.asctime(), rlctr) 20 | 21 | b.Ticker(Tfunc,1000) # 5 secs 22 | 23 | 24 | def cnct_cb(): 25 | print ("Connected: "+ sntp.asctime()[11:]) 26 | b.virtual_write(18, "\n"+network.WLAN(0).config("dhcp_hostname")+"\n") 27 | b.on_connect(cnct_cb) 28 | 29 | 30 | ###################################################################################### 31 | 32 | b.run() 33 | 34 | ###################################################################################### 35 | 36 | 37 | # At APP: 38 | # make terminal widget on that V18, fill whole phone screen, 39 | # turn off its input box 40 | # now it is a live-time remote log display screen rpi->app with rlogger.write() 41 | # but server WONT indefinitely buffer messages if APP is offline. 42 | 43 | # OR use labelled value box 44 | -------------------------------------------------------------------------------- /blynk-examples/p4_jlogger2oled.py: -------------------------------------------------------------------------------- 1 | 2 | from Oled import oled 3 | from pblynk import Blynk 4 | b=Blynk() 5 | 6 | 7 | 8 | 9 | if oled: 10 | oled.fill(0) 11 | oled.text("Hello",0,0,1) 12 | oled.show() 13 | 14 | def jlog(val, pin, st): 15 | if oled: 16 | oled.fill(0) 17 | oled.text(val[0],0,0,1) 18 | oled.show() 19 | print(val[0]) 20 | b.add_virtual_pin(19, write= jlog) 21 | 22 | 23 | 24 | 25 | ###################################################################################### 26 | 27 | b.run() 28 | 29 | ###################################################################################### 30 | 31 | 32 | 33 | # At 8266: 34 | # oled on i2c 35 | # 36 | # At APP: 37 | # Terminal widget on V19. only interested in its input window 38 | 39 | -------------------------------------------------------------------------------- /blynk-examples/p5_lcd.py: -------------------------------------------------------------------------------- 1 | 2 | import settings 3 | settings.BlynkExtras = True # load the lcd functions 4 | from pblynk import Blynk 5 | import sntp 6 | b=Blynk() 7 | 8 | 9 | 10 | # lcd widget 11 | 12 | ctr = 0 13 | 14 | def Tfunc(st): 15 | # write to (terminal style) rlogger at APP on vp 21 16 | global ctr 17 | ctr = ctr+1 18 | b.lcd_cls(21) 19 | b.lcd_print(21, 0,0, sntp.asctime()) 20 | b.lcd_print(21, 0,1,str(ctr)) 21 | print(sntp.asctime(), ctr) 22 | 23 | b.Ticker(Tfunc,1000) # 5 secs 24 | 25 | 26 | ###################################################################################### 27 | 28 | b.run() 29 | 30 | ###################################################################################### 31 | 32 | 33 | # At APP: 34 | # make LCD V21, 35 | 36 | 37 | -------------------------------------------------------------------------------- /images/23017.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/23017.jpg -------------------------------------------------------------------------------- /images/23017a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/23017a.png -------------------------------------------------------------------------------- /images/4051.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/4051.jpg -------------------------------------------------------------------------------- /images/4051a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/4051a.png -------------------------------------------------------------------------------- /images/4x8x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/4x8x8.png -------------------------------------------------------------------------------- /images/7seg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/7seg.jpg -------------------------------------------------------------------------------- /images/8591.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/8591.jpg -------------------------------------------------------------------------------- /images/8x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/8x8.png -------------------------------------------------------------------------------- /images/MQTT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/MQTT.png -------------------------------------------------------------------------------- /images/Trig.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/Trig.gif -------------------------------------------------------------------------------- /images/accel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/accel.jpg -------------------------------------------------------------------------------- /images/beeper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/beeper.jpg -------------------------------------------------------------------------------- /images/bl.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/bl.bmp -------------------------------------------------------------------------------- /images/blynk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/blynk.jpg -------------------------------------------------------------------------------- /images/browser.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/browser.jpg -------------------------------------------------------------------------------- /images/d1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/d1.jpg -------------------------------------------------------------------------------- /images/dispBox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/dispBox.jpg -------------------------------------------------------------------------------- /images/esp-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/esp-12.png -------------------------------------------------------------------------------- /images/esp01-uart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/esp01-uart.png -------------------------------------------------------------------------------- /images/esp01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/esp01.jpg -------------------------------------------------------------------------------- /images/gpio.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/gpio.bmp -------------------------------------------------------------------------------- /images/i.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/i.bmp -------------------------------------------------------------------------------- /images/init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/init.png -------------------------------------------------------------------------------- /images/irrx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/irrx.jpg -------------------------------------------------------------------------------- /images/kb3x4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/kb3x4.png -------------------------------------------------------------------------------- /images/library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/library.png -------------------------------------------------------------------------------- /images/log.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/log.jpg -------------------------------------------------------------------------------- /images/nodemcu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/nodemcu.png -------------------------------------------------------------------------------- /images/oled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/oled.jpg -------------------------------------------------------------------------------- /images/project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/project.jpg -------------------------------------------------------------------------------- /images/putty1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/putty1.png -------------------------------------------------------------------------------- /images/putty2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/putty2.png -------------------------------------------------------------------------------- /images/rollpitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/rollpitch.png -------------------------------------------------------------------------------- /images/servo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/servo.jpg -------------------------------------------------------------------------------- /images/sr04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/sr04.jpg -------------------------------------------------------------------------------- /images/star.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/star.bmp -------------------------------------------------------------------------------- /images/star1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/star1.bmp -------------------------------------------------------------------------------- /images/stepper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/stepper.jpg -------------------------------------------------------------------------------- /images/telnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/telnet.png -------------------------------------------------------------------------------- /images/temptspeak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/temptspeak.png -------------------------------------------------------------------------------- /images/tft144.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/tft144.jpg -------------------------------------------------------------------------------- /images/time1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/time1.png -------------------------------------------------------------------------------- /images/time2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/time2.png -------------------------------------------------------------------------------- /images/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/wifi.png -------------------------------------------------------------------------------- /images/zzzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLavery/psuite-upython/8522637babe2eda32d4b3140a156574bb5930d32/images/zzzz.png -------------------------------------------------------------------------------- /lib/Oled.py: -------------------------------------------------------------------------------- 1 | from machine import I2C, Pin 2 | import settings, network, sntp 3 | 4 | oled=False 5 | _i2c=I2C(-1, Pin(settings.scl), Pin(settings.sda)) 6 | if 60 in set(_i2c.scan()): 7 | import ssd1306 8 | oled=ssd1306.SSD1306_I2C(settings.oledtype[0],settings.oledtype[1],_i2c) # what pixels? 9 | _t=sntp.asctime() 10 | _ip=network.WLAN(network.STA_IF).ifconfig()[0] 11 | _dot2=_ip.index('.', _ip.index('.') + 1) 12 | # squeezed to 64x48 oled as per wemos D1 shield 13 | oled.text("pSuite",0,0,1) # txt, x, y, colour 14 | oled.text(_ip[0:_dot2+1] ,0,10,1) 15 | oled.text(_ip[_dot2+1:] ,6,20,1) 16 | oled.text(_t[11:],0,30,1) 17 | oled.text(_t[0:6]+_t[8:10],0,40,1) # date yy not yyyy ( fits D1 mini oled) 18 | oled.show() 19 | 20 | # better layout option for 128x64 display? 21 | 22 | # see framebuf calls: oled uses framebuf: 0=black 1=white 23 | # http://docs.micropython.org/en/latest/esp8266/library/framebuf.html#module-framebuf 24 | -------------------------------------------------------------------------------- /lib/blynk_0.py: -------------------------------------------------------------------------------- 1 | 2 | from micropython import const 3 | 4 | HDR_LEN = const(5) 5 | HDR_FMT = "!BHH" 6 | 7 | MAX_MSG_PER_SEC = const(20) 8 | 9 | MSG_RSP = const(0) 10 | MSG_LOGIN = const(2) 11 | MSG_PING = const(6) 12 | MSG_TWEET = const(12) 13 | MSG_EMAIL = const(13) 14 | MSG_NOTIFY = const(14) 15 | MSG_BRIDGE = const(15) 16 | MSG_HW_SYNC = const(16) 17 | MSG_HW_INFO = const(17) 18 | MSG_PROPERTY = const(19) 19 | MSG_HW = const(20) 20 | 21 | STA_SUCCESS = const(200) 22 | 23 | HB_PERIOD = const(10) 24 | NON_BLK_SOCK = const(0) 25 | MIN_SOCK_TO = const(1) # 1 second 26 | MAX_SOCK_TO = const(5) # 5 seconds 27 | RECONNECT_DELAY = const(2) # 1 second 28 | TASK_PERIOD_RES = const(50) # 50 ms 29 | IDLE_TIME_MS = const(5) # 5 ms 30 | 31 | RE_TX_DELAY = const(2) 32 | MAX_TX_RETRIES = const(3) 33 | 34 | MAX_VIRTUAL_PINS = const(32) 35 | 36 | DISCONNECTED = const(0) 37 | CONNECTING = const(1) 38 | AUTHENTICATING = const(2) 39 | AUTHENTICATED = const(3) 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/blynk_1.py: -------------------------------------------------------------------------------- 1 | import socket, errno 2 | import ustruct as struct 3 | import utime as time 4 | 5 | from blynk_0 import * 6 | 7 | def idle_loop (self, start, delay): 8 | # 200 Hz loop 9 | if self._on_tick: 10 | self._tick_count = 1 + self._tick_count 11 | if self._tick_count == self._tick_scale: 12 | self._tick_count = 0 13 | rtn=self._on_tick(self._tick_state) 14 | if rtn != None: 15 | self._tick_state = rtn 16 | while (time.ticks_ms()-start) < delay: 17 | pass 18 | return start + delay 19 | 20 | def _format_msg(self, msg_type, *args): 21 | data = bytes('\0'.join(map(str, args)), 'ascii') 22 | return struct.pack(HDR_FMT, msg_type, self._new_msg_id(), len(data)) + data 23 | 24 | def _handle_hw(self, data): 25 | params = list(map(lambda x: x.decode('ascii'), data.split(b'\0'))) 26 | cmd = params.pop(0) 27 | if cmd == 'pm': 28 | if self._on_setup: 29 | self._on_setup(params) 30 | self._pins_configured = True 31 | elif cmd == 'vw': 32 | pin = int(params.pop(0)) 33 | if pin in self._vr_pins and self._vr_pins[pin].write: 34 | self._vr_pins[pin].write(params, pin, self._vr_pins[pin].state) 35 | elif cmd == 'vr': 36 | pin = int(params.pop(0)) 37 | if pin in self._vr_pins and self._vr_pins[pin].read: 38 | val = self._vr_pins[pin].read(pin, self._vr_pins[pin].state) 39 | if val != None : 40 | self.virtual_write(pin, val) 41 | elif self._pins_configured: 42 | if cmd == 'dw': 43 | pin = int(params.pop(0)) 44 | val = int(params.pop(0)) 45 | if pin in self._digital_hw_pins: 46 | if self._digital_hw_pins[pin].write is not None: 47 | self._digital_hw_pins[pin].write(val, pin, self._digital_hw_pins[pin].state) 48 | 49 | elif cmd == 'dr': 50 | pin = int(params.pop(0)) 51 | if pin in self._digital_hw_pins: 52 | if self._digital_hw_pins[pin].read is not None: 53 | val = self._digital_hw_pins[pin].read(pin, self._digital_hw_pins[pin].state) 54 | if val != None: 55 | self._send(self._format_msg(MSG_HW, 'dw', pin, val)) 56 | 57 | #else: 58 | # raise ValueError("Unknown message cmd: %s" % cmd) 59 | 60 | def _new_msg_id(self): 61 | self._msg_id += 1 62 | if (self._msg_id > 0xFFFF): 63 | self._msg_id = 1 64 | return self._msg_id 65 | 66 | def _settimeout(self, timeout): 67 | if timeout != self._timeout: 68 | self._timeout = timeout 69 | self.conn.settimeout(timeout) 70 | 71 | 72 | def _recv(self, length, timeout=0): 73 | 74 | self._settimeout(timeout) 75 | try: 76 | self._rx_data += self.conn.recv(length) 77 | except OSError as e: 78 | if e.args[0] == errno.EAGAIN or e.args[0] == errno.ETIMEDOUT: 79 | return b'' 80 | else: 81 | print("RX Error") 82 | self._must_close = True 83 | return b'' 84 | 85 | 86 | if len(self._rx_data) >= length: 87 | data = self._rx_data[:length] 88 | self._rx_data = self._rx_data[length:] 89 | return data 90 | else: 91 | return b'' 92 | 93 | 94 | def _sendL(self, data, send_anyway): # locked 95 | if self._tx_count < MAX_MSG_PER_SEC or send_anyway: 96 | retries = 0 97 | while retries <= MAX_TX_RETRIES: 98 | try: 99 | 100 | self.conn.send(data) 101 | self._tx_count += 1 102 | 103 | break 104 | 105 | except OSError as er: 106 | if er.args[0] != errno.EAGAIN: 107 | print(er) 108 | return False 109 | else: 110 | time.sleep_ms(RE_TX_DELAY) 111 | retries += 1 112 | return True 113 | 114 | def _send(self, data, send_anyway=False): 115 | #self.lock.acquire() # lock against reentrancy - no longer reqd 116 | was_sent = self._sendL(data, send_anyway) 117 | #self.lock.release() 118 | if not was_sent: 119 | print("Send Error") 120 | self._must_close = True 121 | 122 | def _close(self, emsg=None): 123 | self.conn.close() 124 | self._rx_data = b'' 125 | self._failed_pings = 0 126 | if emsg: 127 | print('[closed] ' , emsg) 128 | if self.state == AUTHENTICATED: 129 | if self._on_disconnect: 130 | self._on_disconnect() 131 | self.state = DISCONNECTED 132 | time.sleep(RECONNECT_DELAY) 133 | 134 | def _server_alive(self): 135 | c_time = int(time.time()) 136 | if self._m_time != c_time: # 1/sec 137 | self._m_time = c_time 138 | self._tx_count = 0 139 | 140 | if c_time - self._hb_time >= HB_PERIOD and self.state == AUTHENTICATED: 141 | # time to issue another heartbeat ping 142 | if self._last_hb_id != 0: 143 | self._failed_pings += 1 144 | print("PING unanswered" + str(self._failed_pings)) 145 | if self._failed_pings > 1: # 2 strikes & y're OUT 146 | return False 147 | self._hb_time = c_time 148 | self._last_hb_id = self._new_msg_id() 149 | self._send(struct.pack(HDR_FMT, MSG_PING, self._last_hb_id, 0), True) 150 | return True 151 | 152 | -------------------------------------------------------------------------------- /lib/blynk_2.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import ustruct as struct 3 | import utime as time 4 | 5 | from blynk_0 import * 6 | 7 | 8 | def run(self): 9 | 10 | #Run the Blynk client (blocking mode) 11 | 12 | self._start_time = time.ticks_ms() 13 | self._task_millis = self._start_time # nyi 14 | self._rx_data = b'' 15 | self._msg_id = 1 16 | self._pins_configured = False 17 | self._timeout = -1 18 | self._tx_count = 0 19 | self._m_time = 0 20 | self._must_close = False 21 | 22 | 23 | while True: # loop forever 24 | self._must_close = False 25 | while self.state != AUTHENTICATED: 26 | if self._do_connect: 27 | try: 28 | self.state = CONNECTING 29 | print('TCP: Connecting to' , self._server, self._port) 30 | self.conn = socket.socket() 31 | self.conn.connect(socket.getaddrinfo(self._server, self._port)[0][4]) 32 | except: 33 | self._close('Fail to connect') 34 | continue 35 | 36 | 37 | self.state = AUTHENTICATING 38 | hdr = struct.pack(HDR_FMT, MSG_LOGIN, self._new_msg_id(), len(self._token)) 39 | print('authenticating...') 40 | self._send(hdr + self._token, True) 41 | data = self._recv(HDR_LEN, timeout=MAX_SOCK_TO) 42 | if not data: 43 | self._close('Authentication t/o') 44 | continue 45 | 46 | msg_type, msg_id, status = struct.unpack(HDR_FMT, data) 47 | if status != STA_SUCCESS or msg_id == 0: 48 | self._close('Authentication fail') 49 | continue 50 | 51 | self.state = AUTHENTICATED 52 | self._send(self._format_msg(MSG_HW_INFO, "h-beat", HB_PERIOD, 'dev', 'esp', "cpu", "8266")) 53 | print('Blynk Access.') 54 | if self._on_connect: 55 | self._on_connect() 56 | self._start_time = time.ticks_ms() 57 | else: 58 | self._start_time = self.idle_loop(self._start_time, TASK_PERIOD_RES) 59 | 60 | self._hb_time = 0 61 | self._last_hb_id = 0 62 | self._tx_count = 0 63 | self._must_close = False 64 | while self._do_connect: 65 | data = self._recv(HDR_LEN, NON_BLK_SOCK) 66 | if data: 67 | msg_type, msg_id, msg_len = struct.unpack(HDR_FMT, data) 68 | if msg_id == 0: 69 | pass 70 | if msg_type == MSG_RSP: 71 | if msg_id == self._last_hb_id: 72 | self._last_hb_id = 0 73 | self._failed_pings = 0 74 | elif msg_type == MSG_PING: 75 | self._send(struct.pack(HDR_FMT, MSG_RSP, msg_id, STA_SUCCESS), True) 76 | elif msg_type == MSG_HW or msg_type == MSG_BRIDGE: 77 | data = self._recv(msg_len, MIN_SOCK_TO) 78 | if data: 79 | 80 | self._handle_hw(data) 81 | else: 82 | self._close('unknown msg ', msg_type) 83 | break 84 | else: 85 | self._start_time = self.idle_loop(self._start_time, IDLE_TIME_MS) 86 | if not self._server_alive(): 87 | self._close('Server off') 88 | break 89 | if self._must_close: 90 | self._close('Network err') 91 | break 92 | 93 | 94 | 95 | if not self._do_connect: 96 | self._close() 97 | print('Blynk discon req') 98 | 99 | -------------------------------------------------------------------------------- /lib/blynk_3.py: -------------------------------------------------------------------------------- 1 | from blynk_0 import * 2 | 3 | 4 | def notify(self, msg): 5 | if self.state == AUTHENTICATED: 6 | self._send(self._format_msg(MSG_NOTIFY, msg)) 7 | 8 | def virtual_write(self, pin, val): 9 | if self.state == AUTHENTICATED: 10 | if type(val) == type([]): 11 | val = '\0'.join(map(str, val)) 12 | self._send(self._format_msg(MSG_HW, 'vw', pin, val)) 13 | 14 | def Ticker(self, func, divider = None, initial_state = None): 15 | if divider: 16 | self._tick_scale = divider 17 | self._on_tick = func 18 | self._tick_state = initial_state 19 | 20 | def add_virtual_pin(self, pin, read=None, write=None, initial_state=None): 21 | if isinstance(pin, int) and pin in range(0, MAX_VIRTUAL_PINS): 22 | self._vr_pins[pin] = self.VrPin(read=read, write=write, blynk_ref=self, initial_state=initial_state) 23 | else: 24 | raise ValueError('pin only 0 to ' , (MAX_VIRTUAL_PINS - 1)) 25 | 26 | def add_digital_hw_pin(self, pin, read=None, write=None, inital_state=None): 27 | if isinstance(pin, int): 28 | self._digital_hw_pins[pin] = self.HwPin(read=read, write=write, blynk_ref=self, initial_state=inital_state) 29 | else: 30 | raise ValueError("pin value INTEGER!") 31 | 32 | def on_connect(self, func): 33 | self._on_connect = func 34 | 35 | def on_disconnect(self, func): 36 | self._on_disconnect = func 37 | 38 | def connect(self): 39 | self._do_connect = True 40 | 41 | def disconnect(self): 42 | self._do_connect = False 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/blynk_4.py: -------------------------------------------------------------------------------- 1 | from blynk_0 import * 2 | from machine import Pin 3 | 4 | def gpio_auto(self, Pull = None): # Pull must be None or machine.Pin.PULL_UP (ie 1) 5 | 6 | def gpioRead_h(pin, gpioObj): 7 | v = gpioObj.value() 8 | return (1 if v else 0) 9 | 10 | def gpioWrite_h(val, pin, gpioObj): 11 | gpioObj.value(val) 12 | 13 | def setup_cb(params): 14 | # do gpio setup 15 | pairs = zip(params[0::2], params[1::2]) 16 | for (pin, mode) in pairs: 17 | pin = int(pin) 18 | print(pin,mode) 19 | if mode == "out": 20 | led = Pin(pin, Pin.OUT) 21 | self.add_digital_hw_pin(pin, None, gpioWrite_h, led) 22 | if mode == "in": 23 | button = Pin(pin, Pin.IN, pull=Pull) # global pullup or not 24 | self.add_digital_hw_pin(pin, gpioRead_h, None, button) 25 | # note the button / led objects are payload as "state" 26 | 27 | self._on_setup=setup_cb 28 | 29 | -------------------------------------------------------------------------------- /lib/blynk_5.py: -------------------------------------------------------------------------------- 1 | from blynk_0 import * 2 | 3 | def sync_all(self): 4 | if self.state == AUTHENTICATED: 5 | self._send(self._format_msg(MSG_HW_SYNC)) 6 | 7 | def sync_virtual(self, pin): 8 | if self.state == AUTHENTICATED: 9 | self._send(self._format_msg(MSG_HW_SYNC, 'vr', pin)) 10 | 11 | def tweet(self, msg): 12 | if self.state == AUTHENTICATED: 13 | self._send(self._format_msg(MSG_TWEET, msg)) 14 | 15 | def email(self, to, subject, body): 16 | if self.state == AUTHENTICATED: 17 | self._send(self._format_msg(MSG_EMAIL, to, subject, body)) 18 | 19 | def set_property(self, pin, prop, val): 20 | if self.state == AUTHENTICATED: 21 | if type(val) == type([]): 22 | val = '\0'.join(map(str, val)) 23 | self._send(self._format_msg(MSG_PROPERTY, pin, prop, val)) 24 | 25 | def lcd_print(self, vpin, x, y, msg): 26 | self.virtual_write(vpin, [ "p", str(x%16), str(y%2), msg]) 27 | 28 | def lcd_cls(self, vpin): 29 | self.virtual_write(vpin, "clr") 30 | 31 | -------------------------------------------------------------------------------- /lib/blynk_6.py: -------------------------------------------------------------------------------- 1 | from blynk_0 import * 2 | 3 | 4 | class BRIDGE: 5 | def __init__(self, blynk, pin): 6 | self._blynk = blynk 7 | self._pin = pin 8 | 9 | def set_auth_token(self, target_token): # but wait until "connected" ! 10 | self._blynk._bridge_write( [str(self._pin), "i", target_token]) 11 | 12 | def digital_write(self, target_gpiopin, val): 13 | self._blynk._bridge_write( [str(self._pin), "dw", target_gpiopin, val]) 14 | 15 | def virtual_write(self, target_vpin, val): 16 | self._blynk._bridge_write( [str(self._pin), "vw", target_vpin, val]) 17 | 18 | def bridge_widget(self, pin): 19 | brw = self.BRIDGE(self, pin) 20 | self.add_virtual_pin(pin) 21 | return brw 22 | 23 | def _bridge_write(self, val): 24 | if self.state == AUTHENTICATED: 25 | if type(val) == type([]): 26 | val = '\0'.join(map(str, val)) 27 | self._send(self._format_msg(MSG_BRIDGE, val)) 28 | 29 | -------------------------------------------------------------------------------- /lib/boot.py: -------------------------------------------------------------------------------- 1 | #This file is executed on every boot (including wake-boot from deepsleep) 2 | print("Mem:",gc.mem_free()) 3 | import network 4 | Wifi = network.WLAN(network.STA_IF) 5 | Wifi.active(True) # allow auto reconnect 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/main.py: -------------------------------------------------------------------------------- 1 | import utime as time 2 | from machine import Pin 3 | import settings 4 | 5 | Pin(2,Pin.OUT).value(0) 6 | time.sleep(1.8) 7 | if not Pin(0, Pin.IN, Pin.PULL_UP).value(): # D3 pulled to gnd (flash button pressed)?? 8 | print("Abort") 9 | import sys 10 | sys.exit() # if so, abort 11 | Pin(2,Pin.OUT).value(1) 12 | 13 | import wifi 14 | import sntp 15 | 16 | gc.collect() 17 | print("Mem: ", gc.mem_free(), "\nProject: ", settings.project) 18 | exec("import " + settings.project) 19 | gc.collect() 20 | print("Mem: ", gc.mem_free()) 21 | 22 | #exec(open("./filename").read()) 23 | # https://stackoverflow.com/questions/436198/what-is-an-alternative-to-execfile-in-python-3 24 | 25 | import Oled 26 | -------------------------------------------------------------------------------- /lib/pblynk.py: -------------------------------------------------------------------------------- 1 | from blynk_0 import * 2 | import gc, settings 3 | 4 | 5 | class Blynk: 6 | def __init__(self, token=None, server='blynk-cloud.com', port=8442, connect=True): 7 | self._vr_pins = {} 8 | self._do_connect = False 9 | self._on_connect = None 10 | self._on_disconnect = None 11 | self._on_setup = None 12 | self._on_tick = None 13 | self._tick_scale = 40 14 | self._tick_count = 0 15 | self._tick_state = None 16 | self._token = token if token else settings.Token 17 | if isinstance(self._token, str): 18 | self._token = str.encode(self._token) 19 | self._server = server 20 | self._port = port 21 | self._do_connect = connect 22 | self._digital_hw_pins = {} 23 | self.state = DISCONNECTED 24 | self._failed_pings = 0 25 | 26 | 27 | 28 | class VrPin: 29 | def __init__(self, read=None, write=None, blynk_ref=None, initial_state=None): 30 | self.read = read 31 | self.write = write 32 | self.state = initial_state if initial_state is not None else {} 33 | self.blynk_ref = blynk_ref 34 | 35 | 36 | class HwPin: 37 | def __init__(self, read=None, write=None, blynk_ref=None, initial_state=None): 38 | self.read = read 39 | self.write = write 40 | self.state = initial_state if initial_state is not None else {} 41 | self.blynk_ref = blynk_ref 42 | 43 | 44 | 45 | # separately import the bulk of class Blynk's member functions, 46 | # in layers (to avoid memory crash), 47 | # and attach them all into class Blynk: 48 | 49 | import blynk_1 50 | import blynk_2 51 | import blynk_3 52 | # 1-3 = blynk core. 4-6 = optional 53 | blynk_4="" 54 | if settings.BlynkGpioAuto: 55 | import blynk_4 # adds 900 b 56 | gc.collect() 57 | blynk_5="" 58 | if settings.BlynkExtras: 59 | import blynk_5 # adds 1250 b 60 | gc.collect() 61 | blynk_6="" 62 | if settings.BlynkBridge: 63 | import blynk_6 # adds 1280 b 64 | gc.collect() 65 | 66 | for bl in (blynk_1, blynk_2, blynk_3, blynk_4, blynk_5, blynk_6): 67 | for a in dir(bl): 68 | typ=str(getattr(bl,a))[0:5] 69 | if typ == (" 6 | 7 | ## Intro 8 | 9 | A single py file for blynk is too large to load in the ESP8266 available memory. It's the initial loading process that uses most memory, and once it's loaded and compiled to bytecode it is (just) workable in the memory available. I have tried to use the mpy-cross compiler to convert it to mpy bytecode, so far without success. I find certain syntax constructions that run in py code fail in mpy code. There may be a solution, but I haven't found it yet. 10 | 11 | So the blynk library in pSuite is in py file(s) and is loaded in stages. You import pblynk.py and that chains to several files blynk_1.py, blynk_2.py etc automatically. The code is a crude stitch-together that does actually work. 12 | 13 | But be aware that the blynk library still leaves available RAM low (about 9-10 kB), and if your "project" code grows large, you should expect memory crashes. Use blynk for modest projects. 14 | 15 | On your APP, use device type = **esp8266**. GPIO numbers are native chip GPIO references, not D0 D1 etc as labelled on some boards. 16 | 17 | ## Creating the blynk object 18 | 19 | Your blynk token should be edited in your settings.py file.There are also 3 blynk options in settings: 20 | 21 | - BlynkBridge 22 | - BlynkExtras (sync, tweet, setproperty, lcd, email) 23 | - BlynkGpioAuto. 24 | 25 | These if True will add extra 26 | functionality to Blynk, at the expense of precious RAM memory. These same settings 27 | can be overridden in project file before you import blynk. 28 | 29 | Normal startup: 30 | 31 | import pblynk 32 | b = pblynk.Blynk() 33 | 34 | or in full: 35 | 36 | b = pblynk.Blynk(token, server, port, connect) 37 | # defaults = settings.Token, "blynk-cloud.com", 8442, True 38 | 39 | -connection is TCP (no ssl option) 40 | 41 | Adding options: 42 | 43 | import settings, machine 44 | settings.BlynkGpioAuto=True 45 | import pblynk 46 | b = pblynk.Blynk() 47 | b.gpio_auto(machine.Pin.PULL_UP) 48 | 49 | At this point a blynk instance ("b") is created, but it has **not** tried to connect to the Blynk cloud. 50 | That is done with b.run(), which needs to be last line of script. 51 | b.run() is a **blocking function**, and does NOT return to your script. 52 | 53 | 54 | ## GPIO control with zero coding 55 | 56 | 57 | b.gpio_auto(Pull) # where Pull = machine.Pin.PULL_UP or is omitted. 58 | 59 | This causes all GPIOs that the phone APP is configured for will be 60 | automatically configured as inputs or outputs on the ESP8266. And all GPIO 61 | writes or reads (polls) issued by the APP will be handled at the ESP 62 | without explicit coding. This function must be pre-optioned as above before importing blynk. 63 | 64 | The optional resistor pullup will apply to ALL the gpio autoconfigured as inputs. 65 | 66 | Note that in many practical cases this will be too simplistic, and then custom coding is needed instead, as in the next section. 67 | 68 | 69 | ## GPIO & Virtual Read & Write Callbacks 70 | 71 | 72 | The callbacks are like this, and should be def'd before doing the add_xxx_pin(): 73 | 74 | def digital_read_callback(pin, state): 75 | digital_value = xxx # access your hardware 76 | return digital_value 77 | 78 | def digital_write_callback(value, pin, state): 79 | # access the necessary digital output and write the value 80 | return 81 | 82 | def virtual_read_callback(pin, state): 83 | virtual_value = 'Anything' # access your hardware 84 | return virtual_value # return may be None, or a single value, or a list of values 85 | # if using return None, then arrange own virtual.write() back to APP, 86 | # but the "correct" way is to "return" the value to APP 87 | 88 | def virtual_write_callback(value, pin, state): 89 | # NOTE: "value" is a LIST, so you need to unpack eg value[0] etc 90 | # access the necessary virtual output and write the value 91 | return 92 | 93 | Then you assign your gpio or virtual pins like this: 94 | 95 | b.add_digital_hw_pin(pin=pin_number, read=digital_read_callback, inital_state=None) 96 | b.add_digital_hw_pin(pin=pin_number, write=digital_write_callback, inital_state=None) 97 | b.add_virtual_pin(vpin_number, read=virtual_read_callback, inital_state=None) 98 | b.add_virtual_pin(vpin_number, write=virtual_write_callback, inital_state=None) 99 | # in this context, write means "APP writes to 8266 HW" and read is "APP polls HW expecting HW reply" 100 | 101 | - initial_state is an optional payload of one value. 102 | - That one initial-state value may possibly be one LIST of values if you want to really pass several. 103 | - Gpio pins are actual GPIO hardware numbers. 104 | - Virtual pins are 0 - 40. 105 | - There is no support for analog pins. Use a virtual pin as needed. 106 | 107 | ## User Task: the "Ticker": 108 | 109 | 110 | It is possible to set up one periodic user task known as the Ticker. This is a function call from Blynk back to your project script. It is NOT a concurrent or threaded task. 111 | 112 | 113 | Ticker is a repeating function 114 | 115 | - Register and start (one only) simple "ticker" function callback. 116 | - "divider" (default 40) divides into 200 to give ticker frequency. eg divider 100 gives 2 ticks / sec. Don't rely strictly on the timing. It may stretch slightly if blynk is busy. 117 | - Use "state" to carry any data between calls. 118 | - Callback suspends blynk until its return. Not concurrent. Ticker should exit promptly to not hold up blynk. (eg 3 mSec would be considered quite too long.) 119 | 120 | Define your callback, then register your Ticker: 121 | 122 | def ticker_callback(state): 123 | # do anything you like. Might be complex or long, but it should be still fast. 124 | # Examine gpio pins? Do a virtual pin write? print to terminal or oled? ... 125 | return new_state # or just return 126 | b.Ticker(ticker_callback, divider=40, initial_state = None) 127 | b.Ticker(None) # disables ! 128 | 129 | 130 | ## Software functions and widgets at python end: 131 | 132 | b.notify(message_text) 133 | 134 | The following bridge group need to be optioned on in your settings: 135 | 136 | bridge = b.bridge_widget(my_vpin_number) # all writes to this widget get bridged to other HW 137 | bridge.set_auth_token(target_token) # but first wait until "connected"! Use a on_connect() callback. 138 | bridge.virtual_write(target_vpin, val) # val = single param only, no lists 139 | bridge.digital_write(target_gpiopin, val) 140 | 141 | *A "bridge" allows you to send writes across to another MCU also running blynk (RPi, 8266, etc). You devote one of your virtual pins as a channel via the server to the other device. And you need to know the separate token (on your blynk account) used by the other hardware.* 142 | 143 | This next extras group also needs to be optioned on in your settings: 144 | 145 | b.lcd_print(vpinnumber, x, y, message) # x=0-15 y=0-1 "advanced" mode at APP 146 | b.lcd_cls(vpinnumber) 147 | b.email([to,] subject, body) # don't forget email widget on APP! 148 | b.tweet(message_text) # (same) 149 | b.virtual_write(vpin_number, value) 150 | # value = either single value (int/str) or list of values. 151 | # For ad-hoc writing to a vpin (ie toward APP), 152 | # without necessarily having done add_virtual_pin() 153 | # For this context, write means 8266 to APP 154 | b.set_property(vpin, property, value) 155 | # eg "color", "#ED9D00" or "label"/"labels" "onLabel" etc 156 | 157 | The main non-returning loop of the blynk engine: 158 | 159 | b.run() # Last line of python script 160 | 161 | And ... 162 | 163 | b.on_connect(connect_callback) 164 | b.on_disconnect(disconnect_callback) 165 | b.connect() 166 | b.disconnect() 167 | 168 | https://github.com/BLavery/psuite-upython/blob/master/pblynk.md 169 | 170 | This blynk is hacked down from PiBlynk: see https://github.com/BLavery/PiBlynk 171 | -------------------------------------------------------------------------------- /pblynk.py: -------------------------------------------------------------------------------- 1 | from blynk_0 import * 2 | import gc, settings 3 | 4 | 5 | class Blynk: 6 | def __init__(self, token=None, server='blynk-cloud.com', port=8442, connect=True): 7 | self._vr_pins = {} 8 | self._do_connect = False 9 | self._on_connect = None 10 | self._on_disconnect = None 11 | self._on_setup = None 12 | self._on_tick = None 13 | self._tick_scale = 40 14 | self._tick_count = 0 15 | self._tick_state = None 16 | self._token = token if token else settings.Token 17 | if isinstance(self._token, str): 18 | self._token = str.encode(self._token) 19 | self._server = server 20 | self._port = port 21 | self._do_connect = connect 22 | self._digital_hw_pins = {} 23 | self.state = DISCONNECTED 24 | self._failed_pings = 0 25 | 26 | 27 | 28 | class VrPin: 29 | def __init__(self, read=None, write=None, blynk_ref=None, initial_state=None): 30 | self.read = read 31 | self.write = write 32 | self.state = initial_state if initial_state is not None else {} 33 | self.blynk_ref = blynk_ref 34 | 35 | 36 | class HwPin: 37 | def __init__(self, read=None, write=None, blynk_ref=None, initial_state=None): 38 | self.read = read 39 | self.write = write 40 | self.state = initial_state if initial_state is not None else {} 41 | self.blynk_ref = blynk_ref 42 | 43 | 44 | 45 | # separately import the bulk of class Blynk's member functions, 46 | # in layers (to avoid memory crash), 47 | # and attach them all into class Blynk: 48 | 49 | import blynk_1 50 | import blynk_2 51 | import blynk_3 52 | # 1-3 = blynk core. 4-6 = optional 53 | blynk_4="" 54 | if settings.BlynkGpioAuto: 55 | import blynk_4 # adds 900 b 56 | gc.collect() 57 | blynk_5="" 58 | if settings.BlynkExtras: 59 | import blynk_5 # adds 1250 b 60 | gc.collect() 61 | blynk_6="" 62 | if settings.BlynkBridge: 63 | import blynk_6 # adds 1280 b 64 | gc.collect() 65 | 66 | for bl in (blynk_1, blynk_2, blynk_3, blynk_4, blynk_5, blynk_6): 67 | for a in dir(bl): 68 | typ=str(getattr(bl,a))[0:5] 69 | if typ == ("