├── .gitignore ├── LICENSE ├── README.md ├── amg88xx.py ├── amg_test.py ├── cam.py ├── cam_lcd.py ├── images ├── build.jpg ├── cat.jpg ├── chair.jpg ├── coffee.jpg └── fingers.jpg ├── interpolate ├── README.md ├── cam_interp.py ├── interpolate.py └── interpolate_a.py ├── mapper.py ├── my_cam ├── README.md ├── amg88xx.py ├── arial10.py ├── aswitch.py ├── cam.py ├── courier17.py ├── font10.py ├── interpolate_a.py ├── mapper.py ├── pir1.fzz ├── ssd1351_16bit.py └── writer.py └── ssd1331.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Peter Hinch 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 | # micropython-amg88xx 2 | Driver for Grid-EYE 8x8 pixel thermal infra red array sensor (Adafruit 3538). 3 | 4 | Now provides optional bicubic interpolation for camera displays. 5 | 6 | The driver is a port of the 7 | [Adafruit CircuitPython driver](https://github.com/adafruit/Adafruit_CircuitPython_AMG88xx) 8 | modified for MicroPython. 9 | 10 | Original author(s): Dean Miller, Scott Shawcroft. 11 | Adapted by Peter Hinch. Dependencies on Adafruit libraries removed, coding 12 | adapted to conform to MicroPython conventions. Extended to provide additional 13 | functionality. 14 | 15 | A typical camera build: 16 | ![Image](images/build.jpg) 17 | Interpolated images (a cup of coffee): 18 | ![Image](images/coffee.jpg) 19 | My chair a while after I had got up: 20 | ![Image](images/chair.jpg) 21 | A re-creation of the Adafruit [fingers](https://www.adafruit.com/product/3538) 22 | demo. Note that their version uses numpy and scipy on a Raspberry Pi as against 23 | bicubic interpolation on a Pyboard. 24 | ![Image](images/fingers.jpg) 25 | 26 | The update rate with bicubic interpolation is just over 3Hz. 27 | 28 | # 1. Files 29 | 30 | * `amg88xx.py` The device driver. 31 | * `amg_test.py` Simple text based test program. 32 | 33 | For thermal camera use: 34 | * `cam.py` Thermal camera demo for Adafruit 0.96 inch OLED display. 35 | * `cam_lcd.py` Thermal camera demo for official LCD160CR display. 36 | * `mapper.py` Provides a class to convert temperature values to rgb colors. 37 | Required for both demos. 38 | * `ssd1331.py` Driver for 39 | [Adafruit 0.96 OLED display](https://www.adafruit.com/product/684). 40 | 41 | The Adafruit OLED display driver is from 42 | [this repo](https://github.com/peterhinch/micropython-nano-gui.git) 43 | which also has drivers for their larger displays based on the SSD1351 chip. 44 | The repo has optional drivers supporting 16 bit color: these trade improved 45 | color against a larger frame buffer, and so are best suited to platforms 46 | with plenty of RAM. 47 | 48 | The driver for the official LCD160CR is included in Pyboard firmware. Source is 49 | [here](https://github.com/micropython/micropython/blob/master/drivers/display/lcd160cr.py). 50 | 51 | The `my_cam` directory contains a complete set of files for my camera project. 52 | 53 | ## 1.1 Interpolation 54 | 55 | In camera applications the 8x8 matrix of the AMG8833 gives a "blocky" effect. 56 | This can be reduced by using bicubic interpolation. Files and 57 | [doc](./interpolate/README.md) for this are in the `interpolate` directory. 58 | 59 | # 2. Wiring 60 | 61 | If used with a Pyboard 62 | 63 | | pyboard | amg8833 | 64 | |:-------:|:-------:| 65 | | 3V3 | VIN | 66 | | GND | GND | 67 | | SCL X9 | SCL | 68 | | SDA X10 | SDA | 69 | 70 | # 3. AMG88XX class 71 | 72 | This maintains an internal `bytearray` object holding a single frame of raw 73 | data from the sensor. It is populated by the `refresh` method. The contents may 74 | be retrieved as integer temperatures in °C by means of array access syntax. 75 | 76 | Constructor: 77 | This takes the following arguments: 78 | * `i2c` An `I2C` instance created using the `machine` module. 79 | * `address=0x69` The default device address. If you solder the jumper on the 80 | back of the board labeled `Addr`, the address will change to 0x68. 81 | 82 | Data access methods: 83 | * `refresh` Takes an optional arg which is ignored. This method causes the 84 | internal array to be updated with data from the sensor. On a Pyboard 1.x this 85 | method blocks for 2.9ms. This method does not allocate RAM and may be called 86 | by an interrupt service routine. The dummy arg facilitiates use as a timer 87 | callback (see commented out code in `cam.py`). 88 | * `__getitem__` Args `row`, `col`. Enables access to the data retrieved by 89 | `refresh`. Return value is a signed integer representing the temperature of 90 | that pixel in °C (or °C x 4 in high resolution mode). 91 | * `temperature` No args. Returns the device temperature in °C as a float. 92 | 93 | Mode setting methods: 94 | * `hi_res=None` By default pixel temperatures are returned in °C. If `True` is 95 | passed, future readings will be in 0.25°C increments. This is the fundamental 96 | resolution of the chip, although its absolute accuracy is +-2.5°C. If `False` 97 | is passed, future readings will be in °C. By default no change is made. In all 98 | cases the method returns `True` if in high resolution mode. 99 | * `ma_mode=None` If `True` is passed the chip operates in moving average mode. 100 | This reduces noise at the expense of response speed. If `False` is passed, 101 | moving average mode is cancelled. By default no change is made. In all cases 102 | the current mode is returned. 103 | 104 | Example usage: 105 | After issuing the `refresh` method, a set of pixel data may be read by means of 106 | array access. 107 | 108 | ```python 109 | import machine 110 | import utime 111 | from amg88xx import AMG88XX 112 | 113 | i2c = machine.I2C(1) 114 | sensor = AMG88XX(i2c) 115 | while True: 116 | utime.sleep(0.2) 117 | sensor.refresh() 118 | for row in range(8): 119 | print() 120 | for col in range(8): 121 | print('{:4d}'.format(sensor[row, col]), end='') 122 | ``` 123 | 124 | # 4. Camera demo cam.py 125 | 126 | This assumes a Pyboard linked to an 127 | [Adafruit 0.96 OLED display](https://www.adafruit.com/product/684). Wiring is 128 | as follows: 129 | 130 | | pyboard | OLED | 131 | |:-------:|:----:| 132 | | 3V3 | VCC | 133 | | GND | GND | 134 | | X8 MOSI | SDA | 135 | | X6 SCL | SCL | 136 | | X3 | RES | 137 | | X2 | CS | 138 | | X1 | DC | 139 | 140 | The sensor is wired as in [section 2](./README.md#2-wiring). 141 | 142 | Orientation of the display may be adjusted to match the physical layout of the 143 | devices by setting the following program booleans: 144 | * `invert` Swap top and bottom. 145 | * `reflect` Swap left and right. 146 | * `transpose` Exchange row and column. 147 | 148 | The limits of temperature display may be adjusted by assigning integer values 149 | to these program values: 150 | * `TMAX` Temperatures >= `TMAX` appear red. 151 | * `TMIN` Temperatures <= `TMIN` appear blue. 152 | 153 | # 5 Camera demo cam_lcd.py 154 | 155 | As written this assumes a Pyboard 1.x with the LCD fitted in the 'Y' position 156 | and the sensor wired as in [section 2](./README.md#2-wiring). 157 | 158 | Orientation of the display may be adjusted to match the physical layout of the 159 | devices by setting the following program booleans: 160 | * `invert` Swap top and bottom. 161 | * `reflect` Swap left and right. 162 | * `transpose` Exchange row and column. 163 | 164 | The limits of temperature display may be adjusted by assigning integer values 165 | to these program values: 166 | * `TMAX` Temperatures >= `TMAX` appear red. 167 | * `TMIN` Temperatures <= `TMIN` appear blue. 168 | 169 | # 6. Mapper class for cameras 170 | 171 | This simple class converts a temperature in °C to 8 bit rgb color values 172 | compatible with most display drivers. Conversion uses a predefined mapping with 173 | blue for low temperatures through green to red, with constant overall 174 | brightness. Out of range temperature values are clipped to the maximum or 175 | minimum as required. The range covered may be dynamically altered. 176 | 177 | Constructor 178 | This takes the following args: 179 | * `tmin` Minimum temperature to represent (°C). 180 | * `tmax` Maximum temperature to represent (°C). 181 | * `ncolors=30` Number of color gradations. 182 | 183 | Methods: 184 | * `set_range(tmin, tmax)` Allows the temperature range to be altered 185 | dynamically. 186 | * `__call__(t)` Function call syntax takes a temperature in °C and returns 187 | `(r, g, b)`. Red, green and blue values are in range 0..255. 188 | -------------------------------------------------------------------------------- /amg88xx.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2017 Dean Miller for Adafruit Industries. 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 13 | # all 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 21 | # THE SOFTWARE. 22 | 23 | 24 | # Original author(s): Dean Miller, Scott Shawcroft for Adafruit Industries. 25 | # Date: June 2017 26 | # Affiliation: Adafruit Industries 27 | # Ported to MicroPython and extended by Peter Hinch 28 | # This port copyright (c) Peter Hinch 2019 29 | 30 | from micropython import const 31 | 32 | 33 | # Possible register values. 34 | 35 | # Operating Modes 36 | _NORMAL_MODE = const(0x00) 37 | _SLEEP_MODE = const(0x10) # error in original 38 | _STAND_BY_60 = const(0x20) 39 | _STAND_BY_10 = const(0x21) 40 | 41 | # sw resets 42 | _FLAG_RESET = const(0x30) 43 | _INITIAL_RESET = const(0x3F) 44 | 45 | # frame rates 46 | _FPS_10 = const(0x00) 47 | _FPS_1 = const(0x01) 48 | 49 | # int enables 50 | _INT_DISABLED = const(0x00) 51 | _INT_ENABLED = const(0x01) 52 | 53 | # int modes 54 | _DIFFERENCE = const(0x00) 55 | _ABSOLUTE_VALUE = const(0x01) 56 | 57 | _INT_OFFSET = const(0x010) 58 | _PIXEL_OFFSET = const(0x80) 59 | 60 | _PIXEL_ARRAY_WIDTH = const(8) 61 | _PIXEL_ARRAY_HEIGHT = const(8) 62 | _PIXEL_TEMP_CONVERSION = .25 63 | _THERMISTOR_CONVERSION = .0625 64 | 65 | # Registers 66 | _PCTL = const(0) 67 | _RST = const(1) 68 | _FPS = const(2) 69 | _INTEN = const(3) 70 | _TTHL = const(0x0e) 71 | _TTHH = const(0x0f) 72 | _MAMOD = const(0x07) 73 | _MAMOD1 = const(0x15) 74 | 75 | 76 | class AMG88XX: 77 | 78 | @staticmethod 79 | def _validrc(row, col): 80 | if min(row, col) >= 0 and row < _PIXEL_ARRAY_HEIGHT and col < _PIXEL_ARRAY_WIDTH: 81 | return 82 | raise ValueError('Invalid row {} or col {}'.format(row, col)) 83 | 84 | def __init__(self, i2c, addr=0x69): 85 | if addr not in i2c.scan(): 86 | raise RuntimeError('AMG8833 not found at address 0x{:02x}'.format(addr)) 87 | self._i2c = i2c 88 | self._address = addr 89 | self._scale = 2 90 | self._mamod = False 91 | # Pixel buffer 2 bytes/pixel (128 bytes) 92 | self._buf = bytearray(_PIXEL_ARRAY_HEIGHT * _PIXEL_ARRAY_WIDTH * 2) 93 | self._buf2 = bytearray(2) 94 | 95 | # enter normal mode 96 | self._write(_PCTL, _NORMAL_MODE) 97 | 98 | # software reset 99 | self._write(_RST, _INITIAL_RESET) 100 | 101 | # disable interrupts by default 102 | self._write(_INTEN, 0) 103 | 104 | # set to 10 FPS 105 | self._write(_FPS, _FPS_10) 106 | 107 | # read byte from register, return int 108 | def _read(self, memaddr, buf=bytearray(1)): # memaddr = memory location within the I2C device 109 | self._i2c.readfrom_mem_into(self._address, memaddr, buf) 110 | return buf[0] 111 | 112 | # write byte to register 113 | def _write(self, memaddr, data, buf=bytearray(1)): 114 | buf[0] = data 115 | self._i2c.writeto_mem(self._address, memaddr, buf) 116 | 117 | # read n bytes, return buffer 118 | def _readn(self, buf, memaddr): # memaddr = memory location within the I2C device 119 | self._i2c.readfrom_mem_into(self._address, memaddr, buf) 120 | return buf 121 | 122 | # Sensor temperature in Celcius 123 | def temperature(self): 124 | self._readn(self._buf2, _TTHL) 125 | v = ((self._buf2[1] << 8) | self._buf2[0]) & 0xfff 126 | if v & 0x800: 127 | v = -(v & 0x7ff) 128 | return float(v) * _THERMISTOR_CONVERSION 129 | 130 | # Set resolution: integer temps are shifted right 2 for °C 131 | def hi_res(self, v=None): 132 | if v is not None: 133 | self._scale = 0 if v else 2 134 | return self._scale == 0 135 | 136 | # Set or clear moving average mode 137 | def ma_mode(self, v=None): 138 | if v is not None: 139 | self._mamod = bool(v) # Exception if illegal type passed 140 | v = 0x20 if v else 0 141 | self._write(_MAMOD1, 0x50) 142 | self._write(_MAMOD1, 0x45) 143 | self._write(_MAMOD1, 0x57) 144 | self._write(_MAMOD, v) 145 | self._write(_MAMOD1, 0) 146 | return self._mamod 147 | 148 | # Pixel temperature as integer Celcius. Access as sensor_instance[row, col] 149 | def __getitem__(self, index): 150 | row, col = index 151 | self._validrc(row, col) 152 | buf_idx = (row * _PIXEL_ARRAY_HEIGHT + col) * 2 153 | raw = ((self._buf[buf_idx + 1] << 8) | self._buf[buf_idx]) & 0xfff 154 | if raw & 0x800: 155 | raw -= 0x1000 # Sign extend 156 | return raw >> self._scale # Pixel temp conversion == 0.25 157 | 158 | # Call before accessing a frame of data. Can be called in an ISR. 159 | # Blocks for 2.9ms on Pyboard 1.0 160 | def refresh(self, _=None): # Dummy arg for use in timer callback 161 | i2c = self._i2c 162 | memaddr = _PIXEL_OFFSET 163 | i2c.readfrom_mem_into(self._address, memaddr, self._buf) 164 | -------------------------------------------------------------------------------- /amg_test.py: -------------------------------------------------------------------------------- 1 | # amg_test.py Basic test of AMG8833 sensor 2 | 3 | # Released under the MIT licence. 4 | # Copyright (c) Peter Hinch 2019 5 | 6 | import machine 7 | import utime 8 | from amg88xx import AMG88XX 9 | 10 | 11 | i2c = machine.I2C(1) 12 | sensor = AMG88XX(i2c) 13 | while True: 14 | utime.sleep(0.2) 15 | sensor.refresh() 16 | for row in range(8): 17 | print() 18 | for col in range(8): 19 | print('{:4d}'.format(sensor[row, col]), end='') 20 | -------------------------------------------------------------------------------- /cam.py: -------------------------------------------------------------------------------- 1 | # cam.py Thermal camera based on Adafruit AMG8833 (Product ID 3538) sensor and 2 | # Adafruit 0.96 inch OLED display (Product ID 684) 3 | 4 | # Released under the MIT licence. 5 | # Copyright (c) Peter Hinch 2019 6 | 7 | import framebuf 8 | import machine 9 | import utime 10 | # 8-bit color driver for 0.96 inch OLED 11 | from ssd1331 import SSD1331 12 | # Optional 16-bit color driver 13 | # from ssd1331_16bit import SSD1331 14 | from mapper import Mapper # Maps temperature to rgb color 15 | from amg88xx import AMG88XX 16 | 17 | # For timer callback demo: 18 | # import pyb 19 | 20 | # Temperature range to cover 21 | TMAX = 30 22 | TMIN = 15 23 | 24 | # Instantiate color mapper 25 | mapper = Mapper(TMIN, TMAX) 26 | 27 | # Instantiate display 28 | pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) 29 | pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) 30 | prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) 31 | spi = machine.SPI(1) 32 | ssd = SSD1331(spi, pcs, pdc, prst) 33 | ssd.fill(0) 34 | ssd.show() 35 | 36 | # Instantiate temperature sensor 37 | i2c = machine.I2C(1) 38 | sensor = AMG88XX(i2c) 39 | sensor.ma_mode(True) # Moving average mode 40 | 41 | # Demo use in timer callback. No point in running faster than 10Hz as this is 42 | # the update rate of the chip. 43 | # tim = pyb.Timer(1) 44 | # tim.init(freq=10) 45 | # tim.callback(sensor.refresh) 46 | 47 | # Draw color scale at right of display 48 | col = 80 49 | val = TMIN 50 | dt = (TMAX - TMIN) / 32 51 | for row in range(63, -1, -2): 52 | ssd.fill_rect(col, row, 15, 2, ssd.rgb(*mapper(int(val)))) 53 | val += dt 54 | 55 | # Coordinate mapping from sensor to screen 56 | invert = True # For my breadboard layout 57 | reflect = True 58 | transpose = True 59 | print('Temperature {:5.1f}°C'.format(sensor.temperature())) 60 | 61 | # Run the camera 62 | while True: 63 | sensor.refresh() # Acquire data 64 | for row in range(8): 65 | for col in range(8): 66 | r = 7 - row if invert else row 67 | c = 7 - col if reflect else col 68 | if transpose: 69 | r, c = c, r 70 | val = sensor[r, c] 71 | ssd.fill_rect(col * 8, row * 8, 8, 8, ssd.rgb(*mapper(val))) 72 | ssd.show() 73 | utime.sleep(0.2) 74 | -------------------------------------------------------------------------------- /cam_lcd.py: -------------------------------------------------------------------------------- 1 | # cam_lcd.py Thermal camera based on Adafruit AMG8833 (Product ID 3538) sensor 2 | # and official LCD160CR LCD display. 3 | 4 | # Released under the MIT licence. 5 | # Copyright (c) Peter Hinch 2019 6 | 7 | import lcd160cr 8 | import machine 9 | import utime 10 | from mapper import Mapper # Maps temperature to rgb color 11 | from amg88xx import AMG88XX 12 | 13 | # Temperature range to cover 14 | TMAX = 30 15 | TMIN = 15 16 | 17 | # Instantiate color mapper 18 | mapper = Mapper(TMIN, TMAX) 19 | 20 | # Instantiate display 21 | lcd = lcd160cr.LCD160CR('Y') 22 | lcd.set_pen(0, 0) 23 | lcd.erase() 24 | 25 | # Instantiate temperature sensor 26 | i2c = machine.I2C(1) 27 | sensor = AMG88XX(i2c) 28 | sensor.ma_mode(True) # Moving average mode 29 | 30 | # Draw color scale at right of display 31 | col = 80 32 | val = TMIN 33 | dt = (TMAX - TMIN) / 32 34 | for row in range(63, -1, -2): 35 | color = lcd.rgb(*mapper(val)) 36 | lcd.set_pen(color, color) 37 | lcd.rect(col, row, 15, 2) 38 | val += dt 39 | 40 | # Coordinate mapping from sensor to screen 41 | invert = True # For my breadboard layout 42 | reflect = True 43 | transpose = True 44 | print('Temperature {:5.1f}°C'.format(sensor.temperature())) 45 | 46 | # Run the camera 47 | white = lcd.rgb(255, 255, 255) 48 | black = lcd.rgb(0, 0, 0) 49 | lcd.set_font(3) 50 | lcd.set_text_color(white, black) 51 | while True: 52 | sensor.refresh() # Acquire data 53 | max_t = -1000 54 | min_t = 1000 55 | sum_t = 0 56 | for row in range(8): 57 | for col in range(8): 58 | r = 7 - row if invert else row 59 | c = 7 - col if reflect else col 60 | if transpose: 61 | r, c = c, r 62 | val = sensor[r, c] 63 | max_t = max(max_t, val) 64 | min_t = min(min_t, val) 65 | sum_t += val 66 | color = lcd.rgb(*mapper(val)) 67 | lcd.set_pen(color, color) 68 | lcd.rect(col * 8, row * 8, 8, 8) 69 | lcd.set_pos(0, 70) 70 | lcd.write('Temperatures') 71 | lcd.set_pos(0, 85) 72 | lcd.write('Max:{:4d}C'.format(max_t)) 73 | lcd.set_pos(0, 100) 74 | lcd.write('Min:{:4d}C'.format(min_t)) 75 | lcd.set_pos(0, 115) 76 | lcd.write('Avg:{:4d}C'.format(round(sum_t / 64))) 77 | utime.sleep(0.2) 78 | -------------------------------------------------------------------------------- /images/build.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-amg88xx/d7442f233c57f7a964de1e1926b7af39c614e7df/images/build.jpg -------------------------------------------------------------------------------- /images/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-amg88xx/d7442f233c57f7a964de1e1926b7af39c614e7df/images/cat.jpg -------------------------------------------------------------------------------- /images/chair.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-amg88xx/d7442f233c57f7a964de1e1926b7af39c614e7df/images/chair.jpg -------------------------------------------------------------------------------- /images/coffee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-amg88xx/d7442f233c57f7a964de1e1926b7af39c614e7df/images/coffee.jpg -------------------------------------------------------------------------------- /images/fingers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-amg88xx/d7442f233c57f7a964de1e1926b7af39c614e7df/images/fingers.jpg -------------------------------------------------------------------------------- /interpolate/README.md: -------------------------------------------------------------------------------- 1 | # AMG8833 Interpolating Camera 2 | 3 | The 8x8 resolution of the AMG8833 produces a "blocky" display. The illusion of 4 | higher resolution may be achieved using bicubic interpolation. This is 5 | computationally intensive. On a Pyboard 1.1 the frame rate for 32x32 pixels 6 | using Python code is ~1Hz. This can be increased to ~2.5Hz using assembler. 7 | 8 | ##### [Main README](../README.md) 9 | 10 | # 1. Files 11 | 12 | * `cam_interp.py` 32x32 pixel demo using the Adafruit 0.96 inch OLED. 13 | * `interpolate.py` Portable interpolator using optimised Python code. 14 | * `interpolate_a.py` Version using Arm Thumb2 Assembler. 15 | 16 | Both versions of the interpolator provide implementations of the `Interpolator` 17 | class. 18 | 19 | # 2. Interpolator class 20 | 21 | Constructor: 22 | This takes a single arg, an `AMG88XX` instance. 23 | 24 | Methods: 25 | * `refresh` No args. Causes the `AMG88XX` instance and the interpolator to 26 | update with physical data. 27 | * `__call__` args `r, c`. The interpolator's coordinate space covers the range 28 | 0.0 <= r <= 1.0, 0.0 <= c <= 1.0. Function call syntax causes the interpolator 29 | to return the temperature value for that row, col location. 30 | 31 | # Usage 32 | 33 | Converting a working 8x8 camera application to use interpolation is simple. The 34 | aim is to replace the 8x8 array of pixels with a larger array of smaller 35 | pixels: the `cam_interp.py` demo uses 32x32 squares of size 2x2. 36 | 37 | After instantiating the sensor, create an `Interpolator`: 38 | ```python 39 | i2c = machine.I2C(1) 40 | sensor = AMG88XX(i2c) 41 | sensor.ma_mode(True) # Moving average mode 42 | interpolator = Interpolator(sensor) 43 | ``` 44 | Replace 45 | ```python 46 | sensor.refresh() # Acquire data 47 | ``` 48 | with 49 | ```python 50 | interpolator.refresh() 51 | ``` 52 | The iterator now needs to iterate through the number of virtual rows and cols 53 | and display smaller squares. Instead of accessing the sensor, the value is 54 | retrieved with 55 | ```python 56 | val = interpolator(r/max_row, c/max_col) # Values range 0.0..1.0 57 | ``` 58 | 59 | # Algorithm 60 | 61 | The theory of cubic and bicubic interpolation is straightforward. 62 | [This ref](https://www.paulinternet.nl/?page=bicubic) provides a good 63 | explanation, with Java and C++ implementations. 64 | -------------------------------------------------------------------------------- /interpolate/cam_interp.py: -------------------------------------------------------------------------------- 1 | # cam_interp.py Thermal camera based on Adafruit AMG8833 (Product ID 3538) sensor and 2 | # Adafruit 0.96 inch OLED display (Product ID 684) 3 | 4 | # Released under the MIT licence. 5 | # Copyright (c) Peter Hinch 2019 6 | 7 | import framebuf 8 | import machine 9 | from ssd1331 import SSD1331 # Driver for 0.96 inch OLED 10 | from mapper import Mapper # Maps temperature to rgb color 11 | from amg88xx import AMG88XX 12 | # from interpolate import Interpolator # Portable version 13 | from interpolate_a import Interpolator # STM assembler version 14 | 15 | # Temperature range to cover 16 | TMAX = 30 17 | TMIN = 15 18 | 19 | # Instantiate color mapper 20 | mapper = Mapper(TMIN, TMAX) 21 | 22 | # Instantiate display 23 | pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) 24 | pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) 25 | prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) 26 | spi = machine.SPI(1) 27 | ssd = SSD1331(spi, pcs, pdc, prst) 28 | ssd.fill(0) 29 | ssd.show() 30 | 31 | # Instantiate temperature sensor 32 | i2c = machine.I2C(1) 33 | sensor = AMG88XX(i2c) 34 | sensor.ma_mode(True) # Moving average mode 35 | interpolator = Interpolator(sensor) 36 | 37 | # Draw color scale at right of display 38 | col = 80 39 | val = TMIN 40 | dt = (TMAX - TMIN) / 32 41 | for row in range(63, -1, -2): 42 | ssd.fill_rect(col, row, 15, 2, ssd.rgb(*mapper(int(val)))) 43 | val += dt 44 | 45 | # Coordinate mapping from sensor to screen 46 | invert = True # For my breadboard layout 47 | reflect = True 48 | transpose = True 49 | print('Temperature {:5.1f}°C'.format(sensor.temperature())) 50 | 51 | # Run the camera 52 | while True: 53 | interpolator.refresh() # Acquire data from sensor via interpolator. 54 | for row in range(32): 55 | for col in range(32): 56 | r = 31 - row if invert else row 57 | c = 31 - col if reflect else col 58 | if transpose: 59 | r, c = c, r 60 | # For interpolator 0.0 <= row <= 1.0, 0.0 <= col <= 1.0 61 | val = interpolator(r/31, c/31) 62 | ssd.fill_rect(col * 2, row * 2, 2, 2, ssd.rgb(*mapper(val))) 63 | ssd.show() 64 | -------------------------------------------------------------------------------- /interpolate/interpolate.py: -------------------------------------------------------------------------------- 1 | # interpolate.py Bicubic interpolator for AMG8833 thermal IR sensor 2 | 3 | # Released under the MIT licence. 4 | # Copyright (c) Peter Hinch 2019 5 | 6 | # Algorithm derivation https://www.paulinternet.nl/?page=bicubic 7 | 8 | from array import array 9 | import math 10 | 11 | # Sensor is 8*8. 12 | _PIXEL_ARRAY_WIDTH = const(8) 13 | _PIXEL_ARRAY_HEIGHT = const(8) 14 | # Extrapolate 1 pixel at each edge so that the 1st derivative can be estimated. 15 | _WIDTH = const(10) 16 | _HEIGHT = const(10) 17 | 18 | # Cubic interpolation of a 4 element one dimensional array of samples p. 19 | # Interpolation is between samples p[1] and p[2]: samples p[0] and p[3] provide 20 | # a first derivative estimate at points p[1] and p[2]. 21 | # 0 <= x < 1.0 is the offset between p[1] and p[2] 22 | @micropython.viper 23 | def interp_arr(p, x): 24 | return p[1] + 0.5 * x*(p[2] - p[0] + x*(2.0*p[0] - 5.0*p[1] + 4.0*p[2] - p[3] + x*(3.0*(p[1] - p[2]) + p[3] - p[0]))) 25 | 26 | # Return index into data array from row, col 27 | _idx = lambda r, c : r * _WIDTH + c 28 | 29 | @micropython.native 30 | def bicubic(line, offs, y, x, rd = array('f', (0 for _ in range(4)))): 31 | rd[0] = interp_arr(line[offs:], x) # Get value of location x of row 0 32 | offs += _WIDTH # Increment one row 33 | rd[1] = interp_arr(line[offs:], x) 34 | offs += _WIDTH 35 | rd[2] = interp_arr(line[offs:], x) 36 | offs += _WIDTH 37 | rd[3] = interp_arr(line[offs:], x) 38 | return interp_arr(rd, y) # interpolate the column of data 39 | 40 | class Interpolator: 41 | def __init__(self, sensor): 42 | self._sensor = sensor 43 | self._data = array('f', (0 for _ in range(_HEIGHT * _WIDTH))) 44 | self._mvd = memoryview(self._data) 45 | 46 | def refresh(self, _=None): 47 | s = self._sensor 48 | s.refresh() 49 | # Populate sensor data 50 | for row in range(_PIXEL_ARRAY_HEIGHT): 51 | for col in range(_PIXEL_ARRAY_WIDTH): 52 | self[row + 1, col + 1] = s[row, col] 53 | # Extrapolate edges 54 | # Populate corners 55 | self[0, 0] = 2 * self[1, 1] - self[2, 2] 56 | self[0, _WIDTH -1] = 2 * self[1, _WIDTH -2] - self[2, _WIDTH -3] 57 | self[_HEIGHT -1, 0] = 2 * self[_HEIGHT -2, 1] - self[_HEIGHT -3, 2] 58 | self[_HEIGHT -1, _WIDTH -1] = 2 * self[_HEIGHT -2, _WIDTH -2] - self[_HEIGHT -3, _WIDTH -3] 59 | # Populate edges 60 | col = _WIDTH -1 61 | for row in range(1, _HEIGHT -1): 62 | self[row, 0] = 2 * self[row, 1] - self[row, 2] 63 | self[row, col] = 2 * self[row, col -1] - self[row, col -2] 64 | row = _HEIGHT -1 65 | for col in range(1, _WIDTH -1): 66 | self[0, col] = 2 * self[1, col] - self[2, col] 67 | self[row, col] = 2 * self[row -1, col] - self[row -2, col] 68 | 69 | def __getitem__(self, index): 70 | return self._data[_idx(*index)] 71 | 72 | def __setitem__(self, index, v): 73 | self._data[_idx(*index)] = v 74 | 75 | # Access interpolated data by row, col: bounding box 0.0,0.0 -> 1.0,1.0 76 | def __call__(self, r, c): 77 | if r < 0.0 or r > 1.0 or c < 0.0 or c > 1.0: 78 | r = max(min(r, 1.0), 0.0) 79 | c = max(min(c, 1.0), 0.0) 80 | y, row = math.modf(r * 6.99) 81 | x, col = math.modf(c * 6.99) 82 | return bicubic(self._mvd, int(row * _WIDTH + col), y, x) 83 | 84 | -------------------------------------------------------------------------------- /interpolate/interpolate_a.py: -------------------------------------------------------------------------------- 1 | # interpolate_a.py Bicubic interpolator for AMG8833 thermal IR sensor 2 | # version for STM using Arm Thumb2 Assembler 3 | 4 | # Released under the MIT licence. 5 | # Copyright (c) Peter Hinch 2019 6 | 7 | # Algorithm derivation https://www.paulinternet.nl/?page=bicubic 8 | 9 | from array import array 10 | import math 11 | 12 | # Sensor is 8*8. 13 | _PIXEL_ARRAY_WIDTH = const(8) 14 | _PIXEL_ARRAY_HEIGHT = const(8) 15 | # Extrapolate 1 pixel at each edge so that the 1st derivative can be estimated. 16 | _WIDTH = const(10) 17 | _HEIGHT = const(10) 18 | 19 | # Cubic interpolation of a 4 element one dimensional array of samples p. 20 | # Interpolation is between samples p[1] and p[2]: samples p[0] and p[3] provide 21 | # a first derivative estimate at points p[1] and p[2]. 22 | # 0 <= x < 1.0 is the offset between p[1] and p[2] 23 | # p[1] + 0.5 * x*(p[2] - p[0] + x*(2.0*p[0] - 5.0*p[1] + 4.0*p[2] - p[3] + x*(3.0*(p[1] - p[2]) + p[3] - p[0]))) 24 | 25 | # r0: Array of 4 samples 26 | # r1: Array of _coefficients. r1[0] = x, r1[1:] = 0.5, 2.0, 3.0, 4.0, 5.0 27 | # r2: r2[0] will hold result 28 | @micropython.asm_thumb 29 | def interp_arr(r0, r1, r2): 30 | vldr(s0, [r1, 4]) # s0 = 0.5 31 | vldr(s1, [r1, 8]) # s1 = 2.0 32 | vldr(s2, [r1, 12]) # s2 = 3.0 33 | vldr(s3, [r1, 16]) # s3 = 4.0 34 | vldr(s4, [r1, 20]) # s4 = 5.0 35 | vldr(s5, [r0, 0]) # s5 = p[0] 36 | vldr(s6, [r0, 4]) # s6 = p[1] 37 | vldr(s7, [r0, 8]) # s7 = p[2] 38 | vldr(s8, [r0, 12]) # s8 = p[3] 39 | vldr(s9, [r1, 0]) # s9 = x 40 | vsub(s10, s6, s7) # s10 = p[1] -p[2] 41 | vmul(s10, s10, s2) # s10 = 3.0*(p[1] - p[2]) 42 | vadd(s10, s10, s8) # s10 += p[3] 43 | vsub(s10, s10, s5) # s10 -= p[0] 44 | vmul(s10, s10, s9) # s10 *= x 45 | vsub(s10, s10, s8) # s10 -= p[3] 46 | vmul(s11, s3, s7) # s11 = 4.0*p[2] 47 | vadd(s10, s10, s11) # s10 += 4.0*p[2] 48 | vmul(s11, s4, s6) # s11 = 5.0*p[1] 49 | vsub(s10, s10, s11) # s10 -= 5.0*p[1] 50 | vmul(s11, s1, s5) # s11 = 2.0*p[0] 51 | vadd(s10, s10, s11) # s10 += 2.0*p[0] 52 | vmul(s10, s10, s9) # s10 *= x 53 | vsub(s10, s10, s5) # s10 -= p[0] 54 | vadd(s10, s10, s7) # s10 += p[2] 55 | vmul(s10, s10, s9) # s10 *= x 56 | vmul(s10, s10, s0) # s10 *= 0.5 57 | vadd(s10, s10, s6) # s10 += p[1] 58 | vstr(s10, [r2, 0]) 59 | 60 | # Return index into data array from row, col 61 | _idx = lambda r, c : r * _WIDTH + c 62 | 63 | class Interpolator: 64 | def __init__(self, sensor): 65 | self._sensor = sensor 66 | self._data = array('f', (0 for _ in range(_HEIGHT * _WIDTH))) 67 | self._coeffs = array('f', (0.0, 0.5, 2.0, 3.0, 4.0, 5.0)) 68 | self._mvd = memoryview(self._data) 69 | self._rd = array('f', (0 for _ in range(4))) 70 | self._mvrd = memoryview(self._rd) 71 | 72 | def refresh(self, _=None): 73 | s = self._sensor 74 | s.refresh() 75 | # Populate sensor data 76 | for row in range(_PIXEL_ARRAY_HEIGHT): 77 | for col in range(_PIXEL_ARRAY_WIDTH): 78 | self[row + 1, col + 1] = s[row, col] 79 | # Extrapolate edges 80 | # Populate corners 81 | self[0, 0] = 2 * self[1, 1] - self[2, 2] 82 | self[0, _WIDTH -1] = 2 * self[1, _WIDTH -2] - self[2, _WIDTH -3] 83 | self[_HEIGHT -1, 0] = 2 * self[_HEIGHT -2, 1] - self[_HEIGHT -3, 2] 84 | self[_HEIGHT -1, _WIDTH -1] = 2 * self[_HEIGHT -2, _WIDTH -2] - self[_HEIGHT -3, _WIDTH -3] 85 | # Populate edges 86 | col = _WIDTH -1 87 | for row in range(1, _HEIGHT -1): 88 | self[row, 0] = 2 * self[row, 1] - self[row, 2] 89 | self[row, col] = 2 * self[row, col -1] - self[row, col -2] 90 | row = _HEIGHT -1 91 | for col in range(1, _WIDTH -1): 92 | self[0, col] = 2 * self[1, col] - self[2, col] 93 | self[row, col] = 2 * self[row -1, col] - self[row -2, col] 94 | 95 | def __getitem__(self, index): 96 | return self._data[_idx(*index)] 97 | 98 | def __setitem__(self, index, v): 99 | self._data[_idx(*index)] = v 100 | 101 | @micropython.native 102 | def bicubic(self, offs, y, x): 103 | c = self._coeffs 104 | rd = self._mvrd 105 | line = self._mvd # line[offs] points to sample set 106 | c[0] = x 107 | interp_arr(line[offs:], c, rd) # Get value of location x of row 0 108 | offs += _WIDTH # Increment one row 109 | interp_arr(line[offs:], c, rd[1:]) 110 | offs += _WIDTH 111 | interp_arr(line[offs:], c, rd[2:]) 112 | offs += _WIDTH 113 | interp_arr(line[offs:], c, rd[3:]) 114 | c[0] = y 115 | interp_arr(rd, c, c) # interpolate the column of data 116 | return c[0] 117 | 118 | # Access interpolated data by row, col: bounding box 0.0,0.0 -> 1.0,1.0 119 | def __call__(self, r, c): 120 | if r < 0.0 or r > 1.0 or c < 0.0 or c > 1.0: 121 | r = max(min(r, 1.0), 0.0) 122 | c = max(min(c, 1.0), 0.0) 123 | y, row = math.modf(r * 6.99) 124 | x, col = math.modf(c * 6.99) 125 | return self.bicubic(int(row * _WIDTH + col), y, x) 126 | -------------------------------------------------------------------------------- /mapper.py: -------------------------------------------------------------------------------- 1 | # mapper.py Color mapper for thermal IR cameras 2 | 3 | # Released under the MIT licence. 4 | # Copyright (c) Peter Hinch 2019 5 | 6 | # Mapper class. Converts a temperature value to r, g, b colors. Colors are in 7 | # range 0..255. Temperature range may be specified. 8 | class Mapper: 9 | 10 | def __init__(self, tmin, tmax, ncolors=30): 11 | self._ncolors = ncolors 12 | N = ncolors 13 | self._b = bytearray(max(int(255*(1 - 2*x/N)), 0) for x in range(N + 1)) 14 | self._g = bytearray(int(255*2*x/N) if x < N/2 else int(255*2*(1 - x/N)) for x in range(N + 1)) 15 | self._r = bytearray(max(int(255*(2*x/N - 1)), 0) for x in range(N + 1)) 16 | self.set_range(tmin, tmax) 17 | 18 | def set_range(self, tmin, tmax): 19 | if tmax <= tmin: 20 | raise ValueError('Invalid temperature range.') 21 | self._tmin = tmin 22 | self._tmax = tmax 23 | self._factor = self._ncolors/(tmax - tmin) 24 | 25 | def __call__(self, t): # Celcius to color value 26 | # Constrain 27 | t = max(min(t, self._tmax), self._tmin) 28 | # Ensure +ve, inclusive range 0..tmax-tmin 29 | t -= self._tmin 30 | t = round(t * self._factor) # 0..ncolors 31 | return self._r[t], self._g[t], self._b[t] 32 | -------------------------------------------------------------------------------- /my_cam/README.md: -------------------------------------------------------------------------------- 1 | This is a record of the files used to build my PIR camera. 2 | 3 | Usage: 4 | The topmost switch controls maximum temperature: push left to increase, push 5 | right to decrease. A long press initiates auto-ranging, a short press clears 6 | it. 7 | 8 | The bottom switch controls minimum temperature. A long press causes a display 9 | hold, a short press clears it. 10 | 11 | The .fzz file defines the PCB. The fixing holes do not align perfectly with 12 | those on the OLED display: some fettling is required. 13 | 14 | The color scale is not ideal at the blue end of the spectrum. This is because 15 | the rrrgggbb mapping is only capable of displaying 4 levels of blue. This gives 16 | some unexpected color shades as blue ramps down and green ramps up. 17 | -------------------------------------------------------------------------------- /my_cam/amg88xx.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2017 Dean Miller for Adafruit Industries. 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 13 | # all 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 21 | # THE SOFTWARE. 22 | 23 | 24 | # Original author(s): Dean Miller, Scott Shawcroft for Adafruit Industries. 25 | # Date: June 2017 26 | # Affiliation: Adafruit Industries 27 | # Ported to MicroPython and extended by Peter Hinch 28 | # This port copyright (c) Peter Hinch 2019 29 | 30 | from micropython import const 31 | 32 | 33 | # Possible register values. 34 | 35 | # Operating Modes 36 | _NORMAL_MODE = const(0x00) 37 | _SLEEP_MODE = const(0x10) # error in original 38 | _STAND_BY_60 = const(0x20) 39 | _STAND_BY_10 = const(0x21) 40 | 41 | # sw resets 42 | _FLAG_RESET = const(0x30) 43 | _INITIAL_RESET = const(0x3F) 44 | 45 | # frame rates 46 | _FPS_10 = const(0x00) 47 | _FPS_1 = const(0x01) 48 | 49 | # int enables 50 | _INT_DISABLED = const(0x00) 51 | _INT_ENABLED = const(0x01) 52 | 53 | # int modes 54 | _DIFFERENCE = const(0x00) 55 | _ABSOLUTE_VALUE = const(0x01) 56 | 57 | _INT_OFFSET = const(0x010) 58 | _PIXEL_OFFSET = const(0x80) 59 | 60 | _PIXEL_ARRAY_WIDTH = const(8) 61 | _PIXEL_ARRAY_HEIGHT = const(8) 62 | _PIXEL_TEMP_CONVERSION = .25 63 | _THERMISTOR_CONVERSION = .0625 64 | 65 | # Registers 66 | _PCTL = const(0) 67 | _RST = const(1) 68 | _FPS = const(2) 69 | _INTEN = const(3) 70 | _TTHL = const(0x0e) 71 | _TTHH = const(0x0f) 72 | _MAMOD = const(0x07) 73 | _MAMOD1 = const(0x15) 74 | 75 | 76 | class AMG88XX: 77 | 78 | @staticmethod 79 | def _validrc(row, col): 80 | if min(row, col) >= 0 and row < _PIXEL_ARRAY_HEIGHT and col < _PIXEL_ARRAY_WIDTH: 81 | return 82 | raise ValueError('Invalid row {} or col {}'.format(row, col)) 83 | 84 | def __init__(self, i2c, addr=0x69): 85 | if addr not in i2c.scan(): 86 | raise RuntimeError('AMG8833 not found at address 0x{:02x}'.format(addr)) 87 | self._i2c = i2c 88 | self._address = addr 89 | self._scale = 2 90 | self._mamod = False 91 | # Pixel buffer 2 bytes/pixel (128 bytes) 92 | self._buf = bytearray(_PIXEL_ARRAY_HEIGHT * _PIXEL_ARRAY_WIDTH * 2) 93 | self._buf2 = bytearray(2) 94 | 95 | # enter normal mode 96 | self._write(_PCTL, _NORMAL_MODE) 97 | 98 | # software reset 99 | self._write(_RST, _INITIAL_RESET) 100 | 101 | # disable interrupts by default 102 | self._write(_INTEN, 0) 103 | 104 | # set to 10 FPS 105 | self._write(_FPS, _FPS_1) 106 | 107 | # read byte from register, return int 108 | def _read(self, memaddr, buf=bytearray(1)): # memaddr = memory location within the I2C device 109 | self._i2c.readfrom_mem_into(self._address, memaddr, buf) 110 | return buf[0] 111 | 112 | # write byte to register 113 | def _write(self, memaddr, data, buf=bytearray(1)): 114 | buf[0] = data 115 | self._i2c.writeto_mem(self._address, memaddr, buf) 116 | 117 | # read n bytes, return buffer 118 | def _readn(self, buf, memaddr): # memaddr = memory location within the I2C device 119 | self._i2c.readfrom_mem_into(self._address, memaddr, buf) 120 | return buf 121 | 122 | # Sensor temperature in Celcius 123 | def temperature(self): 124 | self._readn(self._buf2, _TTHL) 125 | v = ((self._buf2[1] << 8) | self._buf2[0]) & 0xfff 126 | if v & 0x800: 127 | v = -(v & 0x7ff) 128 | return float(v) * _THERMISTOR_CONVERSION 129 | 130 | # Set resolution: integer temps are shifted right 2 for °C 131 | def hi_res(self, v=None): 132 | if v is not None: 133 | self._scale = 0 if v else 2 134 | return self._scale == 0 135 | 136 | # Set or clear moving average mode 137 | def ma_mode(self, v=None): 138 | if v is not None: 139 | self._mamod = bool(v) # Exception if illegal type passed 140 | v = 0x20 if v else 0 141 | self._write(_MAMOD1, 0x50) 142 | self._write(_MAMOD1, 0x45) 143 | self._write(_MAMOD1, 0x57) 144 | self._write(_MAMOD, v) 145 | self._write(_MAMOD1, 0) 146 | return self._mamod 147 | 148 | # Pixel temperature as integer Celcius. Access as sensor_instance[row, col] 149 | def __getitem__(self, index): 150 | row, col = index 151 | self._validrc(row, col) 152 | buf_idx = (row * _PIXEL_ARRAY_HEIGHT + col) * 2 153 | raw = ((self._buf[buf_idx + 1] << 8) | self._buf[buf_idx]) & 0xfff 154 | if raw & 0x800: 155 | raw -= 0x1000 # Sign extend 156 | return raw >> self._scale # Pixel temp conversion == 0.25 157 | 158 | # Call before accessing a frame of data. Can be called in an ISR. 159 | # Blocks for 2.9ms on Pyboard 1.0 160 | def refresh(self, _=None): # Dummy arg for use in timer callback 161 | i2c = self._i2c 162 | memaddr = _PIXEL_OFFSET 163 | i2c.readfrom_mem_into(self._address, memaddr, self._buf) 164 | -------------------------------------------------------------------------------- /my_cam/arial10.py: -------------------------------------------------------------------------------- 1 | # Code generated by font-to-py.py. 2 | # Font: Arial.ttf 3 | version = '0.25' 4 | 5 | def height(): 6 | return 10 7 | 8 | def max_width(): 9 | return 11 10 | 11 | def hmap(): 12 | return True 13 | 14 | def reverse(): 15 | return False 16 | 17 | def monospaced(): 18 | return False 19 | 20 | def min_ch(): 21 | return 32 22 | 23 | def max_ch(): 24 | return 126 25 | 26 | _font =\ 27 | b'\x06\x00\x70\x88\x08\x10\x20\x20\x00\x20\x00\x00\x03\x00\x00\x00'\ 28 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\x80\x80\x80\x80\x80'\ 29 | b'\x00\x80\x00\x00\x04\x00\xa0\xa0\xa0\x00\x00\x00\x00\x00\x00\x00'\ 30 | b'\x06\x00\x28\x28\xf8\x50\x50\xf8\xa0\xa0\x00\x00\x06\x00\x70\xa8'\ 31 | b'\xa0\x70\x28\x28\xa8\x70\x20\x00\x0a\x00\x62\x00\x94\x00\x94\x00'\ 32 | b'\x68\x00\x0b\x00\x14\x80\x14\x80\x23\x00\x00\x00\x00\x00\x07\x00'\ 33 | b'\x30\x48\x48\x30\x50\x8c\x88\x74\x00\x00\x02\x00\x80\x80\x80\x00'\ 34 | b'\x00\x00\x00\x00\x00\x00\x04\x00\x20\x40\x80\x80\x80\x80\x80\x80'\ 35 | b'\x40\x20\x04\x00\x80\x40\x20\x20\x20\x20\x20\x20\x40\x80\x04\x00'\ 36 | b'\x40\xe0\x40\xa0\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x20\x20'\ 37 | b'\xf8\x20\x20\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x80'\ 38 | b'\x80\x80\x04\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x03\x00'\ 39 | b'\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x03\x00\x20\x20\x40\x40'\ 40 | b'\x40\x40\x80\x80\x00\x00\x06\x00\x70\x88\x88\x88\x88\x88\x88\x70'\ 41 | b'\x00\x00\x06\x00\x20\x60\xa0\x20\x20\x20\x20\x20\x00\x00\x06\x00'\ 42 | b'\x70\x88\x08\x08\x10\x20\x40\xf8\x00\x00\x06\x00\x70\x88\x08\x30'\ 43 | b'\x08\x08\x88\x70\x00\x00\x06\x00\x10\x30\x50\x50\x90\xf8\x10\x10'\ 44 | b'\x00\x00\x06\x00\x78\x40\x80\xf0\x08\x08\x88\x70\x00\x00\x06\x00'\ 45 | b'\x70\x88\x80\xf0\x88\x88\x88\x70\x00\x00\x06\x00\xf8\x10\x10\x20'\ 46 | b'\x20\x40\x40\x40\x00\x00\x06\x00\x70\x88\x88\x70\x88\x88\x88\x70'\ 47 | b'\x00\x00\x06\x00\x70\x88\x88\x88\x78\x08\x88\x70\x00\x00\x03\x00'\ 48 | b'\x00\x00\x80\x00\x00\x00\x00\x80\x00\x00\x03\x00\x00\x00\x80\x00'\ 49 | b'\x00\x00\x00\x80\x80\x80\x06\x00\x00\x00\x08\x70\x80\x70\x08\x00'\ 50 | b'\x00\x00\x06\x00\x00\x00\x00\xf8\x00\xf8\x00\x00\x00\x00\x06\x00'\ 51 | b'\x00\x00\x80\x70\x08\x70\x80\x00\x00\x00\x06\x00\x70\x88\x08\x10'\ 52 | b'\x20\x20\x00\x20\x00\x00\x0b\x00\x1f\x00\x60\x80\x4d\x40\x93\x40'\ 53 | b'\xa2\x40\xa2\x40\xa6\x80\x9b\x00\x40\x40\x3f\x80\x08\x00\x10\x28'\ 54 | b'\x28\x28\x44\x7c\x82\x82\x00\x00\x07\x00\xf8\x84\x84\xfc\x84\x84'\ 55 | b'\x84\xf8\x00\x00\x07\x00\x38\x44\x80\x80\x80\x80\x44\x38\x00\x00'\ 56 | b'\x07\x00\xf0\x88\x84\x84\x84\x84\x88\xf0\x00\x00\x06\x00\xf8\x80'\ 57 | b'\x80\xf8\x80\x80\x80\xf8\x00\x00\x06\x00\xf8\x80\x80\xf0\x80\x80'\ 58 | b'\x80\x80\x00\x00\x08\x00\x38\x44\x82\x80\x8e\x82\x44\x38\x00\x00'\ 59 | b'\x07\x00\x84\x84\x84\xfc\x84\x84\x84\x84\x00\x00\x02\x00\x80\x80'\ 60 | b'\x80\x80\x80\x80\x80\x80\x00\x00\x05\x00\x10\x10\x10\x10\x10\x90'\ 61 | b'\x90\x60\x00\x00\x07\x00\x84\x88\x90\xb0\xd0\x88\x88\x84\x00\x00'\ 62 | b'\x06\x00\x80\x80\x80\x80\x80\x80\x80\xf8\x00\x00\x08\x00\x82\xc6'\ 63 | b'\xc6\xaa\xaa\xaa\x92\x92\x00\x00\x07\x00\x84\xc4\xa4\xa4\x94\x94'\ 64 | b'\x8c\x84\x00\x00\x08\x00\x38\x44\x82\x82\x82\x82\x44\x38\x00\x00'\ 65 | b'\x06\x00\xf0\x88\x88\x88\xf0\x80\x80\x80\x00\x00\x08\x00\x38\x44'\ 66 | b'\x82\x82\x82\x9a\x44\x3e\x00\x00\x07\x00\xf8\x84\x84\xf8\x90\x88'\ 67 | b'\x88\x84\x00\x00\x07\x00\x78\x84\x80\x60\x18\x04\x84\x78\x00\x00'\ 68 | b'\x06\x00\xf8\x20\x20\x20\x20\x20\x20\x20\x00\x00\x07\x00\x84\x84'\ 69 | b'\x84\x84\x84\x84\x84\x78\x00\x00\x08\x00\x82\x82\x44\x44\x28\x28'\ 70 | b'\x10\x10\x00\x00\x0b\x00\x84\x20\x8a\x20\x4a\x40\x4a\x40\x51\x40'\ 71 | b'\x51\x40\x20\x80\x20\x80\x00\x00\x00\x00\x07\x00\x84\x48\x48\x30'\ 72 | b'\x30\x48\x48\x84\x00\x00\x08\x00\x82\x44\x44\x28\x10\x10\x10\x10'\ 73 | b'\x00\x00\x07\x00\x7c\x08\x10\x10\x20\x20\x40\xfc\x00\x00\x03\x00'\ 74 | b'\xc0\x80\x80\x80\x80\x80\x80\x80\x80\xc0\x03\x00\x80\x80\x40\x40'\ 75 | b'\x40\x40\x20\x20\x00\x00\x03\x00\xc0\x40\x40\x40\x40\x40\x40\x40'\ 76 | b'\x40\xc0\x05\x00\x20\x50\x50\x88\x00\x00\x00\x00\x00\x00\x06\x00'\ 77 | b'\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x00\x04\x00\x80\x40\x00\x00'\ 78 | b'\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x70\x88\x78\x88\x98\xe8'\ 79 | b'\x00\x00\x06\x00\x80\x80\xb0\xc8\x88\x88\xc8\xb0\x00\x00\x06\x00'\ 80 | b'\x00\x00\x70\x88\x80\x80\x88\x70\x00\x00\x06\x00\x08\x08\x68\x98'\ 81 | b'\x88\x88\x98\x68\x00\x00\x06\x00\x00\x00\x70\x88\xf8\x80\x88\x70'\ 82 | b'\x00\x00\x04\x00\x20\x40\xe0\x40\x40\x40\x40\x40\x00\x00\x06\x00'\ 83 | b'\x00\x00\x68\x98\x88\x88\x98\x68\x08\xf0\x06\x00\x80\x80\xb0\xc8'\ 84 | b'\x88\x88\x88\x88\x00\x00\x02\x00\x80\x00\x80\x80\x80\x80\x80\x80'\ 85 | b'\x00\x00\x02\x00\x40\x00\x40\x40\x40\x40\x40\x40\x40\x80\x05\x00'\ 86 | b'\x80\x80\x90\xa0\xc0\xe0\xa0\x90\x00\x00\x02\x00\x80\x80\x80\x80'\ 87 | b'\x80\x80\x80\x80\x00\x00\x08\x00\x00\x00\xbc\xd2\x92\x92\x92\x92'\ 88 | b'\x00\x00\x06\x00\x00\x00\xf0\x88\x88\x88\x88\x88\x00\x00\x06\x00'\ 89 | b'\x00\x00\x70\x88\x88\x88\x88\x70\x00\x00\x06\x00\x00\x00\xb0\xc8'\ 90 | b'\x88\x88\xc8\xb0\x80\x80\x06\x00\x00\x00\x68\x98\x88\x88\x98\x68'\ 91 | b'\x08\x08\x04\x00\x00\x00\xa0\xc0\x80\x80\x80\x80\x00\x00\x06\x00'\ 92 | b'\x00\x00\x70\x88\x60\x10\x88\x70\x00\x00\x03\x00\x40\x40\xe0\x40'\ 93 | b'\x40\x40\x40\x60\x00\x00\x06\x00\x00\x00\x88\x88\x88\x88\x98\x68'\ 94 | b'\x00\x00\x06\x00\x00\x00\x88\x88\x50\x50\x20\x20\x00\x00\x0a\x00'\ 95 | b'\x00\x00\x00\x00\x88\x80\x94\x80\x55\x00\x55\x00\x22\x00\x22\x00'\ 96 | b'\x00\x00\x00\x00\x06\x00\x00\x00\x88\x50\x20\x20\x50\x88\x00\x00'\ 97 | b'\x06\x00\x00\x00\x88\x88\x50\x50\x20\x20\x20\x40\x06\x00\x00\x00'\ 98 | b'\xf8\x10\x20\x20\x40\xf8\x00\x00\x04\x00\x20\x40\x40\x40\x80\x40'\ 99 | b'\x40\x40\x40\x20\x02\x00\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80'\ 100 | b'\x04\x00\x80\x40\x40\x40\x20\x40\x40\x40\x40\x80\x06\x00\x00\x00'\ 101 | b'\x00\xe8\xb0\x00\x00\x00\x00\x00' 102 | 103 | _index =\ 104 | b'\x00\x00\x0c\x00\x0c\x00\x18\x00\x18\x00\x24\x00\x24\x00\x30\x00'\ 105 | b'\x30\x00\x3c\x00\x3c\x00\x48\x00\x48\x00\x5e\x00\x5e\x00\x6a\x00'\ 106 | b'\x6a\x00\x76\x00\x76\x00\x82\x00\x82\x00\x8e\x00\x8e\x00\x9a\x00'\ 107 | b'\x9a\x00\xa6\x00\xa6\x00\xb2\x00\xb2\x00\xbe\x00\xbe\x00\xca\x00'\ 108 | b'\xca\x00\xd6\x00\xd6\x00\xe2\x00\xe2\x00\xee\x00\xee\x00\xfa\x00'\ 109 | b'\xfa\x00\x06\x01\x06\x01\x12\x01\x12\x01\x1e\x01\x1e\x01\x2a\x01'\ 110 | b'\x2a\x01\x36\x01\x36\x01\x42\x01\x42\x01\x4e\x01\x4e\x01\x5a\x01'\ 111 | b'\x5a\x01\x66\x01\x66\x01\x72\x01\x72\x01\x7e\x01\x7e\x01\x8a\x01'\ 112 | b'\x8a\x01\x96\x01\x96\x01\xac\x01\xac\x01\xb8\x01\xb8\x01\xc4\x01'\ 113 | b'\xc4\x01\xd0\x01\xd0\x01\xdc\x01\xdc\x01\xe8\x01\xe8\x01\xf4\x01'\ 114 | b'\xf4\x01\x00\x02\x00\x02\x0c\x02\x0c\x02\x18\x02\x18\x02\x24\x02'\ 115 | b'\x24\x02\x30\x02\x30\x02\x3c\x02\x3c\x02\x48\x02\x48\x02\x54\x02'\ 116 | b'\x54\x02\x60\x02\x60\x02\x6c\x02\x6c\x02\x78\x02\x78\x02\x84\x02'\ 117 | b'\x84\x02\x90\x02\x90\x02\x9c\x02\x9c\x02\xa8\x02\xa8\x02\xb4\x02'\ 118 | b'\xb4\x02\xca\x02\xca\x02\xd6\x02\xd6\x02\xe2\x02\xe2\x02\xee\x02'\ 119 | b'\xee\x02\xfa\x02\xfa\x02\x06\x03\x06\x03\x12\x03\x12\x03\x1e\x03'\ 120 | b'\x1e\x03\x2a\x03\x2a\x03\x36\x03\x36\x03\x42\x03\x42\x03\x4e\x03'\ 121 | b'\x4e\x03\x5a\x03\x5a\x03\x66\x03\x66\x03\x72\x03\x72\x03\x7e\x03'\ 122 | b'\x7e\x03\x8a\x03\x8a\x03\x96\x03\x96\x03\xa2\x03\xa2\x03\xae\x03'\ 123 | b'\xae\x03\xba\x03\xba\x03\xc6\x03\xc6\x03\xd2\x03\xd2\x03\xde\x03'\ 124 | b'\xde\x03\xea\x03\xea\x03\xf6\x03\xf6\x03\x02\x04\x02\x04\x0e\x04'\ 125 | b'\x0e\x04\x1a\x04\x1a\x04\x26\x04\x26\x04\x32\x04\x32\x04\x3e\x04'\ 126 | b'\x3e\x04\x54\x04\x54\x04\x60\x04\x60\x04\x6c\x04\x6c\x04\x78\x04'\ 127 | b'\x78\x04\x84\x04\x84\x04\x90\x04\x90\x04\x9c\x04\x9c\x04\xa8\x04'\ 128 | 129 | _mvfont = memoryview(_font) 130 | 131 | def get_ch(ch): 132 | ordch = ord(ch) 133 | ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 134 | idx_offs = 4 * (ordch - 32) 135 | offset = int.from_bytes(_index[idx_offs : idx_offs + 2], 'little') 136 | next_offs = int.from_bytes(_index[idx_offs + 2 : idx_offs + 4], 'little') 137 | width = int.from_bytes(_font[offset:offset + 2], 'little') 138 | return _mvfont[offset + 2:next_offs], 10, width 139 | 140 | -------------------------------------------------------------------------------- /my_cam/aswitch.py: -------------------------------------------------------------------------------- 1 | # aswitch.py Switch and pushbutton classes for asyncio 2 | # Delay_ms A retriggerable delay class. Can schedule a coro on timeout. 3 | # Switch Simple debounced switch class for normally open grounded switch. 4 | # Pushbutton extend the above to support logical state, long press and 5 | # double-click events 6 | # Tested on Pyboard but should run on other microcontroller platforms 7 | # running MicroPython and uasyncio. 8 | 9 | # The MIT License (MIT) 10 | # 11 | # Copyright (c) 2017 Peter Hinch 12 | # 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | # 20 | # The above copyright notice and this permission notice shall be included in 21 | # all copies or substantial portions of the Software. 22 | # 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | # THE SOFTWARE. 30 | 31 | import uasyncio as asyncio 32 | import utime as time 33 | # Remove dependency on asyn to save RAM: 34 | # launch: run a callback or initiate a coroutine depending on which is passed. 35 | async def _g(): 36 | pass 37 | type_coro = type(_g()) 38 | 39 | # If a callback is passed, run it and return. 40 | # If a coro is passed initiate it and return. 41 | # coros are passed by name i.e. not using function call syntax. 42 | def launch(func, tup_args): 43 | res = func(*tup_args) 44 | if isinstance(res, type_coro): 45 | loop = asyncio.get_event_loop() 46 | loop.create_task(res) 47 | 48 | 49 | class Delay_ms(object): 50 | def __init__(self, func=None, args=(), can_alloc=True, duration=1000): 51 | self.func = func 52 | self.args = args 53 | self.can_alloc = can_alloc 54 | self.duration = duration # Default duration 55 | self.tstop = None # Not running 56 | self.loop = asyncio.get_event_loop() 57 | if not can_alloc: 58 | self.loop.create_task(self._run()) 59 | 60 | async def _run(self): 61 | while True: 62 | if self.tstop is None: # Not running 63 | await asyncio.sleep_ms(0) 64 | else: 65 | await self.killer() 66 | 67 | def stop(self): 68 | self.tstop = None 69 | 70 | def trigger(self, duration=0): # Update end time 71 | if duration <= 0: 72 | duration = self.duration 73 | if self.can_alloc and self.tstop is None: # No killer task is running 74 | self.tstop = time.ticks_add(time.ticks_ms(), duration) 75 | # Start a task which stops the delay after its period has elapsed 76 | self.loop.create_task(self.killer()) 77 | self.tstop = time.ticks_add(time.ticks_ms(), duration) 78 | 79 | def running(self): 80 | return self.tstop is not None 81 | 82 | __call__ = running 83 | 84 | async def killer(self): 85 | twait = time.ticks_diff(self.tstop, time.ticks_ms()) 86 | while twait > 0: # Must loop here: might be retriggered 87 | await asyncio.sleep_ms(twait) 88 | if self.tstop is None: 89 | break # Return if stop() called during wait 90 | twait = time.ticks_diff(self.tstop, time.ticks_ms()) 91 | if self.tstop is not None and self.func is not None: 92 | launch(self.func, self.args) # Timed out: execute callback 93 | self.tstop = None # Not running 94 | 95 | class Switch(object): 96 | debounce_ms = 50 97 | def __init__(self, pin): 98 | self.pin = pin # Should be initialised for input with pullup 99 | self._open_func = False 100 | self._close_func = False 101 | self.switchstate = self.pin.value() # Get initial state 102 | loop = asyncio.get_event_loop() 103 | loop.create_task(self.switchcheck()) # Thread runs forever 104 | 105 | def open_func(self, func, args=()): 106 | self._open_func = func 107 | self._open_args = args 108 | 109 | def close_func(self, func, args=()): 110 | self._close_func = func 111 | self._close_args = args 112 | 113 | # Return current state of switch (0 = pressed) 114 | def __call__(self): 115 | return self.switchstate 116 | 117 | async def switchcheck(self): 118 | while True: 119 | state = self.pin.value() 120 | if state != self.switchstate: 121 | # State has changed: act on it now. 122 | self.switchstate = state 123 | if state == 0 and self._close_func: 124 | launch(self._close_func, self._close_args) 125 | elif state == 1 and self._open_func: 126 | launch(self._open_func, self._open_args) 127 | # Ignore further state changes until switch has settled 128 | await asyncio.sleep_ms(Switch.debounce_ms) 129 | 130 | class Pushbutton(object): 131 | debounce_ms = 50 132 | long_press_ms = 1000 133 | double_click_ms = 400 134 | def __init__(self, pin, suppress=False): 135 | self.pin = pin # Initialise for input 136 | self._supp = suppress 137 | self._dblpend = False # Doubleclick waiting for 2nd click 138 | self._dblran = False # Doubleclick executed user function 139 | self._tf = False 140 | self._ff = False 141 | self._df = False 142 | self._lf = False 143 | self._ld = False # Delay_ms instance for long press 144 | self._dd = False # Ditto for doubleclick 145 | self.sense = pin.value() # Convert from electrical to logical value 146 | self.state = self.rawstate() # Initial state 147 | loop = asyncio.get_event_loop() 148 | loop.create_task(self.buttoncheck()) # Thread runs forever 149 | 150 | def press_func(self, func, args=()): 151 | self._tf = func 152 | self._ta = args 153 | 154 | def release_func(self, func, args=()): 155 | self._ff = func 156 | self._fa = args 157 | 158 | def double_func(self, func, args=()): 159 | self._df = func 160 | self._da = args 161 | 162 | def long_func(self, func, args=()): 163 | self._lf = func 164 | self._la = args 165 | 166 | # Current non-debounced logical button state: True == pressed 167 | def rawstate(self): 168 | return bool(self.pin.value() ^ self.sense) 169 | 170 | # Current debounced state of button (True == pressed) 171 | def __call__(self): 172 | return self.state 173 | 174 | def _ddto(self): # Doubleclick timeout: no doubleclick occurred 175 | self._dblpend = False 176 | if self._supp and not self.state: 177 | if not self._ld or (self._ld and not self._ld()): 178 | launch(self._ff, self._fa) 179 | 180 | async def buttoncheck(self): 181 | if self._lf: # Instantiate timers if funcs exist 182 | self._ld = Delay_ms(self._lf, self._la) 183 | if self._df: 184 | self._dd = Delay_ms(self._ddto) 185 | while True: 186 | state = self.rawstate() 187 | # State has changed: act on it now. 188 | if state != self.state: 189 | self.state = state 190 | if state: # Button pressed: launch pressed func 191 | if self._tf: 192 | launch(self._tf, self._ta) 193 | if self._lf: # There's a long func: start long press delay 194 | self._ld.trigger(Pushbutton.long_press_ms) 195 | if self._df: 196 | if self._dd(): # Second click: timer running 197 | self._dd.stop() 198 | self._dblpend = False 199 | self._dblran = True # Prevent suppressed launch on release 200 | launch(self._df, self._da) 201 | else: 202 | # First click: start doubleclick timer 203 | self._dd.trigger(Pushbutton.double_click_ms) 204 | self._dblpend = True # Prevent suppressed launch on release 205 | else: # Button release. Is there a release func? 206 | if self._ff: 207 | if self._supp: 208 | d = self._ld 209 | # If long delay exists, is running and doubleclick status is OK 210 | if not self._dblpend and not self._dblran: 211 | if (d and d()) or not d: 212 | launch(self._ff, self._fa) 213 | else: 214 | launch(self._ff, self._fa) 215 | if self._ld: 216 | self._ld.stop() # Avoid interpreting a second click as a long push 217 | self._dblran = False 218 | # Ignore state changes until switch has settled 219 | await asyncio.sleep_ms(Pushbutton.debounce_ms) 220 | -------------------------------------------------------------------------------- /my_cam/cam.py: -------------------------------------------------------------------------------- 1 | # cam.py Thermal camera based on Adafruit AMG8833 (Product ID 3538) sensor 2 | # and Adafruit OLED 128*128 display. 3 | 4 | # Released under the MIT licence. 5 | # Copyright (c) Peter Hinch 2019 6 | 7 | from machine import Pin, SPI, I2C, freq 8 | from utime import ticks_ms, ticks_diff 9 | import uasyncio as asyncio 10 | from micropython import const, mem_info 11 | import gc 12 | 13 | from primitives.switch import Switch 14 | from primitives.delay_ms import Delay_ms 15 | from ssd1351_16bit import SSD1351 as SSD # STM Asm version 16 | from writer import CWriter 17 | import courier17 as font # Main text 18 | import arial10 # Small text 19 | from mapper import Mapper # Maps temperature to rgb color 20 | from amg88xx import AMG88XX 21 | from interpolate_a import Interpolator # STM assembler version 22 | 23 | freq(216_000_000) # In old version improved update rate 750ms -> 488ms 24 | 25 | eliza = lambda *_ : None 26 | 27 | # Possible modes. Note there is no point in having a _HOLD mode as the text 28 | # fields are not updated so it would never show. 29 | _NORM = const(0) 30 | _AUTO = const(1) 31 | _HOG = const(2) 32 | 33 | class Cam: 34 | 35 | def __init__(self, txt_rf_ms, verbose): 36 | self.txt_rf_ms = txt_rf_ms 37 | self.verbose = verbose 38 | self.tmax = 30 # Initial temperature range 39 | self.tmin = 15 40 | self.mode = _NORM 41 | # Enable initial update 42 | self.rf_disp = True 43 | self.rf_txt = True 44 | 45 | # Instantiate color mapper 46 | self.mapper = Mapper(self.tmin, self.tmax) 47 | 48 | # Instantiate switches 49 | self.timer = Delay_ms(duration=2000) # Long press delay 50 | # Release arg rarg enables calling switch to be identified. 51 | for item in (('X4', self.chmax, 5, self.ar, 0), ('Y1', self.chmax, -5, self.ar, 1), 52 | ('X5', self.chmin, 5, eliza, 2), ('X6', self.chmin, -5, eliza, 3)): 53 | sw, func, arg, long_func, rarg = item 54 | cs = Switch(Pin(sw, Pin.IN, Pin.PULL_UP)) 55 | cs.close_func(self.press, (func, arg)) 56 | cs.open_func(self.release, (long_func, rarg)) 57 | 58 | # Instantiate display 59 | pdc = Pin('X1', Pin.OUT_PP, value=0) 60 | pcs = Pin('X2', Pin.OUT_PP, value=1) 61 | prst = Pin('X3', Pin.OUT_PP, value=1) 62 | # In practice baudrate made no difference to update rate which is 63 | # dominated by interpolation time 64 | spi = SPI(2, baudrate=13_500_000) 65 | verbose and print('SPI:', spi) 66 | ssd = SSD(spi, pcs, pdc, prst) # Create a display instance 67 | ssd.fill(0) 68 | ssd.show() 69 | 70 | self.avg = 0.0 71 | # Instantiate PIR temperature sensor 72 | i2c = I2C(2) 73 | pir = AMG88XX(i2c) 74 | pir.ma_mode(True) # Moving average mode 75 | 76 | # Run the camera 77 | asyncio.create_task(self.run(pir, ssd)) 78 | 79 | # A switch was pressed. Change temperature range. 80 | def press(self, func, arg): 81 | self.timer.trigger() 82 | self.mode = _NORM 83 | if self.mode == _AUTO: # Short press clears auto range, leaves range unchanged 84 | self.rf_disp = True 85 | self.rf_txt = True 86 | else: 87 | self.rf_disp = False # Disable display updates in case it's a long press 88 | func(arg) # Change range 89 | 90 | # Change .tmax 91 | def chmax(self, val): 92 | if self.tmax + val > self.tmin: # val can be -ve 93 | self.tmax += val 94 | 95 | # Change .tmin 96 | def chmin(self, val): 97 | if self.tmin + val < self.tmax: 98 | self.tmin += val 99 | 100 | def release(self, func, arg): 101 | if self.timer.running(): # Brief press: re-enable display 102 | self.rf_txt = True # Show changed range 103 | self.rf_disp = True 104 | else: 105 | func(arg) # eliza will leave it with rf_disp False 106 | 107 | def ar(self, sw): # Long press callback, top switch 108 | if sw: # Animal detection mode 109 | self.mode = _HOG 110 | self.tmin = self.avg 111 | self.tmax = self.avg + 5 112 | else: # Auto range 113 | self.mode = _AUTO 114 | self.rf_disp = True 115 | self.rf_txt = True # Show changed range 116 | 117 | # Draw color scale at right of display 118 | def draw_scale(self, ssd): 119 | col = 75 120 | val = self.tmax 121 | dt = (self.tmax - self.tmin) / 31 122 | for row in range(32): 123 | ssd.rect(col, row * 2, 15, 2, ssd.rgb(*self.mapper(val))) 124 | val -= dt 125 | 126 | # Refreshing text is slow so do it periodically to maximise mean image framerate 127 | async def refresh_txt(self): 128 | while True: 129 | await asyncio.sleep_ms(self.txt_rf_ms) 130 | self.rf_txt = True 131 | 132 | # Run the camera 133 | async def run(self, pir, ssd): 134 | # Define colors 135 | white = ssd.rgb(255, 255, 255) 136 | black = ssd.rgb(0, 0, 0) 137 | red = ssd.rgb(255, 0, 0) 138 | blue = ssd.rgb(0, 0, 255) 139 | yellow = ssd.rgb(255, 255, 0) 140 | green = ssd.rgb(0, 255, 0) 141 | 142 | # Instantiate CWriters 143 | wri_l = CWriter(ssd, font, green, black, self.verbose) # Large font. 144 | wri_s = CWriter(ssd, arial10, white, black, self.verbose) # Small text 145 | 146 | # Instantiate interpolator and draw the scale 147 | interp = Interpolator(pir) 148 | self.draw_scale(ssd) 149 | 150 | while True: 151 | t = ticks_ms() # For verbose timing 152 | self.mapper.set_range(self.tmin, self.tmax) 153 | interp.refresh() # Acquire data 154 | max_t = -1000 155 | min_t = 1000 156 | sum_t = 0 157 | for row in range(32): 158 | for col in range(32): 159 | # Transpose, reflect and invert 160 | val = interp((31 - col)/31, row/31) 161 | max_t = max(max_t, val) 162 | min_t = min(min_t, val) 163 | sum_t += val 164 | ssd.rect(col * 2, row * 2, 2, 2, ssd.rgb(*self.mapper(val))) 165 | await asyncio.sleep(0) 166 | self.avg = round(sum_t / 1024) 167 | if self.mode == _AUTO: 168 | self.tmin = round(min_t) 169 | self.tmax = round(max_t) 170 | if self.rf_disp: 171 | if self.rf_txt: 172 | wri_l.set_textpos(ssd, 66, 0) 173 | wri_l.printstring('Max:{:+4d}C\n'.format(int(max_t))) 174 | wri_l.printstring('Min:{:+4d}C\n'.format(int(min_t))) 175 | wri_l.printstring('Avg:{:+4d}C'.format(self.avg)) 176 | wri_s.set_textpos(ssd, 128 - arial10.height(), 64) 177 | wri_s.setcolor(yellow, black) 178 | wri_s.printstring('Chip:{:5.1f}C'.format(pir.temperature())) 179 | wri_s.set_textpos(ssd, 0, 90) 180 | wri_s.setcolor(red, black) 181 | wri_s.printstring('{:4d}C '.format(self.tmax)) 182 | wri_s.set_textpos(ssd, 28, 95) 183 | wri_s.setcolor(green, black) 184 | if self.mode == _HOG: 185 | wri_s.printstring('Hog ') 186 | elif self.mode == _NORM: 187 | wri_s.printstring('Norm ') 188 | else: 189 | wri_s.printstring('Auto ') 190 | wri_s.set_textpos(ssd, 64 - arial10.height(), 90) 191 | wri_s.setcolor(blue, black) 192 | wri_s.printstring('{:4d}C '.format(self.tmin)) 193 | self.rf_txt = False 194 | ssd.show() 195 | self.verbose and print(ticks_diff(ticks_ms(), t)) 196 | gc.collect() 197 | # self.verbose and mem_info() 198 | 199 | # stack: 1276 out of 15360 200 | # GC: total: 196672, used: 52128, free: 144544 201 | # No. of 1-blocks: 365, 2-blocks: 106, max blk sz: 1024, max free sz: 2545 202 | 203 | cam = Cam(2000, False) # Refresh text every 2000ms. Verbose? 204 | asyncio.run(cam.refresh_txt()) 205 | -------------------------------------------------------------------------------- /my_cam/courier17.py: -------------------------------------------------------------------------------- 1 | # Code generated by font-to-py.py. 2 | # Font: Courier Prime.ttf 3 | version = '0.26' 4 | 5 | def height(): 6 | return 17 7 | 8 | def max_width(): 9 | return 12 10 | 11 | def hmap(): 12 | return True 13 | 14 | def reverse(): 15 | return False 16 | 17 | def monospaced(): 18 | return False 19 | 20 | def min_ch(): 21 | return 32 22 | 23 | def max_ch(): 24 | return 126 25 | 26 | _font =\ 27 | b'\x0b\x00\x00\x00\x00\x00\x78\x00\x84\x00\x84\x00\x04\x00\x18\x00'\ 28 | b'\x20\x00\x20\x00\x00\x00\x70\x00\x70\x00\x70\x00\x00\x00\x00\x00'\ 29 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 30 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 31 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x60\x00'\ 32 | b'\x60\x00\x60\x00\x40\x00\x40\x00\x40\x00\x40\x00\x00\x00\xe0\x00'\ 33 | b'\xe0\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 34 | b'\x00\x00\xdc\x00\xd8\x00\xc8\x00\xc8\x00\xc8\x00\x00\x00\x00\x00'\ 35 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 36 | b'\x0b\x00\x00\x00\x00\x00\x04\xc0\x04\x80\x0c\x80\x7f\xe0\x09\x00'\ 37 | b'\x19\x00\x13\x00\xff\xc0\x12\x00\x26\x00\x24\x00\x00\x00\x00\x00'\ 38 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x10\x00\x10\x00\x7e\x00\x96\x00'\ 39 | b'\x92\x00\xf8\x00\x7e\x00\x1f\x00\x91\x00\xd1\x00\xfe\x00\x10\x00'\ 40 | b'\x10\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x60\x00'\ 41 | b'\x90\x80\x91\x80\x93\x00\x66\x00\x0c\x00\x1b\x80\x34\x40\x44\x40'\ 42 | b'\x84\x40\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 43 | b'\x00\x00\x3c\x00\x42\x00\x42\x00\x40\x00\x60\x00\x71\x80\xfb\x00'\ 44 | b'\x9e\x00\x8e\x00\x8f\x00\x79\x80\x00\x00\x00\x00\x00\x00\x00\x00'\ 45 | b'\x0b\x00\x00\x00\x00\x00\x60\x00\x60\x00\x60\x00\x40\x00\x40\x00'\ 46 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 47 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x18\x00\x30\x00\x60\x00\x40\x00'\ 48 | b'\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\xc0\x00'\ 49 | b'\x40\x00\x60\x00\x30\x00\x18\x00\x0b\x00\x00\x00\xc0\x00\x60\x00'\ 50 | b'\x30\x00\x10\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00'\ 51 | b'\x08\x00\x18\x00\x10\x00\x30\x00\x60\x00\xc0\x00\x0b\x00\x00\x00'\ 52 | b'\x00\x00\x10\x00\x18\x00\x92\x00\xff\x00\x18\x00\x2c\x00\x64\x00'\ 53 | b'\x44\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 54 | b'\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x10\x00\x10\x00'\ 55 | b'\xfe\x00\x10\x00\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 56 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 57 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x00\x70\x00\x60\x00'\ 58 | b'\x60\x00\x40\x00\xc0\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 59 | b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00'\ 60 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 61 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 62 | b'\x00\x00\xe0\x00\xe0\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 63 | b'\x0b\x00\x00\x00\x03\x00\x02\x00\x06\x00\x04\x00\x0c\x00\x08\x00'\ 64 | b'\x18\x00\x10\x00\x10\x00\x30\x00\x20\x00\x60\x00\x40\x00\xc0\x00'\ 65 | b'\x80\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x38\x00\x44\x00\x82\x00'\ 66 | b'\x82\x00\x82\x00\x82\x00\x82\x00\x82\x00\x82\x00\x44\x00\x38\x00'\ 67 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x70\x00'\ 68 | b'\xd0\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00'\ 69 | b'\x10\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 70 | b'\x00\x00\x78\x00\x86\x00\x82\x00\x02\x00\x02\x00\x04\x00\x08\x00'\ 71 | b'\x10\x00\x22\x00\x42\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 72 | b'\x0b\x00\x00\x00\x00\x00\x78\x00\x86\x00\x82\x00\x02\x00\x06\x00'\ 73 | b'\x1c\x00\x02\x00\x02\x00\x02\x00\x86\x00\x7c\x00\x00\x00\x00\x00'\ 74 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x04\x00\x0c\x00\x1c\x00'\ 75 | b'\x34\x00\x34\x00\x64\x00\xc4\x00\xff\x00\x04\x00\x04\x00\x1f\x00'\ 76 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xfc\x00'\ 77 | b'\x80\x00\x80\x00\x80\x00\xf8\x00\xc4\x00\x02\x00\x02\x00\x02\x00'\ 78 | b'\xc2\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 79 | b'\x00\x00\x1e\x00\x30\x00\x40\x00\x80\x00\xb8\x00\xc4\x00\x82\x00'\ 80 | b'\x82\x00\x82\x00\x44\x00\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 81 | b'\x0b\x00\x00\x00\x00\x00\xfe\x00\x86\x00\x84\x00\x0c\x00\x0c\x00'\ 82 | b'\x08\x00\x18\x00\x10\x00\x30\x00\x20\x00\x20\x00\x00\x00\x00\x00'\ 83 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x38\x00\xc6\x00\x82\x00'\ 84 | b'\x82\x00\x44\x00\x38\x00\xc6\x00\x82\x00\x82\x00\x86\x00\x78\x00'\ 85 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x38\x00'\ 86 | b'\x44\x00\x82\x00\x82\x00\x82\x00\x46\x00\x3a\x00\x02\x00\x04\x00'\ 87 | b'\x18\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 88 | b'\x00\x00\x00\x00\x00\x00\xe0\x00\xe0\x00\xe0\x00\x00\x00\x00\x00'\ 89 | b'\x00\x00\xe0\x00\xe0\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 90 | b'\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x00\x70\x00\x70\x00'\ 91 | b'\x00\x00\x00\x00\x00\x00\x70\x00\x70\x00\x60\x00\x60\x00\x40\x00'\ 92 | b'\xc0\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x80\x03\x80'\ 93 | b'\x0e\x00\x30\x00\xc0\x00\x70\x00\x1c\x00\x07\x00\x01\x80\x00\x00'\ 94 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 95 | b'\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00'\ 96 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 97 | b'\x00\x00\x00\x00\x80\x00\xe0\x00\x38\x00\x0e\x00\x03\x00\x0e\x00'\ 98 | b'\x38\x00\xe0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 99 | b'\x0b\x00\x00\x00\x00\x00\x78\x00\x84\x00\x84\x00\x04\x00\x18\x00'\ 100 | b'\x20\x00\x20\x00\x00\x00\x70\x00\x70\x00\x70\x00\x00\x00\x00\x00'\ 101 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x30\xc0'\ 102 | b'\x40\x40\x46\xa0\x8d\xa0\x99\x20\x91\x20\x91\x60\xcf\xc0\x60\x00'\ 103 | b'\x3e\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x3e\x00'\ 104 | b'\x0a\x00\x0a\x00\x1b\x00\x19\x00\x11\x00\x31\x80\x3f\x80\x20\x80'\ 105 | b'\x40\x40\xf1\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 106 | b'\x00\x00\xfe\x00\x21\x00\x21\x00\x21\x00\x21\x00\x3e\x00\x21\x00'\ 107 | b'\x21\x00\x21\x00\x23\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 108 | b'\x0b\x00\x00\x00\x00\x00\x1f\x80\x61\x80\x40\x80\x80\x80\x80\x00'\ 109 | b'\x80\x00\x80\x00\x80\x00\x80\x00\x41\x80\x3f\x00\x00\x00\x00\x00'\ 110 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xfe\x00\x21\x00\x21\x00'\ 111 | b'\x20\x80\x20\x80\x20\x80\x20\x80\x20\x80\x21\x00\x21\x00\xfe\x00'\ 112 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xff\xc0'\ 113 | b'\x20\x40\x20\x40\x20\x00\x22\x00\x3e\x00\x22\x00\x20\x00\x20\x40'\ 114 | b'\x20\x40\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 115 | b'\x00\x00\xff\x80\x20\x80\x20\x80\x20\x00\x22\x00\x3e\x00\x22\x00'\ 116 | b'\x20\x00\x20\x00\x20\x00\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 117 | b'\x0b\x00\x00\x00\x00\x00\x1e\x80\x61\x80\x40\x80\x80\x00\x80\x00'\ 118 | b'\x80\x00\x87\xc0\x80\x80\x40\x80\x60\x80\x1f\x00\x00\x00\x00\x00'\ 119 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xe3\x80\x41\x00\x41\x00'\ 120 | b'\x41\x00\x41\x00\x7f\x00\x41\x00\x41\x00\x41\x00\x41\x00\xe3\x80'\ 121 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x7f\x00'\ 122 | b'\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00'\ 123 | b'\x08\x00\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 124 | b'\x00\x00\x3f\xc0\x02\x00\x02\x00\x02\x00\x02\x00\x02\x00\x82\x00'\ 125 | b'\x82\x00\x82\x00\x86\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 126 | b'\x0b\x00\x00\x00\x00\x00\xfb\xc0\x21\x00\x22\x00\x24\x00\x28\x00'\ 127 | b'\x3e\x00\x36\x00\x23\x00\x21\x00\x21\x80\xf8\xc0\x00\x00\x00\x00'\ 128 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xfc\x00\x20\x00\x20\x00'\ 129 | b'\x20\x00\x20\x00\x20\x00\x20\x80\x20\x80\x20\x80\x20\x80\xff\x80'\ 130 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xe0\xe0'\ 131 | b'\x71\xc0\x71\xc0\x5b\x40\x5b\x40\x4a\x40\x4e\x40\x44\x40\x40\x40'\ 132 | b'\x40\x40\xf1\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 133 | b'\x00\x00\xe3\xc0\x70\x80\x78\x80\x58\x80\x5c\x80\x4c\x80\x46\x80'\ 134 | b'\x47\x80\x43\x80\x41\x80\xf1\x80\x00\x00\x00\x00\x00\x00\x00\x00'\ 135 | b'\x0b\x00\x00\x00\x00\x00\x3e\x00\x41\x00\x41\x00\x80\x80\x80\x80'\ 136 | b'\x80\x80\x80\x80\x80\x80\x41\x00\x41\x00\x3e\x00\x00\x00\x00\x00'\ 137 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xfe\x00\x21\x00\x20\x80'\ 138 | b'\x20\x80\x20\x80\x21\x00\x3e\x00\x20\x00\x20\x00\x20\x00\xfc\x00'\ 139 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x3e\x00'\ 140 | b'\x41\x00\x81\x00\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x41\x00'\ 141 | b'\x63\x00\x3e\x00\x30\x80\x7f\x80\x00\x00\x00\x00\x0b\x00\x00\x00'\ 142 | b'\x00\x00\xfe\x00\x21\x00\x21\x00\x21\x00\x21\x00\x3e\x00\x26\x00'\ 143 | b'\x22\x00\x23\x00\x21\x00\xf8\xc0\x00\x00\x00\x00\x00\x00\x00\x00'\ 144 | b'\x0b\x00\x00\x00\x00\x00\x3f\x00\x43\x00\x41\x00\x40\x00\x7c\x00'\ 145 | b'\x1f\x00\x01\x80\x00\x80\xc0\x80\xe1\x00\xfe\x00\x00\x00\x00\x00'\ 146 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xff\x80\x88\x80\x88\x80'\ 147 | b'\x88\x80\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x7f\x00'\ 148 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\xf9\xf0'\ 149 | b'\x20\x40\x20\x40\x20\x40\x20\x40\x20\x40\x20\x40\x20\x40\x20\x40'\ 150 | b'\x10\x80\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 151 | b'\x00\x00\xf9\xe0\x60\x80\x20\x80\x31\x80\x31\x00\x11\x00\x1b\x00'\ 152 | b'\x1a\x00\x0a\x00\x0e\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 153 | b'\x0b\x00\x00\x00\x00\x00\xf1\xe0\x40\x40\x60\xc0\x64\xc0\x66\xc0'\ 154 | b'\x2a\x80\x2a\x80\x2a\x80\x31\x80\x31\x80\x31\x80\x00\x00\x00\x00'\ 155 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xf1\xc0\x21\x00\x33\x00'\ 156 | b'\x12\x00\x0c\x00\x0c\x00\x1e\x00\x12\x00\x21\x00\x40\x80\xe3\xc0'\ 157 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xf3\xc0'\ 158 | b'\x61\x00\x23\x00\x32\x00\x14\x00\x1c\x00\x08\x00\x08\x00\x08\x00'\ 159 | b'\x08\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 160 | b'\x00\x00\xfe\x00\x86\x00\x84\x00\x88\x00\x18\x00\x10\x00\x20\x00'\ 161 | b'\x22\x00\x42\x00\x82\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 162 | b'\x0b\x00\x00\x00\xf8\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00'\ 163 | b'\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00'\ 164 | b'\x80\x00\xf8\x00\x0b\x00\x00\x00\x80\x00\xc0\x00\x40\x00\x60\x00'\ 165 | b'\x20\x00\x30\x00\x10\x00\x10\x00\x18\x00\x08\x00\x0c\x00\x04\x00'\ 166 | b'\x06\x00\x02\x00\x03\x00\x00\x00\x0b\x00\x00\x00\xf8\x00\x08\x00'\ 167 | b'\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00'\ 168 | b'\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\xf8\x00\x0b\x00\x00\x00'\ 169 | b'\x00\x00\x10\x00\x38\x00\x28\x00\x64\x00\x44\x00\xc6\x00\x82\x00'\ 170 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 171 | b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 172 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xf0\x00\x00'\ 173 | b'\x00\x00\x00\x00\x0b\x00\x40\x00\xf0\x00\x1c\x00\x00\x00\x00\x00'\ 174 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 175 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 176 | b'\x00\x00\x3e\x00\x61\x00\x01\x00\x01\x00\x7f\x00\x81\x00\x81\x00'\ 177 | b'\x83\x00\x7d\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 178 | b'\xe0\x00\x20\x00\x20\x00\x2f\x00\x30\x80\x20\x40\x20\x40\x20\x40'\ 179 | b'\x20\x40\x30\x40\x30\x80\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 180 | b'\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x00\x41\x00\x81\x00'\ 181 | b'\x80\x00\x80\x00\x80\x00\x80\x00\x43\x00\x3e\x00\x00\x00\x00\x00'\ 182 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x07\x00\x01\x00\x01\x00\x01\x00'\ 183 | b'\x3d\x00\x43\x00\x81\x00\x81\x00\x81\x00\x81\x00\x43\x00\x3d\xc0'\ 184 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 185 | b'\x00\x00\x3e\x00\x61\x00\xc0\x80\x80\x80\xff\x80\x80\x00\x80\x00'\ 186 | b'\x41\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 187 | b'\x1f\x00\x20\x00\x20\x00\x20\x00\xfe\x00\x20\x00\x20\x00\x20\x00'\ 188 | b'\x20\x00\x20\x00\x20\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 189 | b'\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3d\xc0\x43\x00\x81\x00'\ 190 | b'\x81\x00\x81\x00\x81\x00\x83\x00\x43\x00\x3d\x00\x01\x00\x01\x00'\ 191 | b'\x03\x00\x7e\x00\x0b\x00\x00\x00\xe0\x00\x20\x00\x20\x00\x26\x00'\ 192 | b'\x39\x00\x31\x00\x21\x00\x21\x00\x21\x00\x21\x00\x21\x00\xff\xc0'\ 193 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x10\x00\x10\x00\x10\x00'\ 194 | b'\x00\x00\xf0\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00'\ 195 | b'\x10\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x04\x00'\ 196 | b'\x04\x00\x04\x00\x00\x00\xfc\x00\x04\x00\x04\x00\x04\x00\x04\x00'\ 197 | b'\x04\x00\x04\x00\x04\x00\x04\x00\x04\x00\x04\x00\x84\x00\x78\x00'\ 198 | b'\x0b\x00\x00\x00\xe0\x00\x20\x00\x20\x00\x27\x80\x22\x00\x24\x00'\ 199 | b'\x28\x00\x3c\x00\x34\x00\x26\x00\x23\x00\xfb\xc0\x00\x00\x00\x00'\ 200 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\xf8\x00\x08\x00\x08\x00\x08\x00'\ 201 | b'\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x7f\x80'\ 202 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 203 | b'\x00\x00\xd9\x80\x66\x40\x44\x40\x44\x40\x44\x40\x44\x40\x44\x40'\ 204 | b'\x44\x40\xe6\x60\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 205 | b'\x00\x00\x00\x00\x00\x00\xef\x00\x38\x80\x30\x80\x20\x80\x20\x80'\ 206 | b'\x20\x80\x20\x80\x20\x80\xfb\xe0\x00\x00\x00\x00\x00\x00\x00\x00'\ 207 | b'\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3e\x00\x41\x00\x80\x80'\ 208 | b'\x80\x80\x80\x80\x80\x80\x80\x80\x41\x00\x3e\x00\x00\x00\x00\x00'\ 209 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\xef\x00'\ 210 | b'\x30\x80\x20\x40\x20\x40\x20\x40\x20\x40\x20\x40\x30\x80\x2f\x00'\ 211 | b'\x20\x00\x20\x00\x20\x00\xf8\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 212 | b'\x00\x00\x3b\x80\x46\x00\x82\x00\x82\x00\x82\x00\x82\x00\x82\x00'\ 213 | b'\x46\x00\x3a\x00\x02\x00\x02\x00\x02\x00\x0f\x80\x0b\x00\x00\x00'\ 214 | b'\x00\x00\x00\x00\x00\x00\xe7\x00\x28\x00\x30\x00\x30\x00\x20\x00'\ 215 | b'\x20\x00\x20\x00\x20\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 216 | b'\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7e\x00\x86\x00\x82\x00'\ 217 | b'\xf8\x00\x7e\x00\x07\x00\x81\x00\xc1\x00\xfe\x00\x00\x00\x00\x00'\ 218 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x20\x00\x20\x00\x20\x00'\ 219 | b'\xfe\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x21\x00\x1e\x00'\ 220 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 221 | b'\x00\x00\xc7\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x43\x00'\ 222 | b'\x47\x00\x3d\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ 223 | b'\x00\x00\x00\x00\x00\x00\xf3\xe0\x60\x80\x21\x80\x31\x00\x13\x00'\ 224 | b'\x13\x00\x1a\x00\x0e\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 225 | b'\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xe0\x40\x40\x64\x40'\ 226 | b'\x64\xc0\x2a\x80\x2a\x80\x2a\x80\x31\x80\x31\x80\x00\x00\x00\x00'\ 227 | b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xc0'\ 228 | b'\x21\x00\x13\x00\x1e\x00\x0c\x00\x1e\x00\x33\x00\x61\x80\xe1\xc0'\ 229 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ 230 | b'\x00\x00\xf3\xe0\x60\x80\x21\x80\x31\x00\x13\x00\x1a\x00\x0e\x00'\ 231 | b'\x0c\x00\x04\x00\x08\x00\x08\x00\x10\x00\x60\x00\x0b\x00\x00\x00'\ 232 | b'\x00\x00\x00\x00\x00\x00\xfe\x00\x86\x00\x8c\x00\x18\x00\x10\x00'\ 233 | b'\x20\x00\x62\x00\xc2\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 234 | b'\x0b\x00\x00\x00\x0c\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00'\ 235 | b'\x10\x00\xe0\x00\x30\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00'\ 236 | b'\x10\x00\x0c\x00\x0b\x00\x00\x00\x80\x00\x80\x00\x80\x00\x80\x00'\ 237 | b'\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00'\ 238 | b'\x80\x00\x80\x00\x80\x00\x80\x00\x0b\x00\x00\x00\xc0\x00\x20\x00'\ 239 | b'\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x1c\x00\x30\x00\x20\x00'\ 240 | b'\x20\x00\x30\x00\x30\x00\x30\x00\x20\x00\xe0\x00\x0b\x00\x00\x00'\ 241 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x7f\x80'\ 242 | b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 243 | 244 | _index =\ 245 | b'\x00\x00\x24\x00\x24\x00\x48\x00\x48\x00\x6c\x00\x6c\x00\x90\x00'\ 246 | b'\x90\x00\xb4\x00\xb4\x00\xd8\x00\xd8\x00\xfc\x00\xfc\x00\x20\x01'\ 247 | b'\x20\x01\x44\x01\x44\x01\x68\x01\x68\x01\x8c\x01\x8c\x01\xb0\x01'\ 248 | b'\xb0\x01\xd4\x01\xd4\x01\xf8\x01\xf8\x01\x1c\x02\x1c\x02\x40\x02'\ 249 | b'\x40\x02\x64\x02\x64\x02\x88\x02\x88\x02\xac\x02\xac\x02\xd0\x02'\ 250 | b'\xd0\x02\xf4\x02\xf4\x02\x18\x03\x18\x03\x3c\x03\x3c\x03\x60\x03'\ 251 | b'\x60\x03\x84\x03\x84\x03\xa8\x03\xa8\x03\xcc\x03\xcc\x03\xf0\x03'\ 252 | b'\xf0\x03\x14\x04\x14\x04\x38\x04\x38\x04\x5c\x04\x5c\x04\x80\x04'\ 253 | b'\x80\x04\xa4\x04\xa4\x04\xc8\x04\xc8\x04\xec\x04\xec\x04\x10\x05'\ 254 | b'\x10\x05\x34\x05\x34\x05\x58\x05\x58\x05\x7c\x05\x7c\x05\xa0\x05'\ 255 | b'\xa0\x05\xc4\x05\xc4\x05\xe8\x05\xe8\x05\x0c\x06\x0c\x06\x30\x06'\ 256 | b'\x30\x06\x54\x06\x54\x06\x78\x06\x78\x06\x9c\x06\x9c\x06\xc0\x06'\ 257 | b'\xc0\x06\xe4\x06\xe4\x06\x08\x07\x08\x07\x2c\x07\x2c\x07\x50\x07'\ 258 | b'\x50\x07\x74\x07\x74\x07\x98\x07\x98\x07\xbc\x07\xbc\x07\xe0\x07'\ 259 | b'\xe0\x07\x04\x08\x04\x08\x28\x08\x28\x08\x4c\x08\x4c\x08\x70\x08'\ 260 | b'\x70\x08\x94\x08\x94\x08\xb8\x08\xb8\x08\xdc\x08\xdc\x08\x00\x09'\ 261 | b'\x00\x09\x24\x09\x24\x09\x48\x09\x48\x09\x6c\x09\x6c\x09\x90\x09'\ 262 | b'\x90\x09\xb4\x09\xb4\x09\xd8\x09\xd8\x09\xfc\x09\xfc\x09\x20\x0a'\ 263 | b'\x20\x0a\x44\x0a\x44\x0a\x68\x0a\x68\x0a\x8c\x0a\x8c\x0a\xb0\x0a'\ 264 | b'\xb0\x0a\xd4\x0a\xd4\x0a\xf8\x0a\xf8\x0a\x1c\x0b\x1c\x0b\x40\x0b'\ 265 | b'\x40\x0b\x64\x0b\x64\x0b\x88\x0b\x88\x0b\xac\x0b\xac\x0b\xd0\x0b'\ 266 | b'\xd0\x0b\xf4\x0b\xf4\x0b\x18\x0c\x18\x0c\x3c\x0c\x3c\x0c\x60\x0c'\ 267 | b'\x60\x0c\x84\x0c\x84\x0c\xa8\x0c\xa8\x0c\xcc\x0c\xcc\x0c\xf0\x0c'\ 268 | b'\xf0\x0c\x14\x0d\x14\x0d\x38\x0d\x38\x0d\x5c\x0d\x5c\x0d\x80\x0d'\ 269 | 270 | _mvfont = memoryview(_font) 271 | 272 | def get_ch(ch): 273 | ordch = ord(ch) 274 | ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 63 275 | idx_offs = 4 * (ordch - 32) 276 | offset = int.from_bytes(_index[idx_offs : idx_offs + 2], 'little') 277 | next_offs = int.from_bytes(_index[idx_offs + 2 : idx_offs + 4], 'little') 278 | width = int.from_bytes(_font[offset:offset + 2], 'little') 279 | return _mvfont[offset + 2:next_offs], 17, width 280 | 281 | -------------------------------------------------------------------------------- /my_cam/font10.py: -------------------------------------------------------------------------------- 1 | # Code generated by font-to-py.py. 2 | # Font: FreeSans.ttf 3 | version = '0.1' 4 | 5 | def height(): 6 | return 17 7 | 8 | def max_width(): 9 | return 17 10 | 11 | def hmap(): 12 | return True 13 | 14 | def reverse(): 15 | return False 16 | 17 | def monospaced(): 18 | return False 19 | 20 | _font =\ 21 | b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 22 | b'\x00\x00\x00\x06\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x80'\ 23 | b'\x00\xc0\x00\x00\x00\x00\x06\x00\x00\xf0\xf0\xf0\xa0\x00\x00\x00'\ 24 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x19'\ 25 | b'\x00\x19\x00\x13\x00\x7f\x80\x12\x00\x32\x00\x32\x00\xff\x80\x26'\ 26 | b'\x00\x24\x00\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x10'\ 27 | b'\x00\x3c\x00\x56\x00\xd3\x00\xd3\x00\xd0\x00\xd0\x00\x3c\x00\x17'\ 28 | b'\x00\x13\x00\xd3\x00\xd6\x00\x7c\x00\x10\x00\x00\x00\x00\x00\x00'\ 29 | b'\x00\x0f\x00\x00\x00\x78\x20\xcc\x40\xcc\x80\xcc\x80\xc9\x00\x31'\ 30 | b'\x00\x02\x78\x04\xcc\x04\xcc\x08\xcc\x08\xcc\x10\x78\x00\x00\x00'\ 31 | b'\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x1e\x00\x33\x00\x33\x00\x33'\ 32 | b'\x00\x1e\x00\x18\x00\x74\xc0\xe6\xc0\xc3\x80\xc1\x80\xe3\x80\x3c'\ 33 | b'\x40\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\xc0\xc0\xc0\x80'\ 34 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x10\x20'\ 35 | b'\x20\x60\x40\xc0\xc0\xc0\xc0\xc0\xc0\x40\x60\x20\x30\x10\x00\x06'\ 36 | b'\x00\x80\xc0\x40\x60\x20\x30\x30\x30\x30\x30\x30\x20\x60\x40\xc0'\ 37 | b'\x80\x00\x07\x00\x20\xa8\x70\x50\x50\x00\x00\x00\x00\x00\x00\x00'\ 38 | b'\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 39 | b'\x00\x00\x00\x30\x00\x30\x00\x30\x00\xfc\x00\x30\x00\x30\x00\x30'\ 40 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00'\ 41 | b'\x00\x00\x00\x00\x00\x00\x00\xc0\x40\x40\x80\x00\x06\x00\x00\x00'\ 42 | b'\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x04'\ 43 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00'\ 44 | b'\x00\x00\x05\x00\x08\x08\x10\x10\x10\x20\x20\x20\x40\x40\x40\x80'\ 45 | b'\x80\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66\x00\x42\x00\xc3'\ 46 | b'\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x42\x00\x66\x00\x3c'\ 47 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x10\x00\x30'\ 48 | b'\x00\xf0\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30'\ 49 | b'\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\ 50 | b'\x00\x3c\x00\x66\x00\xc3\x00\xc3\x00\x03\x00\x06\x00\x0c\x00\x38'\ 51 | b'\x00\x60\x00\x40\x00\xc0\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00'\ 52 | b'\x00\x09\x00\x00\x00\x7c\x00\xe7\x00\xc3\x00\x03\x00\x02\x00\x1c'\ 53 | b'\x00\x07\x00\x03\x00\x03\x00\xc3\x00\xe6\x00\x3c\x00\x00\x00\x00'\ 54 | b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\x0c\x00\x0c\x00\x1c\x00\x2c'\ 55 | b'\x00\x2c\x00\x4c\x00\x8c\x00\x8c\x00\xfe\x00\x0c\x00\x0c\x00\x0c'\ 56 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x7e\x00\x40'\ 57 | b'\x00\x40\x00\x80\x00\xbc\x00\xe6\x00\x03\x00\x03\x00\x03\x00\xc3'\ 58 | b'\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\ 59 | b'\x00\x3c\x00\x66\x00\x43\x00\xc0\x00\xc0\x00\xfc\x00\xe6\x00\xc3'\ 60 | b'\x00\xc3\x00\xc3\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00'\ 61 | b'\x00\x09\x00\x00\x00\xff\x00\x03\x00\x02\x00\x06\x00\x04\x00\x0c'\ 62 | b'\x00\x08\x00\x18\x00\x18\x00\x10\x00\x30\x00\x30\x00\x00\x00\x00'\ 63 | b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66\x00\xc3\x00\xc3'\ 64 | b'\x00\x66\x00\x3c\x00\x66\x00\xc3\x00\xc3\x00\xc3\x00\x66\x00\x3c'\ 65 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66'\ 66 | b'\x00\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x03\x00\x03\x00\xc2'\ 67 | b'\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00'\ 68 | b'\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00'\ 69 | b'\x04\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\xc0\x40'\ 70 | b'\x40\x80\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03'\ 71 | b'\x00\x0e\x00\x38\x00\xc0\x00\xe0\x00\x38\x00\x07\x00\x01\x00\x00'\ 72 | b'\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00'\ 73 | b'\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\xff\x00\x00'\ 74 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00'\ 75 | b'\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x70\x00\x1c\x00\x03\x00\x07'\ 76 | b'\x00\x1c\x00\xe0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09'\ 77 | b'\x00\x3c\x00\xc7\x00\xc3\x00\x03\x00\x03\x00\x06\x00\x0c\x00\x08'\ 78 | b'\x00\x18\x00\x18\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00'\ 79 | b'\x00\x00\x00\x11\x00\x07\xe0\x00\x0c\x38\x00\x30\x0c\x00\x20\x06'\ 80 | b'\x00\x63\xb7\x00\x4c\x73\x00\xcc\x63\x00\xd8\x63\x00\xd8\x63\x00'\ 81 | b'\xd8\x46\x00\xdc\xce\x00\x6f\x78\x00\x30\x00\x00\x18\x00\x00\x0f'\ 82 | b'\xe0\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x06\x00\x0e\x00\x0b\x00'\ 83 | b'\x1b\x00\x1b\x00\x11\x80\x31\x80\x31\x80\x3f\xc0\x60\xc0\x60\x40'\ 84 | b'\x40\x60\xc0\x60\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xfe\x00'\ 85 | b'\xc3\x80\xc1\x80\xc1\x80\xc1\x80\xc3\x00\xfe\x00\xc1\x80\xc0\xc0'\ 86 | b'\xc0\xc0\xc0\xc0\xc1\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 87 | b'\x0c\x00\x1f\x80\x30\xc0\x60\x60\x40\x60\xc0\x00\xc0\x00\xc0\x00'\ 88 | b'\xc0\x00\xc0\x00\x40\x60\x60\x60\x30\xc0\x1f\x80\x00\x00\x00\x00'\ 89 | b'\x00\x00\x00\x00\x0c\x00\xff\x00\xc1\x80\xc0\xc0\xc0\x60\xc0\x60'\ 90 | b'\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\xc0\xc1\x80\xff\x00'\ 91 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xff\x00\xc0\x00\xc0\x00'\ 92 | b'\xc0\x00\xc0\x00\xc0\x00\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ 93 | b'\xc0\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\xff\x00'\ 94 | b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfe\x00\xc0\x00\xc0\x00'\ 95 | b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 96 | b'\x0d\x00\x0f\xc0\x30\x60\x60\x30\x60\x00\xc0\x00\xc0\x00\xc1\xf0'\ 97 | b'\xc0\x30\xc0\x30\x60\x30\x60\x70\x30\xf0\x0f\x10\x00\x00\x00\x00'\ 98 | b'\x00\x00\x00\x00\x0c\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 99 | b'\xc0\xc0\xff\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 100 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\xc0\xc0\xc0\xc0\xc0\xc0'\ 101 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x09\x00\x06\x00\x06'\ 102 | b'\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\xc6'\ 103 | b'\x00\xc6\x00\xc4\x00\x78\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b'\ 104 | b'\x00\xc0\xc0\xc1\x80\xc3\x00\xc6\x00\xcc\x00\xd8\x00\xfc\x00\xe6'\ 105 | b'\x00\xc6\x00\xc3\x00\xc1\x80\xc1\x80\xc0\xc0\x00\x00\x00\x00\x00'\ 106 | b'\x00\x00\x00\x0a\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0'\ 107 | b'\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfe\x00\x00'\ 108 | b'\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xe0\x38\xe0\x38\xf0\x78\xf0'\ 109 | b'\x78\xd0\x58\xd8\xd8\xd8\xd8\xc8\x98\xcd\x98\xcd\x98\xc5\x18\xc7'\ 110 | b'\x18\xc7\x18\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\xe0\x60\xe0'\ 111 | b'\x60\xf0\x60\xd0\x60\xd8\x60\xcc\x60\xc4\x60\xc6\x60\xc3\x60\xc3'\ 112 | b'\x60\xc1\xe0\xc0\xe0\xc0\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\ 113 | b'\x00\x1f\x80\x30\xc0\x60\x60\xe0\x60\xc0\x30\xc0\x30\xc0\x30\xc0'\ 114 | b'\x30\xc0\x30\xe0\x60\x60\x60\x30\xc0\x1f\x80\x00\x00\x00\x00\x00'\ 115 | b'\x00\x00\x00\x0b\x00\xff\x00\xc1\x80\xc0\xc0\xc0\xc0\xc0\xc0\xc1'\ 116 | b'\x80\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00'\ 117 | b'\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x1f\x80\x30\xc0\x60\x60\xe0'\ 118 | b'\x60\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xe1\x60\x61\xe0\x30'\ 119 | b'\xc0\x1f\xe0\x00\x20\x00\x00\x00\x00\x00\x00\x0c\x00\xff\x00\xc1'\ 120 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc1\x80\xff\x00\xc1\xc0\xc0\xc0\xc0'\ 121 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0b'\ 122 | b'\x00\x3f\x00\x61\x80\xc0\xc0\xc0\x00\xc0\x00\x60\x00\x3e\x00\x07'\ 123 | b'\x80\x01\xc0\xc0\xc0\xc0\xc0\x61\x80\x3f\x00\x00\x00\x00\x00\x00'\ 124 | b'\x00\x00\x00\x0b\x00\xff\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18'\ 125 | b'\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x00'\ 126 | b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 127 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x61'\ 128 | b'\x80\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xc0\x60\x40'\ 129 | b'\x40\x60\xc0\x60\xc0\x20\x80\x31\x80\x31\x80\x11\x00\x1b\x00\x0b'\ 130 | b'\x00\x0a\x00\x0e\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10'\ 131 | b'\x00\xc1\x83\xc1\x82\x42\x86\x62\xc6\x62\xc6\x62\x44\x24\x44\x24'\ 132 | b'\x6c\x34\x2c\x3c\x28\x18\x38\x18\x38\x18\x18\x00\x00\x00\x00\x00'\ 133 | b'\x00\x00\x00\x0b\x00\x60\x40\x20\xc0\x31\x80\x19\x00\x1b\x00\x0e'\ 134 | b'\x00\x06\x00\x0e\x00\x1b\x00\x11\x80\x31\x80\x60\xc0\x40\x60\x00'\ 135 | b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x40\x60\x60\x60\x30\xc0\x30'\ 136 | b'\xc0\x19\x80\x0d\x00\x0f\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06'\ 137 | b'\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\xff\x80\x01'\ 138 | b'\x80\x03\x00\x06\x00\x06\x00\x0c\x00\x18\x00\x18\x00\x30\x00\x60'\ 139 | b'\x00\x60\x00\xc0\x00\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x05'\ 140 | b'\x00\xe0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 141 | b'\xc0\xe0\x05\x00\x80\x80\x40\x40\x40\x20\x20\x20\x10\x10\x10\x08'\ 142 | b'\x08\x00\x00\x00\x00\x05\x00\xe0\x60\x60\x60\x60\x60\x60\x60\x60'\ 143 | b'\x60\x60\x60\x60\x60\x60\x60\xe0\x08\x00\x00\x30\x30\x78\x48\x48'\ 144 | b'\x84\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00'\ 145 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 146 | b'\x00\x00\x00\x00\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x04'\ 147 | b'\x00\xc0\x40\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 148 | b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7c\x00\xc6\x00'\ 149 | b'\x06\x00\x06\x00\x7e\x00\xc6\x00\xc6\x00\xce\x00\x77\x00\x00\x00'\ 150 | b'\x00\x00\x00\x00\x00\x00\x09\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ 151 | b'\xde\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xe3\x00'\ 152 | b'\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ 153 | b'\x00\x00\x00\x00\x3c\x00\x66\x00\xc3\x00\xc0\x00\xc0\x00\xc0\x00'\ 154 | b'\xc3\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00'\ 155 | b'\x03\x00\x03\x00\x03\x00\x03\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00'\ 156 | b'\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x00\x00\x00\x00\x00\x00'\ 157 | b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c\x00\x66\x00'\ 158 | b'\xc3\x00\xc3\x00\xff\x00\xc0\x00\xc3\x00\x66\x00\x3c\x00\x00\x00'\ 159 | b'\x00\x00\x00\x00\x00\x00\x05\x00\x30\x60\x60\x60\xf0\x60\x60\x60'\ 160 | b'\x60\x60\x60\x60\x60\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00'\ 161 | b'\x00\x00\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3'\ 162 | b'\x00\x67\x00\x3b\x00\x03\x00\x03\x00\xc6\x00\x7c\x00\x09\x00\xc0'\ 163 | b'\x00\xc0\x00\xc0\x00\xc0\x00\xde\x00\xe3\x00\xc3\x00\xc3\x00\xc3'\ 164 | b'\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x00\x00\x00\x00\x00\x00\x00'\ 165 | b'\x00\x04\x00\xc0\x00\x00\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 166 | b'\x00\x00\x00\x00\x04\x00\x60\x00\x00\x00\x60\x60\x60\x60\x60\x60'\ 167 | b'\x60\x60\x60\x60\x60\x60\xc0\x09\x00\xc0\x00\xc0\x00\xc0\x00\xc0'\ 168 | b'\x00\xc6\x00\xcc\x00\xd8\x00\xf8\x00\xe8\x00\xcc\x00\xc6\x00\xc6'\ 169 | b'\x00\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\xc0\xc0\xc0'\ 170 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x0e\x00'\ 171 | b'\x00\x00\x00\x00\x00\x00\x00\x00\xdd\xe0\xe7\x30\xc6\x30\xc6\x30'\ 172 | b'\xc6\x30\xc6\x30\xc6\x30\xc6\x30\xc6\x30\x00\x00\x00\x00\x00\x00'\ 173 | b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\x00\xe3\x00'\ 174 | b'\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x00\x00'\ 175 | b'\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 176 | b'\x3c\x00\x66\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x66\x00'\ 177 | b'\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ 178 | b'\x00\x00\x00\x00\xde\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\ 179 | b'\xc1\x80\xe3\x00\xde\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x0a\x00'\ 180 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00'\ 181 | b'\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x03\x00\x03\x00\x03\x00'\ 182 | b'\x00\x00\x06\x00\x00\x00\x00\x00\xd8\xe0\xc0\xc0\xc0\xc0\xc0\xc0'\ 183 | b'\xc0\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\x70'\ 184 | b'\x0e\xc6\xc6\x7c\x00\x00\x00\x00\x05\x00\x00\x00\x60\x60\xf0\x60'\ 185 | b'\x60\x60\x60\x60\x60\x60\x70\x00\x00\x00\x00\x09\x00\x00\x00\x00'\ 186 | b'\x00\x00\x00\x00\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3'\ 187 | b'\x00\xc3\x00\xc7\x00\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08'\ 188 | b'\x00\x00\x00\x00\x00\xc3\x43\x62\x66\x26\x34\x3c\x18\x18\x00\x00'\ 189 | b'\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6\x30\x46\x30'\ 190 | b'\x47\x20\x6f\x20\x69\x60\x29\x60\x29\xc0\x39\xc0\x10\xc0\x00\x00'\ 191 | b'\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x42\x66\x34\x18'\ 192 | b'\x18\x1c\x24\x66\x43\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\xc3'\ 193 | b'\x42\x42\x66\x24\x24\x3c\x18\x18\x18\x10\x30\x60\x08\x00\x00\x00'\ 194 | b'\x00\x00\xfe\x0c\x08\x18\x30\x60\x40\xc0\xfe\x00\x00\x00\x00\x06'\ 195 | b'\x00\x30\x60\x60\x60\x60\x60\x60\xe0\xc0\xe0\x60\x60\x60\x60\x60'\ 196 | b'\x60\x30\x04\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 197 | b'\xc0\xc0\xc0\xc0\x00\x06\x00\xc0\x60\x60\x60\x60\x60\x60\x70\x30'\ 198 | b'\x70\x60\x60\x60\x60\x60\x60\xc0\x09\x00\x00\x00\x00\x00\x00\x00'\ 199 | b'\x00\x00\x00\x00\x00\x00\x62\x00\x9e\x00\x00\x00\x00\x00\x00\x00'\ 200 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 201 | 202 | _index =\ 203 | b'\x00\x00\x13\x00\x26\x00\x39\x00\x5d\x00\x81\x00\xa5\x00\xc9\x00'\ 204 | b'\xdc\x00\xef\x00\x02\x01\x15\x01\x39\x01\x4c\x01\x5f\x01\x72\x01'\ 205 | b'\x85\x01\xa9\x01\xcd\x01\xf1\x01\x15\x02\x39\x02\x5d\x02\x81\x02'\ 206 | b'\xa5\x02\xc9\x02\xed\x02\x00\x03\x13\x03\x37\x03\x5b\x03\x7f\x03'\ 207 | b'\xa3\x03\xd8\x03\xfc\x03\x20\x04\x44\x04\x68\x04\x8c\x04\xb0\x04'\ 208 | b'\xd4\x04\xf8\x04\x0b\x05\x2f\x05\x53\x05\x77\x05\x9b\x05\xbf\x05'\ 209 | b'\xe3\x05\x07\x06\x2b\x06\x4f\x06\x73\x06\x97\x06\xbb\x06\xdf\x06'\ 210 | b'\x03\x07\x27\x07\x4b\x07\x6f\x07\x82\x07\x95\x07\xa8\x07\xbb\x07'\ 211 | b'\xdf\x07\xf2\x07\x16\x08\x3a\x08\x5e\x08\x82\x08\xa6\x08\xb9\x08'\ 212 | b'\xdd\x08\x01\x09\x14\x09\x27\x09\x4b\x09\x5e\x09\x82\x09\xa6\x09'\ 213 | b'\xca\x09\xee\x09\x12\x0a\x25\x0a\x38\x0a\x4b\x0a\x6f\x0a\x82\x0a'\ 214 | b'\xa6\x0a\xb9\x0a\xcc\x0a\xdf\x0a\xf2\x0a\x05\x0b\x18\x0b\x3c\x0b'\ 215 | 216 | _mvfont = memoryview(_font) 217 | 218 | def _chr_addr(ordch): 219 | offset = 2 * (ordch - 32) 220 | return int.from_bytes(_index[offset:offset + 2], 'little') 221 | 222 | def get_ch(ch): 223 | ordch = ord(ch) 224 | ordch = ordch if ordch >= 32 and ordch <= 126 else ord('?') 225 | offset = _chr_addr(ordch) 226 | width = int.from_bytes(_font[offset:offset + 2], 'little') 227 | next_offs = _chr_addr(ordch +1) 228 | return _mvfont[offset + 2:next_offs], 17, width 229 | 230 | -------------------------------------------------------------------------------- /my_cam/interpolate_a.py: -------------------------------------------------------------------------------- 1 | # interpolate_a.py Bicubic interpolator for AMG8833 thermal IR sensor 2 | # version for STM using Arm Thumb2 Assembler 3 | 4 | # Released under the MIT licence. 5 | # Copyright (c) Peter Hinch 2019 6 | 7 | # Algorithm derivation https://www.paulinternet.nl/?page=bicubic 8 | 9 | from array import array 10 | import math 11 | 12 | # Sensor is 8*8. 13 | _PIXEL_ARRAY_WIDTH = const(8) 14 | _PIXEL_ARRAY_HEIGHT = const(8) 15 | # Extrapolate 1 pixel at each edge so that the 1st derivative can be estimated. 16 | _WIDTH = const(10) 17 | _HEIGHT = const(10) 18 | 19 | # Cubic interpolation of a 4 element one dimensional array of samples p. 20 | # Interpolation is between samples p[1] and p[2]: samples p[0] and p[3] provide 21 | # a first derivative estimate at points p[1] and p[2]. 22 | # 0 <= x < 1.0 is the offset between p[1] and p[2] 23 | # p[1] + 0.5 * x*(p[2] - p[0] + x*(2.0*p[0] - 5.0*p[1] + 4.0*p[2] - p[3] + x*(3.0*(p[1] - p[2]) + p[3] - p[0]))) 24 | 25 | # r0: Array of 4 samples 26 | # r1: Array of _coefficients. r1[0] = x, r1[1:] = 0.5, 2.0, 3.0, 4.0, 5.0 27 | # r2: r2[0] will hold result 28 | @micropython.asm_thumb 29 | def interp_arr(r0, r1, r2): 30 | vldr(s0, [r1, 4]) # s0 = 0.5 31 | vldr(s1, [r1, 8]) # s1 = 2.0 32 | vldr(s2, [r1, 12]) # s2 = 3.0 33 | vldr(s3, [r1, 16]) # s3 = 4.0 34 | vldr(s4, [r1, 20]) # s4 = 5.0 35 | vldr(s5, [r0, 0]) # s5 = p[0] 36 | vldr(s6, [r0, 4]) # s6 = p[1] 37 | vldr(s7, [r0, 8]) # s7 = p[2] 38 | vldr(s8, [r0, 12]) # s8 = p[3] 39 | vldr(s9, [r1, 0]) # s9 = x 40 | vsub(s10, s6, s7) # s10 = p[1] -p[2] 41 | vmul(s10, s10, s2) # s10 = 3.0*(p[1] - p[2]) 42 | vadd(s10, s10, s8) # s10 += p[3] 43 | vsub(s10, s10, s5) # s10 -= p[0] 44 | vmul(s10, s10, s9) # s10 *= x 45 | vsub(s10, s10, s8) # s10 -= p[3] 46 | vmul(s11, s3, s7) # s11 = 4.0*p[2] 47 | vadd(s10, s10, s11) # s10 += 4.0*p[2] 48 | vmul(s11, s4, s6) # s11 = 5.0*p[1] 49 | vsub(s10, s10, s11) # s10 -= 5.0*p[1] 50 | vmul(s11, s1, s5) # s11 = 2.0*p[0] 51 | vadd(s10, s10, s11) # s10 += 2.0*p[0] 52 | vmul(s10, s10, s9) # s10 *= x 53 | vsub(s10, s10, s5) # s10 -= p[0] 54 | vadd(s10, s10, s7) # s10 += p[2] 55 | vmul(s10, s10, s9) # s10 *= x 56 | vmul(s10, s10, s0) # s10 *= 0.5 57 | vadd(s10, s10, s6) # s10 += p[1] 58 | vstr(s10, [r2, 0]) 59 | 60 | # Return index into data array from row, col 61 | _idx = lambda r, c : r * _WIDTH + c 62 | 63 | class Interpolator: 64 | def __init__(self, sensor): 65 | self._sensor = sensor 66 | self._data = array('f', (0 for _ in range(_HEIGHT * _WIDTH))) 67 | self._coeffs = array('f', (0.0, 0.5, 2.0, 3.0, 4.0, 5.0)) 68 | self._mvd = memoryview(self._data) 69 | self._rd = array('f', (0 for _ in range(4))) 70 | self._mvrd = memoryview(self._rd) 71 | 72 | def refresh(self, _=None): 73 | s = self._sensor 74 | s.refresh() 75 | # Populate sensor data 76 | for row in range(_PIXEL_ARRAY_HEIGHT): 77 | for col in range(_PIXEL_ARRAY_WIDTH): 78 | self[row + 1, col + 1] = s[row, col] 79 | # Extrapolate edges 80 | # Populate corners 81 | self[0, 0] = 2 * self[1, 1] - self[2, 2] 82 | self[0, _WIDTH -1] = 2 * self[1, _WIDTH -2] - self[2, _WIDTH -3] 83 | self[_HEIGHT -1, 0] = 2 * self[_HEIGHT -2, 1] - self[_HEIGHT -3, 2] 84 | self[_HEIGHT -1, _WIDTH -1] = 2 * self[_HEIGHT -2, _WIDTH -2] - self[_HEIGHT -3, _WIDTH -3] 85 | # Populate edges 86 | col = _WIDTH -1 87 | for row in range(1, _HEIGHT -1): 88 | self[row, 0] = 2 * self[row, 1] - self[row, 2] 89 | self[row, col] = 2 * self[row, col -1] - self[row, col -2] 90 | row = _HEIGHT -1 91 | for col in range(1, _WIDTH -1): 92 | self[0, col] = 2 * self[1, col] - self[2, col] 93 | self[row, col] = 2 * self[row -1, col] - self[row -2, col] 94 | 95 | def __getitem__(self, index): 96 | return self._data[_idx(*index)] 97 | 98 | def __setitem__(self, index, v): 99 | self._data[_idx(*index)] = v 100 | 101 | @micropython.native 102 | def bicubic(self, offs, y, x): 103 | c = self._coeffs 104 | rd = self._mvrd 105 | line = self._mvd # line[offs] points to sample set 106 | c[0] = x 107 | interp_arr(line[offs:], c, rd) # Get value of location x of row 0 108 | offs += _WIDTH # Increment one row 109 | interp_arr(line[offs:], c, rd[1:]) 110 | offs += _WIDTH 111 | interp_arr(line[offs:], c, rd[2:]) 112 | offs += _WIDTH 113 | interp_arr(line[offs:], c, rd[3:]) 114 | c[0] = y 115 | interp_arr(rd, c, c) # interpolate the column of data 116 | return c[0] 117 | 118 | # Access interpolated data by row, col: bounding box 0.0,0.0 -> 1.0,1.0 119 | def __call__(self, r, c): 120 | if r < 0.0 or r > 1.0 or c < 0.0 or c > 1.0: 121 | r = max(min(r, 1.0), 0.0) 122 | c = max(min(c, 1.0), 0.0) 123 | y, row = math.modf(r * 6.99) 124 | x, col = math.modf(c * 6.99) 125 | return self.bicubic(int(row * _WIDTH + col), y, x) 126 | -------------------------------------------------------------------------------- /my_cam/mapper.py: -------------------------------------------------------------------------------- 1 | # mapper.py Color mapper for thermal IR cameras 2 | 3 | # Released under the MIT licence. 4 | # Copyright (c) Peter Hinch 2019 5 | 6 | # Mapper class. Converts a temperature value to r, g, b colors. Colors are in 7 | # range 0..255. Temperature range may be specified. 8 | class Mapper: 9 | 10 | def __init__(self, tmin, tmax, ncolors=30): 11 | self._ncolors = ncolors 12 | N = ncolors 13 | self._b = bytearray(max(int(255*(1 - 2*x/N)), 0) for x in range(N + 1)) 14 | self._g = bytearray(int(255*2*x/N) if x < N/2 else int(255*2*(1 - x/N)) for x in range(N + 1)) 15 | self._r = bytearray(max(int(255*(2*x/N - 1)), 0) for x in range(N + 1)) 16 | self.set_range(tmin, tmax) 17 | 18 | def set_range(self, tmin, tmax): 19 | if tmax <= tmin: 20 | raise ValueError('Invalid temperature range.') 21 | self._tmin = tmin 22 | self._tmax = tmax 23 | self._factor = self._ncolors/(tmax - tmin) 24 | 25 | def __call__(self, t): # Celcius to color value 26 | # Constrain 27 | t = max(min(t, self._tmax), self._tmin) 28 | # Ensure +ve, inclusive range 0..tmax-tmin 29 | t -= self._tmin 30 | t = round(t * self._factor) # 0..ncolors 31 | return self._r[t], self._g[t], self._b[t] 32 | -------------------------------------------------------------------------------- /my_cam/pir1.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-amg88xx/d7442f233c57f7a964de1e1926b7af39c614e7df/my_cam/pir1.fzz -------------------------------------------------------------------------------- /my_cam/ssd1351_16bit.py: -------------------------------------------------------------------------------- 1 | # SSD1351_16bit.py MicroPython driver for Adafruit color OLED displays. 2 | 3 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 4 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 5 | # For wiring details see drivers/ADAFRUIT.md in this repo. 6 | 7 | # This driver is based on the Adafruit C++ library for Arduino 8 | # https://github.com/adafruit/Adafruit-SSD1351-library.git 9 | 10 | # The MIT License (MIT) 11 | 12 | # Copyright (c) 2019 Peter Hinch 13 | 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in 22 | # all copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | # THE SOFTWARE. 31 | 32 | import framebuf 33 | import utime 34 | import gc 35 | import micropython 36 | from uctypes import addressof 37 | 38 | # Initialisation commands in cmd_init: 39 | # 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode 40 | # 0xae, # display off (sleep mode) 41 | # 0xb3, 0xf1, # clock div 42 | # 0xca, 0x7f, # mux ratio 43 | # 0xa0, 0x74, # setremap 0x74 44 | # 0x15, 0, 0x7f, # setcolumn 45 | # 0x75, 0, 0x7f, # setrow 46 | # 0xa1, 0, # set display start line 47 | # 0xa2, 0, # displayoffset 48 | # 0xb5, 0, # setgpio 49 | # 0xab, 1, # functionselect: serial interface, internal Vdd regulator 50 | # 0xb1, 0x32, # Precharge 51 | # 0xbe, 0x05, # vcommh 52 | # 0xa6, # normaldisplay 53 | # 0xc1, 0xc8, 0x80, 0xc8, # contrast abc 54 | # 0xc7, 0x0f, # Master contrast 55 | # 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit) 56 | # 0xb6, 1, # Precharge 2 57 | # 0xaf, # Display on 58 | 59 | # SPI baudrate: Pyboard can produce 10.5MHz or 21MHz. Datasheet gives max of 20MHz. 60 | # Attempt to use 21MHz failed but might work on a PCB or with very short leads. 61 | class SSD1351(framebuf.FrameBuffer): 62 | # Convert r, g, b in range 0-255 to a 16 bit colour value RGB565 63 | # acceptable to hardware: rrrrrggggggbbbbb 64 | @staticmethod 65 | def rgb(r, g, b): 66 | return ((r & 0xf8) << 5) | ((g & 0x1c) << 11) | (b & 0xf8) | ((g & 0xe0) >> 5) 67 | 68 | def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128): 69 | if height not in (96, 128): 70 | raise ValueError('Unsupported height {}'.format(height)) 71 | self.spi = spi 72 | self.rate = 11000000 # See baudrate note above. 73 | self.pincs = pincs 74 | self.pindc = pindc # 1 = data 0 = cmd 75 | self.height = height # Required by Writer class 76 | self.width = width 77 | # Save color mode for use by writer_gui (blit) 78 | self.mode = framebuf.RGB565 79 | gc.collect() 80 | self.buffer = bytearray(self.height * self.width * 2) 81 | super().__init__(self.buffer, self.width, self.height, self.mode) 82 | self.mvb = memoryview(self.buffer) 83 | pinrs(0) # Pulse the reset line 84 | utime.sleep_ms(1) 85 | pinrs(1) 86 | utime.sleep_ms(1) 87 | # See above comment to explain this allocation-saving gibberish. 88 | self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\ 89 | b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\ 90 | b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\ 91 | b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0) 92 | self.show() 93 | gc.collect() 94 | 95 | def _write(self, mv, dc): 96 | self.spi.init(baudrate=self.rate, polarity=1, phase=1) 97 | self.pincs(1) 98 | self.pindc(dc) 99 | self.pincs(0) 100 | self.spi.write(bytes(mv)) 101 | self.pincs(1) 102 | 103 | # Write lines from the framebuf out of order to match the mapping of the 104 | # SSD1351 RAM to the OLED device. 105 | def show(self): 106 | mvb = self.mvb 107 | bw = self.width * 2 # Width in bytes 108 | self._write(b'\x5c', 0) # Enable data write 109 | if self.height == 128: 110 | for l in range(128): 111 | l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126 .. 96 112 | start = l0 * self.width * 2 113 | self._write(mvb[start : start + bw], 1) # Send a line 114 | else: 115 | for l in range(128): 116 | if l < 64: 117 | start = (63 -l) * self.width * 2 # 63 62 .. 1 0 118 | elif l < 96: 119 | start = 0 120 | else: 121 | start = (191 - l) * self.width * 2 # 127 126 .. 95 122 | self._write(mvb[start : start + bw], 1) # Send a line 123 | -------------------------------------------------------------------------------- /my_cam/writer.py: -------------------------------------------------------------------------------- 1 | # writer.py Implements the Writer class. 2 | # V0.3 Peter Hinch 11th Aug 2018 3 | # Handles colour, upside down diplays, word wrap and tab stops 4 | 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2016 Peter Hinch 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in 17 | # all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | # THE SOFTWARE. 26 | 27 | # A Writer supports rendering text to a Display instance in a given font. 28 | # Multiple Writer instances may be created, each rendering a font to the 29 | # same Display object. 30 | 31 | # Timings based on a 20 pixel high proportional font, run on a pyboard V1.0. 32 | # Using CWriter's slow rendering: _printchar 9.5ms typ, 13.5ms max. 33 | # Using Writer's fast rendering: _printchar 115μs min 480μs typ 950μs max. 34 | 35 | import framebuf 36 | 37 | class DisplayState(): 38 | def __init__(self): 39 | self.text_row = 0 40 | self.text_col = 0 41 | self.usd = False 42 | 43 | def _get_id(device): 44 | if not isinstance(device, framebuf.FrameBuffer): 45 | raise ValueError('Device must be derived from FrameBuffer.') 46 | return id(device) 47 | 48 | # Basic Writer class for monochrome displays 49 | class Writer(): 50 | 51 | state = {} # Holds a display state for each device 52 | 53 | @staticmethod 54 | def set_textpos(device, row=None, col=None): 55 | devid = _get_id(device) 56 | if devid not in Writer.state: 57 | Writer.state[devid] = DisplayState() 58 | s = Writer.state[devid] # Current state 59 | if row is not None: 60 | if row < 0 or row >= device.height: 61 | raise ValueError('row is out of range') 62 | s.text_row = device.height - 1 - row if s.usd else row 63 | if col is not None: 64 | if col < 0 or col >= device.width: 65 | raise ValueError('col is out of range') 66 | s.text_col = device.width -1 - col if s.usd else col 67 | return s.text_row, s.text_col 68 | 69 | def __init__(self, device, font, verbose=True): 70 | self.devid = _get_id(device) 71 | self.device = device 72 | if self.devid not in Writer.state: 73 | Writer.state[self.devid] = DisplayState() 74 | self.font = font 75 | self.usd = Writer.state[self.devid].usd 76 | 77 | # Allow to work with reverse or normal font mapping 78 | if font.hmap(): 79 | self.map = framebuf.MONO_HMSB if font.reverse() else framebuf.MONO_HLSB 80 | else: 81 | raise ValueError('Font must be horizontally mapped.') 82 | if verbose: 83 | fstr = 'Orientation: Horizontal. Reversal: {}. Width: {}. Height: {}.' 84 | print(fstr.format(font.reverse(), device.width, device.height)) 85 | print('Start row = {} col = {}'.format(self._getstate().text_row, self._getstate().text_col)) 86 | self.screenwidth = device.width # In pixels 87 | self.screenheight = device.height 88 | self.bgcolor = 0 # Monochrome background and foreground colors 89 | self.fgcolor = 1 90 | self.row_clip = False # Clip or scroll when screen full 91 | self.col_clip = False # Clip or new line when row is full 92 | self.wrap = True # Word wrap 93 | self.cpos = 0 94 | self.tab = 4 95 | 96 | self.glyph = None # Current char 97 | self.char_height = 0 98 | self.char_width = 0 99 | 100 | def _getstate(self): 101 | return Writer.state[self.devid] 102 | 103 | def _newline(self): 104 | s = self._getstate() 105 | height = self.font.height() 106 | if self.usd: 107 | s.text_row -= height 108 | s.text_col = self.screenwidth - 1 109 | margin = s.text_row - height 110 | y = 0 111 | else: 112 | s.text_row += height 113 | s.text_col = 0 114 | margin = self.screenheight - (s.text_row + height) 115 | y = self.screenheight + margin 116 | if margin < 0: 117 | if not self.row_clip: 118 | if self.usd: 119 | margin = -margin 120 | self.device.scroll(0, margin) 121 | self.device.fill_rect(0, y, self.screenwidth, abs(margin), self.bgcolor) 122 | s.text_row += margin 123 | 124 | def set_clip(self, row_clip=None, col_clip=None, wrap=None): 125 | if row_clip is not None: 126 | self.row_clip = row_clip 127 | if col_clip is not None: 128 | self.col_clip = col_clip 129 | if wrap is not None: 130 | self.wrap = wrap 131 | return self.row_clip, self.col_clip, self.wrap 132 | 133 | @property 134 | def height(self): # Property for consistency with device 135 | return self.font.height() 136 | 137 | def printstring(self, string, invert=False): 138 | # word wrapping. Assumes words separated by single space. 139 | while True: 140 | lines = string.split('\n', 1) 141 | s = lines[0] 142 | if s: 143 | self._printline(s, invert) 144 | if len(lines) == 1: 145 | break 146 | else: 147 | self._printchar('\n') 148 | string = lines[1] 149 | 150 | def _printline(self, string, invert): 151 | rstr = None 152 | if self.wrap and self.stringlen(string) > self.screenwidth: 153 | pos = 0 154 | lstr = string[:] 155 | while self.stringlen(lstr) > self.screenwidth: 156 | pos = lstr.rfind(' ') 157 | lstr = lstr[:pos].rstrip() 158 | if pos > 0: 159 | rstr = string[pos + 1:] 160 | string = lstr 161 | 162 | for char in string: 163 | self._printchar(char, invert) 164 | if rstr is not None: 165 | self._printchar('\n') 166 | self._printline(rstr, invert) # Recurse 167 | 168 | def stringlen(self, string): 169 | l = 0 170 | for char in string: 171 | l += self._charlen(char) 172 | return l 173 | 174 | def _charlen(self, char): 175 | if char == '\n': 176 | char_width = 0 177 | else: 178 | _, _, char_width = self.font.get_ch(char) 179 | return char_width 180 | 181 | def _get_char(self, char, recurse): 182 | if not recurse: # Handle tabs 183 | if char == '\n': 184 | self.cpos = 0 185 | elif char == '\t': 186 | nspaces = self.tab - (self.cpos % self.tab) 187 | if nspaces == 0: 188 | nspaces = self.tab 189 | while nspaces: 190 | nspaces -= 1 191 | self._printchar(' ', recurse=True) 192 | self.glyph = None # All done 193 | return 194 | 195 | self.glyph = None # Assume all done 196 | if char == '\n': 197 | self._newline() 198 | return 199 | glyph, char_height, char_width = self.font.get_ch(char) 200 | s = self._getstate() 201 | if self.usd: 202 | if s.text_row - char_height < 0: 203 | if self.row_clip: 204 | return 205 | self._newline() 206 | if s.text_col - char_width < 0: 207 | if self.col_clip: 208 | return 209 | else: 210 | self._newline() 211 | else: 212 | if s.text_row + char_height > self.screenheight: 213 | if self.row_clip: 214 | return 215 | self._newline() 216 | if s.text_col + char_width > self.screenwidth: 217 | if self.col_clip: 218 | return 219 | else: 220 | self._newline() 221 | self.glyph = glyph 222 | self.char_height = char_height 223 | self.char_width = char_width 224 | 225 | # Method using blitting. Efficient rendering for monochrome displays. 226 | # Tested on SSD1306. Invert is for black-on-white rendering. 227 | def _printchar(self, char, invert=False, recurse=False): 228 | s = self._getstate() 229 | self._get_char(char, recurse) 230 | if self.glyph is None: 231 | return # All done 232 | buf = bytearray(self.glyph) 233 | if invert: 234 | for i, v in enumerate(buf): 235 | buf[i] = 0xFF & ~ v 236 | fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map) 237 | self.device.blit(fbc, s.text_col, s.text_row) 238 | s.text_col += self.char_width 239 | self.cpos += 1 240 | 241 | def tabsize(self, value=None): 242 | if value is not None: 243 | self.tab = value 244 | return self.tab 245 | 246 | def setcolor(self, *_): 247 | return self.fgcolor, self.bgcolor 248 | 249 | # Writer for colour displays or upside down rendering 250 | class CWriter(Writer): 251 | 252 | @staticmethod 253 | def invert_display(device, value=True): 254 | devid = id(device) 255 | if devid not in Writer.state: 256 | Writer.state[devid] = DisplayState() 257 | Writer.state[devid].usd = value 258 | 259 | def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True): 260 | super().__init__(device, font, verbose) 261 | if bgcolor is not None: # Assume monochrome. 262 | self.bgcolor = bgcolor 263 | if fgcolor is not None: 264 | self.fgcolor = fgcolor 265 | self.def_bgcolor = self.bgcolor 266 | self.def_fgcolor = self.fgcolor 267 | 268 | def setcolor(self, fgcolor=None, bgcolor=None): 269 | if fgcolor is None and bgcolor is None: 270 | self.fgcolor = self.def_fgcolor 271 | self.bgcolor = self.def_bgcolor 272 | else: 273 | if fgcolor is not None: 274 | self.fgcolor = fgcolor 275 | if bgcolor is not None: 276 | self.bgcolor = bgcolor 277 | return self.fgcolor, self.bgcolor 278 | 279 | def _printchar(self, char, invert=False, recurse=False): 280 | s = self._getstate() 281 | self._get_char(char, recurse) 282 | if self.glyph is None: 283 | return # All done 284 | char_height = self.char_height 285 | char_width = self.char_width 286 | 287 | div, mod = divmod(char_width, 8) 288 | gbytes = div + 1 if mod else div # No. of bytes per row of glyph 289 | device = self.device 290 | fgcolor = self.bgcolor if invert else self.fgcolor 291 | bgcolor = self.fgcolor if invert else self.bgcolor 292 | usd = self.usd 293 | drow = s.text_row # Destination row 294 | wcol = s.text_col # Destination column of character start 295 | for srow in range(char_height): # Source row 296 | for scol in range(char_width): # Source column 297 | # Destination column: add/subtract writer column 298 | if usd: 299 | dcol = wcol - scol 300 | else: 301 | dcol = wcol + scol 302 | gbyte, gbit = divmod(scol, 8) 303 | if gbit == 0: # Next glyph byte 304 | data = self.glyph[srow * gbytes + gbyte] 305 | pixel = fgcolor if data & (1 << (7 - gbit)) else bgcolor 306 | device.pixel(dcol, drow, pixel) 307 | drow += -1 if usd else 1 308 | if drow >= self.screenheight or drow < 0: 309 | break 310 | s.text_col += -char_width if usd else char_width 311 | self.cpos += 1 312 | -------------------------------------------------------------------------------- /ssd1331.py: -------------------------------------------------------------------------------- 1 | # SSD1331.py MicroPython driver for Adafruit 0.96" OLED display 2 | # https://www.adafruit.com/product/684 3 | 4 | # The MIT License (MIT) 5 | 6 | # Copyright (c) 2018 Peter Hinch 7 | 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | 26 | # Show command 27 | # 0x15, 0, 0x5f, 0x75, 0, 0x3f Col 0-95 row 0-63 28 | 29 | # Initialisation command 30 | # 0xae display off (sleep mode) 31 | # 0xa0, 0x32 256 color RGB, horizontal RAM increment 32 | # 0xa1, 0x00 Startline row 0 33 | # 0xa2, 0x00 Vertical offset 0 34 | # 0xa4 Normal display 35 | # 0xa8, 0x3f Set multiplex ratio 36 | # 0xad, 0x8e Ext supply 37 | # 0xb0, 0x0b Disable power save mode 38 | # 0xb1, 0x31 Phase period 39 | # 0xb3, 0xf0 Oscillator frequency 40 | # 0x8a, 0x64, 0x8b, 0x78, 0x8c, 0x64, # Precharge 41 | # 0xbb, 0x3a Precharge voltge 42 | # 0xbe, 0x3e COM deselect level 43 | # 0x87, 0x06 master current attenuation factor 44 | # 0x81, 0x91 contrast for all color "A" segment 45 | # 0x82, 0x50 contrast for all color "B" segment 46 | # 0x83, 0x7d contrast for all color "C" segment 47 | # 0xaf Display on 48 | 49 | import framebuf 50 | import utime 51 | import gc 52 | 53 | class SSD1331(framebuf.FrameBuffer): 54 | # Convert r, g, b in range 0-255 to an 8 bit colour value 55 | # acceptable to hardware: rrrgggbb 56 | @staticmethod 57 | def rgb(r, g, b): 58 | return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6) 59 | 60 | def __init__(self, spi, pincs, pindc, pinrs, height=64, width=96): 61 | self.spi = spi 62 | self.rate = 6660000 # Data sheet: 150ns min clock period 63 | self.pincs = pincs 64 | self.pindc = pindc # 1 = data 0 = cmd 65 | self.height = height # Required by Writer class 66 | self.width = width 67 | # Save color mode for use by writer_gui (blit) 68 | self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. 69 | gc.collect() 70 | self.buffer = bytearray(self.height * self.width) 71 | super().__init__(self.buffer, self.width, self.height, self.mode) 72 | pinrs(0) # Pulse the reset line 73 | utime.sleep_ms(1) 74 | pinrs(1) 75 | utime.sleep_ms(1) 76 | self._write(b'\xae\xa0\x32\xa1\x00\xa2\x00\xa4\xa8\x3f\xad\x8e\xb0'\ 77 | b'\x0b\xb1\x31\xb3\xf0\x8a\x64\x8b\x78\x8c\x64\xbb\x3a\xbe\x3e\x87'\ 78 | b'\x06\x81\x91\x82\x50\x83\x7d\xaf', 0) 79 | gc.collect() 80 | self.show() 81 | 82 | def _write(self, buf, dc): 83 | self.spi.init(baudrate=self.rate, polarity=1, phase=1) 84 | self.pincs(1) 85 | self.pindc(dc) 86 | self.pincs(0) 87 | self.spi.write(buf) 88 | self.pincs(1) 89 | 90 | def show(self, _cmd=b'\x15\x00\x5f\x75\x00\x3f'): # Pre-allocate 91 | self._write(_cmd, 0) 92 | self._write(self.buffer, 1) 93 | --------------------------------------------------------------------------------