├── 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 == ("