├── lib ├── adafruit_io │ ├── __init__.py │ ├── adafruit_io.mpy │ └── adafruit_io_errors.mpy ├── adafruit_bitmap_font │ ├── __init__.py │ ├── bdf.mpy │ ├── pcf.mpy │ ├── ttf.mpy │ ├── bitmap_font.mpy │ └── glyph_cache.mpy ├── adafruit_bus_device │ ├── __init__.py │ ├── i2c_device.mpy │ └── spi_device.mpy ├── adafruit_display_text │ ├── __init__.py │ ├── label.mpy │ └── bitmap_label.mpy ├── neopixel.mpy ├── adafruit_lis3dh.mpy ├── adafruit_requests.mpy ├── adafruit_esp32spi │ ├── PWMOut.mpy │ ├── digitalio.mpy │ ├── adafruit_esp32spi.mpy │ ├── adafruit_esp32spi_socket.mpy │ ├── adafruit_esp32spi_wifimanager.mpy │ └── adafruit_esp32spi_wsgiserver.mpy ├── adafruit_matrixportal │ ├── graphics.mpy │ ├── matrix.mpy │ ├── fakerequests.mpy │ ├── matrixportal.mpy │ ├── wifi.py │ └── network.py ├── adafruit_display_shapes │ ├── line.py │ ├── circle.py │ ├── rect.py │ ├── polygon.py │ ├── triangle.py │ ├── roundrect.py │ └── sparkline.py ├── adafruit_displayio_sh1107.py ├── adafruit_displayio_ssd1306.py └── adafruit_displayio_ssd1305.py ├── ImgForReadme ├── IMG_1329.jpg └── IMG_1330.jpg ├── nightscout_secrets.py ├── LICENSE ├── README.md ├── .gitignore └── main.py /lib/adafruit_io/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/adafruit_bitmap_font/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/adafruit_bus_device/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/adafruit_display_text/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/neopixel.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/neopixel.mpy -------------------------------------------------------------------------------- /lib/adafruit_lis3dh.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_lis3dh.mpy -------------------------------------------------------------------------------- /ImgForReadme/IMG_1329.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/ImgForReadme/IMG_1329.jpg -------------------------------------------------------------------------------- /ImgForReadme/IMG_1330.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/ImgForReadme/IMG_1330.jpg -------------------------------------------------------------------------------- /lib/adafruit_requests.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_requests.mpy -------------------------------------------------------------------------------- /lib/adafruit_bitmap_font/bdf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_bitmap_font/bdf.mpy -------------------------------------------------------------------------------- /lib/adafruit_bitmap_font/pcf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_bitmap_font/pcf.mpy -------------------------------------------------------------------------------- /lib/adafruit_bitmap_font/ttf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_bitmap_font/ttf.mpy -------------------------------------------------------------------------------- /lib/adafruit_esp32spi/PWMOut.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_esp32spi/PWMOut.mpy -------------------------------------------------------------------------------- /lib/adafruit_io/adafruit_io.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_io/adafruit_io.mpy -------------------------------------------------------------------------------- /lib/adafruit_display_text/label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_display_text/label.mpy -------------------------------------------------------------------------------- /lib/adafruit_esp32spi/digitalio.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_esp32spi/digitalio.mpy -------------------------------------------------------------------------------- /lib/adafruit_bus_device/i2c_device.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_bus_device/i2c_device.mpy -------------------------------------------------------------------------------- /lib/adafruit_bus_device/spi_device.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_bus_device/spi_device.mpy -------------------------------------------------------------------------------- /lib/adafruit_io/adafruit_io_errors.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_io/adafruit_io_errors.mpy -------------------------------------------------------------------------------- /lib/adafruit_matrixportal/graphics.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_matrixportal/graphics.mpy -------------------------------------------------------------------------------- /lib/adafruit_matrixportal/matrix.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_matrixportal/matrix.mpy -------------------------------------------------------------------------------- /lib/adafruit_bitmap_font/bitmap_font.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_bitmap_font/bitmap_font.mpy -------------------------------------------------------------------------------- /lib/adafruit_bitmap_font/glyph_cache.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_bitmap_font/glyph_cache.mpy -------------------------------------------------------------------------------- /lib/adafruit_display_text/bitmap_label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_display_text/bitmap_label.mpy -------------------------------------------------------------------------------- /lib/adafruit_esp32spi/adafruit_esp32spi.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_esp32spi/adafruit_esp32spi.mpy -------------------------------------------------------------------------------- /lib/adafruit_matrixportal/fakerequests.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_matrixportal/fakerequests.mpy -------------------------------------------------------------------------------- /lib/adafruit_matrixportal/matrixportal.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_matrixportal/matrixportal.mpy -------------------------------------------------------------------------------- /lib/adafruit_esp32spi/adafruit_esp32spi_socket.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_esp32spi/adafruit_esp32spi_socket.mpy -------------------------------------------------------------------------------- /lib/adafruit_esp32spi/adafruit_esp32spi_wifimanager.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_esp32spi/adafruit_esp32spi_wifimanager.mpy -------------------------------------------------------------------------------- /lib/adafruit_esp32spi/adafruit_esp32spi_wsgiserver.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/AdaFruitM4MatrixWithNightScout/main/lib/adafruit_esp32spi/adafruit_esp32spi_wsgiserver.mpy -------------------------------------------------------------------------------- /nightscout_secrets.py: -------------------------------------------------------------------------------- 1 | secrets = { 2 | 'ssid' : 'YOURSSID', 3 | 'password' : 'YOURPASSWORD', 4 | 'timezone' : "America/Los_Angeles", 5 | 'token' : "api-fromyournightscoutsite", 6 | 'nightscout' : "https://YOURNIGHTSCOUTSITE" 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Scott Hanselman 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 | # AdaFruitM4MatrixWithNightScout 2 | 3 | This project is designed to pull data from Nightscout and display your blood sugar levels in near real-time (every 5 min) on an [Adafruit M4 MatrixPortal Display](https://learn.adafruit.com/adafruit-matrixportal-m4/overview) 4 | 5 | ![MatrixPortal m4 with blood sugar](ImgForReadme/IMG_1330.jpg) 6 | 7 | ## Prerequisites 8 | 9 | To use this project, you will need the following: 10 | 11 | - An Adafruit M4 MatrixPortal Display 12 | - CircuitPython on the M4 (should be there by default) 13 | - A USB cable to connect your device to your computer 14 | 15 | ## Installation 16 | 17 | 1. Clone the code from this repository. 18 | 2. Connect your Adafruit M4 Matrix Display to your computer via USB. 19 | 3. Copy the code to the M4 device's disk drive. 20 | 4. Disconnect your device from your computer. 21 | 22 | ## Usage 23 | 24 | Once you have installed the code on your device, it will automatically pull data from Nightscout and display your blood sugar levels on the Adafruit M4 Matrix Display. 25 | 26 | Please note that this project requires CircuitPython to be installed on your device. If you do not have CircuitPython installed, please refer to the [CircuitPython Setup Guide](https://learn.adafruit.com/adafruit-matrixportal-m4/circuitpython-setup) for instructions. 27 | -------------------------------------------------------------------------------- /lib/adafruit_display_shapes/line.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Limor Fried 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 | `line` 24 | ================================================================================ 25 | 26 | Various common shapes for use with displayio - Line shape! 27 | 28 | 29 | * Author(s): Melissa LeBlanc-Williams 30 | 31 | Implementation Notes 32 | -------------------- 33 | 34 | **Software and Dependencies:** 35 | 36 | * Adafruit CircuitPython firmware for the supported boards: 37 | https://github.com/adafruit/circuitpython/releases 38 | 39 | """ 40 | 41 | from adafruit_display_shapes.polygon import Polygon 42 | 43 | __version__ = "2.0.1" 44 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git" 45 | 46 | 47 | class Line(Polygon): 48 | # pylint: disable=too-many-arguments,invalid-name, too-few-public-methods 49 | """A line. 50 | 51 | :param x0: The x-position of the first vertex. 52 | :param y0: The y-position of the first vertex. 53 | :param x1: The x-position of the second vertex. 54 | :param y1: The y-position of the second vertex. 55 | :param color: The color of the line. 56 | """ 57 | 58 | def __init__(self, x0, y0, x1, y1, color): 59 | super().__init__([(x0, y0), (x1, y1)], outline=color) 60 | -------------------------------------------------------------------------------- /lib/adafruit_display_shapes/circle.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Limor Fried 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 | `circle` 24 | ================================================================================ 25 | 26 | Various common shapes for use with displayio - Circle shape! 27 | 28 | 29 | * Author(s): Limor Fried 30 | 31 | Implementation Notes 32 | -------------------- 33 | 34 | **Software and Dependencies:** 35 | 36 | * Adafruit CircuitPython firmware for the supported boards: 37 | https://github.com/adafruit/circuitpython/releases 38 | 39 | """ 40 | 41 | from adafruit_display_shapes.roundrect import RoundRect 42 | 43 | __version__ = "2.0.1" 44 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git" 45 | 46 | 47 | class Circle(RoundRect): 48 | # pylint: disable=too-few-public-methods, invalid-name 49 | """A circle. 50 | 51 | :param x0: The x-position of the center. 52 | :param y0: The y-position of the center.. 53 | :param r: The radius of the circle. 54 | :param fill: The color to fill the rounded-corner rectangle. Can be a hex value for a color or 55 | ``None`` for transparent. 56 | :param outline: The outline of the rounded-corner rectangle. Can be a hex value for a color or 57 | ``None`` for no outline. 58 | :param stroke: Used for the outline. Will not change the radius. 59 | 60 | """ 61 | 62 | def __init__(self, x0, y0, r, *, fill=None, outline=None, stroke=1): 63 | super().__init__( 64 | x0 - r, 65 | y0 - r, 66 | 2 * r + 1, 67 | 2 * r + 1, 68 | r, 69 | fill=fill, 70 | outline=outline, 71 | stroke=stroke, 72 | ) 73 | -------------------------------------------------------------------------------- /lib/adafruit_displayio_sh1107.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # SPDX-FileCopyrightText: Copyright (c) 2020 Mark Roberts for Adafruit Industries 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | `adafruit_displayio_sh1107` 7 | ================================================================================ 8 | 9 | DisplayIO driver for SH1107 monochrome displays 10 | 11 | 12 | * Author(s): Scott Shawcroft, Mark Roberts (mdroberts1243) 13 | 14 | Implementation Notes 15 | -------------------- 16 | 17 | **Hardware:** 18 | 19 | * `Adafruit FeatherWing 128 x 64 OLED - SH1107 128x64 OLED `_ 20 | 21 | **Software and Dependencies:** 22 | 23 | * Adafruit CircuitPython (version 5+) firmware for the supported boards: 24 | https://github.com/adafruit/circuitpython/releases 25 | 26 | """ 27 | 28 | import displayio 29 | 30 | __version__ = "1.0.0" 31 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SH1107.git" 32 | 33 | # Sequence from sh1107 framebuf driver formatted for displayio init 34 | _INIT_SEQUENCE = ( 35 | b"\xae\x00" # display off, sleep mode 36 | b"\xdc\x01\x00" # display start line = 0 (POR = 0) 37 | b"\x81\x01\x2f" # contrast setting = 0x2f 38 | b"\x21\x00" # vertical (column) addressing mode (POR=0x20) 39 | b"\xa0\x00" # segment remap = 1 (POR=0, down rotation) 40 | b"\xcf\x00" # common output scan direction = 15 (0 to n-1 (POR=0)) 41 | b"\xa8\x01\x7f" # multiplex ratio = 128 (POR) 42 | b"\xd3\x01\x60" # set display offset mode = 0x60 43 | b"\xd5\x01\x51" # divide ratio/oscillator: divide by 2, fOsc (POR) 44 | b"\xd9\x01\x22" # pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR) 45 | b"\xdb\x01\x35" # VCOM deselect level = 0.770 (POR) 46 | b"\xb0\x00" # set page address = 0 (POR) 47 | b"\xa4\x00" # entire display off, retain RAM, normal status (POR) 48 | b"\xa6\x00" # normal (not reversed) display 49 | b"\xAF\x00\x00" # DISPLAY_ON 50 | ) 51 | 52 | 53 | # pylint: disable=too-few-public-methods 54 | class SH1107(displayio.Display): 55 | """SSD1107 driver""" 56 | 57 | def __init__(self, bus, **kwargs): 58 | init_sequence = bytearray(_INIT_SEQUENCE) 59 | super().__init__( 60 | bus, 61 | init_sequence, 62 | **kwargs, 63 | color_depth=1, 64 | grayscale=True, 65 | pixels_in_byte_share_row=True, # in vertical (column) mode 66 | data_as_commands=True, # every byte will have a command byte preceeding 67 | set_vertical_scroll=0xD3, # TBD -- not sure about this one! 68 | brightness_command=0x81, 69 | single_byte_bounds=True, 70 | # for sh1107 use column and page addressing. 71 | # lower column command = 0x00 - 0x0F 72 | # upper column command = 0x10 - 0x17 73 | # set page address = 0xB0 - 0xBF (16 pages) 74 | SH1107_addressing=True, 75 | ) 76 | -------------------------------------------------------------------------------- /lib/adafruit_matrixportal/wifi.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | """ 5 | `adafruit_matrixportal.wifi` 6 | ================================================================================ 7 | 8 | Helper library for the Adafruit RGB Matrix Shield + Metro M4 Airlift Lite. 9 | 10 | * Author(s): Melissa LeBlanc-Williams 11 | 12 | Implementation Notes 13 | -------------------- 14 | 15 | **Hardware:** 16 | 17 | * `Adafruit Metro M4 Express AirLift `_ 18 | * `Adafruit RGB Matrix Shield `_ 19 | * `64x32 RGB LED Matrix `_ 20 | 21 | **Software and Dependencies:** 22 | 23 | * Adafruit CircuitPython firmware for the supported boards: 24 | https://github.com/adafruit/circuitpython/releases 25 | 26 | """ 27 | 28 | import gc 29 | import board 30 | import busio 31 | from digitalio import DigitalInOut 32 | import neopixel 33 | from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager 34 | 35 | __version__ = "1.8.1" 36 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MatrixPortal.git" 37 | 38 | 39 | class WiFi: 40 | """Class representing the ESP. 41 | 42 | :param status_neopixel: The pin for the status NeoPixel. Use ``board.NEOPIXEL`` for the on-board 43 | NeoPixel. Defaults to ``None``, not the status LED 44 | :param esp: A passed ESP32 object, Can be used in cases where the ESP32 chip needs to be used 45 | before calling the pyportal class. Defaults to ``None``. 46 | :param busio.SPI external_spi: A previously declared spi object. Defaults to ``None``. 47 | 48 | """ 49 | 50 | def __init__(self, *, status_neopixel=None, esp=None, external_spi=None): 51 | 52 | if status_neopixel: 53 | self.neopix = neopixel.NeoPixel(status_neopixel, 1, brightness=0.2) 54 | else: 55 | self.neopix = None 56 | self.neo_status(0) 57 | 58 | if esp: # If there was a passed ESP Object 59 | self.esp = esp 60 | if external_spi: # If SPI Object Passed 61 | spi = external_spi 62 | else: # Else: Make ESP32 connection 63 | spi = busio.SPI(board.SCK, board.MOSI, board.MISO) 64 | else: 65 | esp32_ready = DigitalInOut(board.ESP_BUSY) 66 | esp32_gpio0 = DigitalInOut(board.ESP_GPIO0) 67 | esp32_reset = DigitalInOut(board.ESP_RESET) 68 | esp32_cs = DigitalInOut(board.ESP_CS) 69 | spi = busio.SPI(board.SCK, board.MOSI, board.MISO) 70 | 71 | self.esp = adafruit_esp32spi.ESP_SPIcontrol( 72 | spi, esp32_cs, esp32_ready, esp32_reset, esp32_gpio0 73 | ) 74 | 75 | self._manager = None 76 | 77 | gc.collect() 78 | 79 | def neo_status(self, value): 80 | """The status NeoPixel. 81 | 82 | :param value: The color to change the NeoPixel. 83 | 84 | """ 85 | if self.neopix: 86 | self.neopix.fill(value) 87 | 88 | def manager(self, secrets): 89 | """Initialize the WiFi Manager if it hasn't been cached and return it""" 90 | if self._manager is None: 91 | self._manager = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager( 92 | self.esp, secrets, None 93 | ) 94 | return self._manager 95 | -------------------------------------------------------------------------------- /.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 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /lib/adafruit_displayio_ssd1306.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Scott Shawcroft 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 | `adafruit_displayio_ssd1306` 24 | ================================================================================ 25 | 26 | DisplayIO driver for SSD1306 monochrome displays 27 | 28 | 29 | * Author(s): Scott Shawcroft 30 | 31 | Implementation Notes 32 | -------------------- 33 | 34 | **Hardware:** 35 | 36 | * `Monochrome 1.3" 128x64 OLED graphic display `_ 37 | * `Monochrome 128x32 I2C OLED graphic display `_ 38 | * `Monochrome 0.96" 128x64 OLED graphic display `_ 39 | * `Monochrome 128x32 SPI OLED graphic display `_ 40 | * `Adafruit FeatherWing OLED - 128x32 OLED `_ 41 | 42 | **Software and Dependencies:** 43 | 44 | * Adafruit CircuitPython (version 5+) firmware for the supported boards: 45 | https://github.com/adafruit/circuitpython/releases 46 | 47 | """ 48 | 49 | import displayio 50 | 51 | __version__ = "1.2.2" 52 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SSD1306.git" 53 | 54 | # Sequence from page 19 here: https://cdn-shop.adafruit.com/datasheets/UG-2864HSWEG01+user+guide.pdf 55 | _INIT_SEQUENCE = ( 56 | b"\xAE\x00" # DISPLAY_OFF 57 | b"\x20\x01\x00" # Set memory addressing to horizontal mode. 58 | b"\x81\x01\xcf" # set contrast control 59 | b"\xA1\x00" # Column 127 is segment 0 60 | b"\xA6\x00" # Normal display 61 | b"\xc8\x00" # Normal display 62 | b"\xA8\x01\x3f" # Mux ratio is 1/64 63 | b"\xd5\x01\x80" # Set divide ratio 64 | b"\xd9\x01\xf1" # Set pre-charge period 65 | b"\xda\x01\x12" # Set com configuration 66 | b"\xdb\x01\x40" # Set vcom configuration 67 | b"\x8d\x01\x14" # Enable charge pump 68 | b"\xAF\x00\x00" # DISPLAY_ON 69 | ) 70 | 71 | # pylint: disable=too-few-public-methods 72 | class SSD1306(displayio.Display): 73 | """SSD1306 driver""" 74 | 75 | def __init__(self, bus, **kwargs): 76 | # Patch the init sequence for 32 pixel high displays. 77 | init_sequence = bytearray(_INIT_SEQUENCE) 78 | height = kwargs["height"] 79 | if "rotation" in kwargs and kwargs["rotation"] % 180 != 0: 80 | height = kwargs["width"] 81 | init_sequence[16] = height - 1 # patch mux ratio 82 | if kwargs["height"] == 32: 83 | init_sequence[25] = 0x02 # patch com configuration 84 | super().__init__( 85 | bus, 86 | init_sequence, 87 | **kwargs, 88 | color_depth=1, 89 | grayscale=True, 90 | pixels_in_byte_share_row=False, 91 | set_column_command=0x21, 92 | set_row_command=0x22, 93 | data_as_commands=True, 94 | set_vertical_scroll=0xD3, 95 | brightness_command=0x81, 96 | single_byte_bounds=True, 97 | ) 98 | -------------------------------------------------------------------------------- /lib/adafruit_displayio_ssd1305.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Melissa LeBlanc-Williams 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 | `adafruit_displayio_ssd1305` 24 | ================================================================================ 25 | 26 | DisplayIO driver for SSD1305 monochrome displays 27 | 28 | 29 | * Author(s): Melissa LeBlanc-Williams 30 | 31 | Implementation Notes 32 | -------------------- 33 | 34 | **Hardware:** 35 | 36 | * `Monochrome 1.54" 128x64 OLED Graphic Display Module Kit `_ 37 | * `Monochrome 2.42" 128x64 OLED Graphic Display Module Kit `_ 38 | * `Monochrome 2.3" 128x32 OLED Graphic Display Module Kit `_ 39 | 40 | **Software and Dependencies:** 41 | 42 | * Adafruit CircuitPython (version 5+) firmware for the supported boards: 43 | https://github.com/adafruit/circuitpython/releases 44 | 45 | """ 46 | 47 | # imports 48 | 49 | import displayio 50 | 51 | __version__ = "1.1.1" 52 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SSD1305.git" 53 | 54 | _INIT_SEQUENCE = ( 55 | b"\xAE\x00" # DISPLAY_OFF 56 | b"\xd5\x01\x80" # SET_DISP_CLK_DIV 57 | b"\xA1\x00" # Column 127 is segment 0 58 | b"\xA8\x01\x3F" # Mux ratio is 1/64 59 | b"\xad\x01\x8e" # Set Master Configuration 60 | b"\xd8\x01\x05" # Set Area Color Mode On/Off & Low Power Display Mode 61 | b"\x20\x01\x00" # Set memory addressing to horizontal mode. 62 | b"\x40\x01\x2E" # SET_DISP_START_LINE ADD 63 | b"\xc8\x00" # Set COM Output Scan Direction 64 to 1 64 | b"\xda\x01\x12" # Set com configuration 65 | b"\x91\x04\x3f\x3f\x3f\x3f" # Current drive pulse width of BANK0, Color A, Band C. 66 | b"\xd9\x01\xd2" # Set pre-charge period orig: 0xd9, 0x22 if self.external_vcc else 0xf1, 67 | b"\xdb\x01\x34" # Set vcom configuration 0xdb, 0x30, $ 0.83* Vcc 68 | b"\xA6\x00" # Normal display 69 | b"\xA4\x00" # output follows RAM contents SET_ENTIRE_ON 70 | b"\x8d\x01\x14" # Enable charge pump 71 | b"\xAF\x00\x00" # DISPLAY_ON 72 | ) 73 | 74 | # pylint: disable=too-few-public-methods 75 | class SSD1305(displayio.Display): 76 | """SSD1305 driver""" 77 | 78 | def __init__(self, bus, **kwargs): 79 | colstart = 0 80 | # Patch the init sequence for 32 pixel high displays. 81 | init_sequence = bytearray(_INIT_SEQUENCE) 82 | 83 | height = kwargs["height"] 84 | if "rotation" in kwargs and kwargs["rotation"] % 180 != 0: 85 | height = kwargs["width"] 86 | init_sequence[9] = height - 1 # patch mux ratio 87 | 88 | if kwargs["height"] == 32: 89 | colstart = 4 90 | super().__init__( 91 | bus, 92 | init_sequence, 93 | **kwargs, 94 | color_depth=1, 95 | grayscale=True, 96 | pixels_in_byte_share_row=False, 97 | set_column_command=0x21, 98 | set_row_command=0x22, 99 | data_as_commands=True, 100 | set_vertical_scroll=0xD3, 101 | brightness_command=0x81, 102 | single_byte_bounds=True, 103 | colstart=colstart, 104 | ) 105 | -------------------------------------------------------------------------------- /lib/adafruit_display_shapes/rect.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Limor Fried 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 | `rect` 24 | ================================================================================ 25 | 26 | Various common shapes for use with displayio - Rectangle shape! 27 | 28 | 29 | * Author(s): Limor Fried 30 | 31 | Implementation Notes 32 | -------------------- 33 | 34 | **Software and Dependencies:** 35 | 36 | * Adafruit CircuitPython firmware for the supported boards: 37 | https://github.com/adafruit/circuitpython/releases 38 | 39 | """ 40 | 41 | import displayio 42 | 43 | __version__ = "2.0.1" 44 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git" 45 | 46 | 47 | class Rect(displayio.TileGrid): 48 | """A rectangle. 49 | 50 | :param x: The x-position of the top left corner. 51 | :param y: The y-position of the top left corner. 52 | :param width: The width of the rectangle. 53 | :param height: The height of the rectangle. 54 | :param fill: The color to fill the rectangle. Can be a hex value for a color or 55 | ``None`` for transparent. 56 | :param outline: The outline of the rectangle. Can be a hex value for a color or 57 | ``None`` for no outline. 58 | :param stroke: Used for the outline. Will not change the outer bound size set by ``width`` and 59 | ``height``. 60 | 61 | """ 62 | 63 | def __init__(self, x, y, width, height, *, fill=None, outline=None, stroke=1): 64 | self._bitmap = displayio.Bitmap(width, height, 2) 65 | self._palette = displayio.Palette(2) 66 | 67 | if outline is not None: 68 | self._palette[1] = outline 69 | for w in range(width): 70 | for line in range(stroke): 71 | self._bitmap[w, line] = 1 72 | self._bitmap[w, height - 1 - line] = 1 73 | for _h in range(height): 74 | for line in range(stroke): 75 | self._bitmap[line, _h] = 1 76 | self._bitmap[width - 1 - line, _h] = 1 77 | 78 | if fill is not None: 79 | self._palette[0] = fill 80 | self._palette.make_opaque(0) 81 | else: 82 | self._palette[0] = 0 83 | self._palette.make_transparent(0) 84 | super().__init__(self._bitmap, pixel_shader=self._palette, x=x, y=y) 85 | 86 | @property 87 | def fill(self): 88 | """The fill of the rectangle. Can be a hex value for a color or ``None`` for 89 | transparent.""" 90 | return self._palette[0] 91 | 92 | @fill.setter 93 | def fill(self, color): 94 | if color is None: 95 | self._palette[0] = 0 96 | self._palette.make_transparent(0) 97 | else: 98 | self._palette[0] = color 99 | self._palette.make_opaque(0) 100 | 101 | @property 102 | def outline(self): 103 | """The outline of the rectangle. Can be a hex value for a color or ``None`` 104 | for no outline.""" 105 | return self._palette[1] 106 | 107 | @outline.setter 108 | def outline(self, color): 109 | if color is None: 110 | self._palette[1] = 0 111 | self._palette.make_transparent(1) 112 | else: 113 | self._palette[1] = color 114 | self._palette.make_opaque(1) 115 | -------------------------------------------------------------------------------- /lib/adafruit_display_shapes/polygon.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Limor Fried 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 | `polygon` 24 | ================================================================================ 25 | 26 | Various common shapes for use with displayio - Polygon shape! 27 | 28 | 29 | * Author(s): Melissa LeBlanc-Williams 30 | 31 | Implementation Notes 32 | -------------------- 33 | 34 | **Software and Dependencies:** 35 | 36 | * Adafruit CircuitPython firmware for the supported boards: 37 | https://github.com/adafruit/circuitpython/releases 38 | 39 | """ 40 | 41 | import displayio 42 | 43 | __version__ = "2.0.1" 44 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git" 45 | 46 | 47 | class Polygon(displayio.TileGrid): 48 | # pylint: disable=too-many-arguments,invalid-name 49 | """A polygon. 50 | 51 | :param points: A list of (x, y) tuples of the points 52 | :param fill: The color to fill the polygon. Can be a hex value for a color or 53 | ``None`` for transparent. 54 | :param outline: The outline of the polygon. Can be a hex value for a color or 55 | ``None`` for no outline. 56 | """ 57 | 58 | def __init__(self, points, *, outline=None): 59 | xs = [] 60 | ys = [] 61 | 62 | for point in points: 63 | xs.append(point[0]) 64 | ys.append(point[1]) 65 | 66 | x_offset = min(xs) 67 | y_offset = min(ys) 68 | 69 | # Find the largest and smallest X values to figure out width for bitmap 70 | width = max(xs) - min(xs) + 1 71 | height = max(ys) - min(ys) + 1 72 | 73 | self._palette = displayio.Palette(3) 74 | self._palette.make_transparent(0) 75 | self._bitmap = displayio.Bitmap(width, height, 3) 76 | 77 | if outline is not None: 78 | # print("outline") 79 | self.outline = outline 80 | for index, _ in enumerate(points): 81 | point_a = points[index] 82 | if index == len(points) - 1: 83 | point_b = points[0] 84 | else: 85 | point_b = points[index + 1] 86 | self._line( 87 | point_a[0] - x_offset, 88 | point_a[1] - y_offset, 89 | point_b[0] - x_offset, 90 | point_b[1] - y_offset, 91 | 1, 92 | ) 93 | 94 | super().__init__( 95 | self._bitmap, pixel_shader=self._palette, x=x_offset, y=y_offset 96 | ) 97 | 98 | # pylint: disable=invalid-name, too-many-locals, too-many-branches 99 | def _line(self, x0, y0, x1, y1, color): 100 | if x0 == x1: 101 | if y0 > y1: 102 | y0, y1 = y1, y0 103 | for _h in range(y0, y1 + 1): 104 | self._bitmap[x0, _h] = color 105 | elif y0 == y1: 106 | if x0 > x1: 107 | x0, x1 = x1, x0 108 | for _w in range(x0, x1 + 1): 109 | self._bitmap[_w, y0] = color 110 | else: 111 | steep = abs(y1 - y0) > abs(x1 - x0) 112 | if steep: 113 | x0, y0 = y0, x0 114 | x1, y1 = y1, x1 115 | 116 | if x0 > x1: 117 | x0, x1 = x1, x0 118 | y0, y1 = y1, y0 119 | 120 | dx = x1 - x0 121 | dy = abs(y1 - y0) 122 | 123 | err = dx / 2 124 | 125 | if y0 < y1: 126 | ystep = 1 127 | else: 128 | ystep = -1 129 | 130 | for x in range(x0, x1): 131 | if steep: 132 | self._bitmap[y0, x] = color 133 | else: 134 | self._bitmap[x, y0] = color 135 | err -= dy 136 | if err < 0: 137 | y0 += ystep 138 | err += dx 139 | 140 | # pylint: enable=invalid-name, too-many-locals, too-many-branches 141 | 142 | @property 143 | def outline(self): 144 | """The outline of the polygon. Can be a hex value for a color or 145 | ``None`` for no outline.""" 146 | return self._palette[1] 147 | 148 | @outline.setter 149 | def outline(self, color): 150 | if color is None: 151 | self._palette[1] = 0 152 | self._palette.make_transparent(1) 153 | else: 154 | self._palette[1] = color 155 | self._palette.make_opaque(1) 156 | -------------------------------------------------------------------------------- /lib/adafruit_display_shapes/triangle.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Limor Fried 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 | `triangle` 24 | ================================================================================ 25 | 26 | Various common shapes for use with displayio - Triangle shape! 27 | 28 | 29 | * Author(s): Melissa LeBlanc-Williams 30 | 31 | Implementation Notes 32 | -------------------- 33 | 34 | **Software and Dependencies:** 35 | 36 | * Adafruit CircuitPython firmware for the supported boards: 37 | https://github.com/adafruit/circuitpython/releases 38 | 39 | """ 40 | 41 | from adafruit_display_shapes.polygon import Polygon 42 | 43 | __version__ = "2.0.1" 44 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git" 45 | 46 | 47 | class Triangle(Polygon): 48 | # pylint: disable=too-many-arguments,invalid-name 49 | """A triangle. 50 | 51 | :param x0: The x-position of the first vertex. 52 | :param y0: The y-position of the first vertex. 53 | :param x1: The x-position of the second vertex. 54 | :param y1: The y-position of the second vertex. 55 | :param x2: The x-position of the third vertex. 56 | :param y2: The y-position of the third vertex. 57 | :param fill: The color to fill the triangle. Can be a hex value for a color or 58 | ``None`` for transparent. 59 | :param outline: The outline of the triangle. Can be a hex value for a color or 60 | ``None`` for no outline. 61 | """ 62 | # pylint: disable=too-many-locals 63 | def __init__(self, x0, y0, x1, y1, x2, y2, *, fill=None, outline=None): 64 | # Sort coordinates by Y order (y2 >= y1 >= y0) 65 | if y0 > y1: 66 | y0, y1 = y1, y0 67 | x0, x1 = x1, x0 68 | 69 | if y1 > y2: 70 | y1, y2 = y2, y1 71 | x1, x2 = x2, x1 72 | 73 | if y0 > y1: 74 | y0, y1 = y1, y0 75 | x0, x1 = x1, x0 76 | 77 | # Find the largest and smallest X values to figure out width for bitmap 78 | xs = [x0, x1, x2] 79 | points = [(x0, y0), (x1, y1), (x2, y2)] 80 | 81 | # Initialize the bitmap and palette 82 | super().__init__(points) 83 | 84 | if fill is not None: 85 | self._draw_filled( 86 | x0 - min(xs), 0, x1 - min(xs), y1 - y0, x2 - min(xs), y2 - y0 87 | ) 88 | self.fill = fill 89 | else: 90 | self.fill = None 91 | 92 | if outline is not None: 93 | self.outline = outline 94 | for index, _ in enumerate(points): 95 | point_a = points[index] 96 | if index == len(points) - 1: 97 | point_b = points[0] 98 | else: 99 | point_b = points[index + 1] 100 | self._line( 101 | point_a[0] - min(xs), 102 | point_a[1] - y0, 103 | point_b[0] - min(xs), 104 | point_b[1] - y0, 105 | 1, 106 | ) 107 | 108 | # pylint: disable=invalid-name, too-many-branches 109 | def _draw_filled(self, x0, y0, x1, y1, x2, y2): 110 | if y0 == y2: # Handle awkward all-on-same-line case as its own thing 111 | a = x0 112 | b = x0 113 | if x1 < a: 114 | a = x1 115 | elif x1 > b: 116 | b = x1 117 | 118 | if x2 < a: 119 | a = x2 120 | elif x2 > b: 121 | b = x2 122 | self._line(a, y0, b, y0, 2) 123 | return 124 | 125 | if y1 == y2: 126 | last = y1 # Include y1 scanline 127 | else: 128 | last = y1 - 1 # Skip it 129 | 130 | # Upper Triangle 131 | for y in range(y0, last + 1): 132 | a = round(x0 + (x1 - x0) * (y - y0) / (y1 - y0)) 133 | b = round(x0 + (x2 - x0) * (y - y0) / (y2 - y0)) 134 | if a > b: 135 | a, b = b, a 136 | self._line(a, y, b, y, 2) 137 | # Lower Triangle 138 | for y in range(last + 1, y2 + 1): 139 | a = round(x1 + (x2 - x1) * (y - y1) / (y2 - y1)) 140 | b = round(x0 + (x2 - x0) * (y - y0) / (y2 - y0)) 141 | 142 | if a > b: 143 | a, b = b, a 144 | self._line(a, y, b, y, 2) 145 | 146 | # pylint: enable=invalid-name, too-many-locals, too-many-branches 147 | 148 | @property 149 | def fill(self): 150 | """The fill of the triangle. Can be a hex value for a color or 151 | ``None`` for transparent.""" 152 | return self._palette[2] 153 | 154 | @fill.setter 155 | def fill(self, color): 156 | if color is None: 157 | self._palette[2] = 0 158 | self._palette.make_transparent(2) 159 | else: 160 | self._palette[2] = color 161 | self._palette.make_opaque(2) 162 | -------------------------------------------------------------------------------- /lib/adafruit_display_shapes/roundrect.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2019 Limor Fried 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 | `roundrect` 24 | ================================================================================ 25 | 26 | A slightly modified version of Adafruit_CircuitPython_Display_Shapes that includes 27 | an explicit call to palette.make_opaque() in the fill color setter function. 28 | 29 | """ 30 | 31 | import displayio 32 | 33 | __version__ = "2.0.1" 34 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git" 35 | 36 | 37 | class RoundRect(displayio.TileGrid): 38 | # pylint: disable=too-many-arguments 39 | """A round-corner rectangle. 40 | 41 | :param x: The x-position of the top left corner. 42 | :param y: The y-position of the top left corner. 43 | :param width: The width of the rounded-corner rectangle. 44 | :param height: The height of the rounded-corner rectangle. 45 | :param r: The radius of the rounded corner. 46 | :param fill: The color to fill the rounded-corner rectangle. Can be a hex value for a color or 47 | ``None`` for transparent. 48 | :param outline: The outline of the rounded-corner rectangle. Can be a hex value for a color or 49 | ``None`` for no outline. 50 | :param stroke: Used for the outline. Will not change the outer bound size set by ``width`` and 51 | ``height``. 52 | 53 | """ 54 | 55 | def __init__(self, x, y, width, height, r, *, fill=None, outline=None, stroke=1): 56 | self._palette = displayio.Palette(3) 57 | self._palette.make_transparent(0) 58 | self._bitmap = displayio.Bitmap(width, height, 3) 59 | for i in range(0, width): # draw the center chunk 60 | for j in range(r, height - r): # draw the center chunk 61 | self._bitmap[i, j] = 2 62 | self._helper( 63 | r, 64 | r, 65 | r, 66 | color=2, 67 | fill=True, 68 | x_offset=width - 2 * r - 1, 69 | y_offset=height - 2 * r - 1, 70 | ) 71 | 72 | if fill is not None: 73 | self._palette[2] = fill 74 | self._palette.make_opaque(2) 75 | else: 76 | self._palette.make_transparent(2) 77 | self._palette[2] = 0 78 | 79 | if outline is not None: 80 | self._palette[1] = outline 81 | # draw flat sides 82 | for w in range(r, width - r): 83 | for line in range(stroke): 84 | self._bitmap[w, line] = 1 85 | self._bitmap[w, height - line - 1] = 1 86 | for _h in range(r, height - r): 87 | for line in range(stroke): 88 | self._bitmap[line, _h] = 1 89 | self._bitmap[width - line - 1, _h] = 1 90 | # draw round corners 91 | self._helper( 92 | r, 93 | r, 94 | r, 95 | color=1, 96 | stroke=stroke, 97 | x_offset=width - 2 * r - 1, 98 | y_offset=height - 2 * r - 1, 99 | ) 100 | super().__init__(self._bitmap, pixel_shader=self._palette, x=x, y=y) 101 | 102 | # pylint: disable=invalid-name, too-many-locals, too-many-branches 103 | def _helper( 104 | self, 105 | x0, 106 | y0, 107 | r, 108 | *, 109 | color, 110 | x_offset=0, 111 | y_offset=0, 112 | stroke=1, 113 | corner_flags=0xF, 114 | fill=False 115 | ): 116 | f = 1 - r 117 | ddF_x = 1 118 | ddF_y = -2 * r 119 | x = 0 120 | y = r 121 | 122 | while x < y: 123 | if f >= 0: 124 | y -= 1 125 | ddF_y += 2 126 | f += ddF_y 127 | x += 1 128 | ddF_x += 2 129 | f += ddF_x 130 | if corner_flags & 0x8: 131 | if fill: 132 | for w in range(x0 - y, x0 + y + x_offset): 133 | self._bitmap[w, y0 + x + y_offset] = color 134 | for w in range(x0 - x, x0 + x + x_offset): 135 | self._bitmap[w, y0 + y + y_offset] = color 136 | else: 137 | for line in range(stroke): 138 | self._bitmap[x0 - y + line, y0 + x + y_offset] = color 139 | self._bitmap[x0 - x, y0 + y + y_offset - line] = color 140 | if corner_flags & 0x1: 141 | if fill: 142 | for w in range(x0 - y, x0 + y + x_offset): 143 | self._bitmap[w, y0 - x] = color 144 | for w in range(x0 - x, x0 + x + x_offset): 145 | self._bitmap[w, y0 - y] = color 146 | else: 147 | for line in range(stroke): 148 | self._bitmap[x0 - y + line, y0 - x] = color 149 | self._bitmap[x0 - x, y0 - y + line] = color 150 | if corner_flags & 0x4: 151 | for line in range(stroke): 152 | self._bitmap[x0 + x + x_offset, y0 + y + y_offset - line] = color 153 | self._bitmap[x0 + y + x_offset - line, y0 + x + y_offset] = color 154 | if corner_flags & 0x2: 155 | for line in range(stroke): 156 | self._bitmap[x0 + x + x_offset, y0 - y + line] = color 157 | self._bitmap[x0 + y + x_offset - line, y0 - x] = color 158 | 159 | # pylint: enable=invalid-name, too-many-locals, too-many-branches 160 | 161 | @property 162 | def fill(self): 163 | """The fill of the rounded-corner rectangle. Can be a hex value for a color or ``None`` for 164 | transparent.""" 165 | return self._palette[2] 166 | 167 | @fill.setter 168 | def fill(self, color): 169 | if color is None: 170 | self._palette[2] = 0 171 | self._palette.make_transparent(2) 172 | else: 173 | self._palette[2] = color 174 | self._palette.make_opaque(2) 175 | 176 | @property 177 | def outline(self): 178 | """The outline of the rounded-corner rectangle. Can be a hex value for a color or ``None`` 179 | for no outline.""" 180 | return self._palette[1] 181 | 182 | @outline.setter 183 | def outline(self, color): 184 | if color is None: 185 | self._palette[1] = 0 186 | self._palette.make_transparent(1) 187 | else: 188 | self._palette[1] = color 189 | self._palette.make_opaque(2) 190 | -------------------------------------------------------------------------------- /lib/adafruit_display_shapes/sparkline.py: -------------------------------------------------------------------------------- 1 | # class of sparklines in CircuitPython 2 | # created by Kevin Matocha - Copyright 2020 (C) 3 | 4 | # See the bottom for a code example using the `sparkline` Class. 5 | 6 | # # File: display_shapes_sparkline.py 7 | # A sparkline is a scrolling line graph, where any values added to sparkline using ` 8 | # add_value` are plotted. 9 | # 10 | # The `sparkline` class creates an element suitable for adding to the display using 11 | # `display.show(mySparkline)` 12 | # or adding to a `displayio.Group` to be displayed. 13 | # 14 | # When creating the sparkline, identify the number of `max_items` that will be 15 | # included in the graph. When additional elements are added to the sparkline and 16 | # the number of items has exceeded max_items, any excess values are removed from 17 | # the left of the graph, and new values are added to the right. 18 | """ 19 | `sparkline` 20 | ================================================================================ 21 | 22 | Various common shapes for use with displayio - Sparkline! 23 | 24 | 25 | * Author(s): Kevin Matocha 26 | 27 | Implementation Notes 28 | -------------------- 29 | 30 | **Software and Dependencies:** 31 | 32 | * Adafruit CircuitPython firmware for the supported boards: 33 | https://github.com/adafruit/circuitpython/releases 34 | 35 | """ 36 | 37 | import displayio 38 | from adafruit_display_shapes.line import Line 39 | 40 | 41 | class Sparkline(displayio.Group): 42 | # pylint: disable=too-many-arguments 43 | """A sparkline graph. 44 | 45 | :param width: Width of the sparkline graph in pixels 46 | :param height: Height of the sparkline graph in pixels 47 | :param max_items: Maximum number of values housed in the sparkline 48 | :param y_min: Lower range for the y-axis. Set to None for autorange. 49 | :param y_max: Upper range for the y-axis. Set to None for autorange. 50 | :param x: X-position on the screen, in pixels 51 | :param y: Y-position on the screen, in pixels 52 | :param color: Line color, the default value is 0xFFFFFF (WHITE) 53 | """ 54 | 55 | def __init__( 56 | self, 57 | width, 58 | height, 59 | max_items, 60 | y_min=None, # None = autoscaling 61 | y_max=None, # None = autoscaling 62 | x=0, 63 | y=0, 64 | color=0xFFFFFF, # line color, default is WHITE 65 | ): 66 | 67 | # define class instance variables 68 | self.width = width # in pixels 69 | self.height = height # in pixels 70 | self.color = color # 71 | self._max_items = max_items # maximum number of items in the list 72 | self._spark_list = [] # list containing the values 73 | self.y_min = y_min # minimum of y-axis (None: autoscale) 74 | self.y_max = y_max # maximum of y-axis (None: autoscale) 75 | self.y_bottom = y_min 76 | # y_bottom: The actual minimum value of the vertical scale, will be 77 | # updated if autorange 78 | self.y_top = y_max 79 | # y_top: The actual minimum value of the vertical scale, will be 80 | # updated if autorange 81 | self._x = x 82 | self._y = y 83 | 84 | super().__init__( 85 | max_size=self._max_items - 1, x=x, y=y 86 | ) # self is a group of lines 87 | 88 | def add_value(self, value): 89 | """Add a value to the sparkline. 90 | :param value: The value to be added to the sparkline 91 | """ 92 | 93 | if value is not None: 94 | if ( 95 | len(self._spark_list) >= self._max_items 96 | ): # if list is full, remove the first item 97 | self._spark_list.pop(0) 98 | self._spark_list.append(value) 99 | self.update() 100 | 101 | # pylint: disable=no-else-return 102 | @staticmethod 103 | def _xintercept( 104 | x_1, y_1, x_2, y_2, horizontal_y 105 | ): # finds intercept of the line and a horizontal line at horizontalY 106 | slope = (y_2 - y_1) / (x_2 - x_1) 107 | b = y_1 - slope * x_1 108 | 109 | if slope == 0 and y_1 != horizontal_y: # does not intercept horizontalY 110 | return None 111 | else: 112 | xint = ( 113 | horizontal_y - b 114 | ) / slope # calculate the x-intercept at position y=horizontalY 115 | return int(xint) 116 | 117 | def _plotline(self, x_1, last_value, x_2, value, y_bottom, y_top): 118 | 119 | y_2 = int(self.height * (y_top - value) / (y_top - y_bottom)) 120 | y_1 = int(self.height * (y_top - last_value) / (y_top - y_bottom)) 121 | self.append(Line(x_1, y_1, x_2, y_2, self.color)) # plot the line 122 | 123 | # pylint: disable= too-many-branches, too-many-nested-blocks 124 | 125 | def update(self): 126 | """Update the drawing of the sparkline.""" 127 | 128 | # get the y range 129 | if self.y_min is None: 130 | self.y_bottom = min(self._spark_list) 131 | else: 132 | self.y_bottom = self.y_min 133 | 134 | if self.y_max is None: 135 | self.y_top = max(self._spark_list) 136 | else: 137 | self.y_top = self.y_max 138 | 139 | if len(self._spark_list) > 2: 140 | xpitch = (self.width - 1) / ( 141 | len(self._spark_list) - 1 142 | ) # this is a float, only make int when plotting the line 143 | 144 | for _ in range(len(self)): # remove all items from the current group 145 | self.pop() 146 | 147 | for count, value in enumerate(self._spark_list): 148 | if count == 0: 149 | pass # don't draw anything for a first point 150 | else: 151 | x_2 = int(xpitch * count) 152 | x_1 = int(xpitch * (count - 1)) 153 | 154 | if (self.y_bottom <= last_value <= self.y_top) and ( 155 | self.y_bottom <= value <= self.y_top 156 | ): # both points are in range, plot the line 157 | self._plotline( 158 | x_1, last_value, x_2, value, self.y_bottom, self.y_top 159 | ) 160 | 161 | else: # at least one point is out of range, clip one or both ends the line 162 | if ((last_value > self.y_top) and (value > self.y_top)) or ( 163 | (last_value < self.y_bottom) and (value < self.y_bottom) 164 | ): 165 | # both points are on the same side out of range: don't draw anything 166 | pass 167 | else: 168 | xint_bottom = self._xintercept( 169 | x_1, last_value, x_2, value, self.y_bottom 170 | ) # get possible new x intercept points 171 | xint_top = self._xintercept( 172 | x_1, last_value, x_2, value, self.y_top 173 | ) # on the top and bottom of range 174 | 175 | if (xint_bottom is None) or ( 176 | xint_top is None 177 | ): # out of range doublecheck 178 | pass 179 | else: 180 | # Initialize the adjusted values as the baseline 181 | adj_x_1 = x_1 182 | adj_last_value = last_value 183 | adj_x_2 = x_2 184 | adj_value = value 185 | 186 | if value > last_value: # slope is positive 187 | if xint_bottom >= x_1: # bottom is clipped 188 | adj_x_1 = xint_bottom 189 | adj_last_value = self.y_bottom # y_1 190 | if xint_top <= x_2: # top is clipped 191 | adj_x_2 = xint_top 192 | adj_value = self.y_top # y_2 193 | else: # slope is negative 194 | if xint_top >= x_1: # top is clipped 195 | adj_x_1 = xint_top 196 | adj_last_value = self.y_top # y_1 197 | if xint_bottom <= x_2: # bottom is clipped 198 | adj_x_2 = xint_bottom 199 | adj_value = self.y_bottom # y_2 200 | 201 | self._plotline( 202 | adj_x_1, 203 | adj_last_value, 204 | adj_x_2, 205 | adj_value, 206 | self.y_bottom, 207 | self.y_top, 208 | ) 209 | 210 | last_value = value # store value for the next iteration 211 | 212 | def values(self): 213 | """Returns the values displayed on the sparkline.""" 214 | 215 | return self._spark_list 216 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Started from MoonPhase sample by Phil 'PaintYourDragon' Burgess for Adafruit Industries. 3 | MIT license, all text above must be included in any redistribution. 4 | BDF fonts from the X.Org project 5 | """ 6 | 7 | # pylint: disable=import-error 8 | import gc 9 | #from lib2to3.pgen2.token import GREATEREQUAL 10 | import time 11 | import math 12 | import json 13 | import board 14 | import busio 15 | import displayio 16 | from rtc import RTC 17 | from adafruit_matrixportal.network import Network 18 | from adafruit_matrixportal.matrix import Matrix 19 | from adafruit_bitmap_font import bitmap_font 20 | import adafruit_display_text.label 21 | import adafruit_lis3dh 22 | 23 | try: 24 | from secrets import secrets 25 | except ImportError: 26 | print('WiFi secrets are kept in secrets.py, please add them there!') 27 | raise 28 | 29 | # CONFIGURABLE SETTINGS ---------------------------------------------------- 30 | 31 | TWELVE_HOUR = True # If set, use 12-hour time vs 24-hour (e.g. 3:00 vs 15:00) 32 | BITPLANES = 2 # Ideally 6, but can set lower if RAM is tight 33 | 34 | RED = 0xFF0000 35 | GREEN = 0x00FF00 36 | YELLOW = 0xFFFF00 37 | 38 | UPARROW = '\u2191' 39 | DOWNARROW = '\u2193' 40 | RIGHTARROW = '\u2192' 41 | UPRIGHTARROW = '\u2197' 42 | DOWNRIGHTARROW = '\u2198' 43 | DOUBLEUPARROW = '\u21D1' 44 | DOUBLEDOWNARROW = '\u21D3' 45 | 46 | # SOME UTILITY FUNCTIONS AND CLASSES --------------------------------------- 47 | 48 | def parse_time(timestring, is_dst=-1): 49 | """ Given a string of the format YYYY-MM-DDTHH:MM:SS.SS-HH:MM (and 50 | optionally a DST flag), convert to and return an equivalent 51 | time.struct_time (strptime() isn't available here). Calling function 52 | can use time.mktime() on result if epoch seconds is needed instead. 53 | Time string is assumed local time; UTC offset is ignored. If seconds 54 | value includes a decimal fraction it's ignored. 55 | """ 56 | date_time = timestring.split('T') # Separate into date and time 57 | year_month_day = date_time[0].split('-') # Separate time into Y/M/D 58 | hour_minute_second = date_time[1].split('+')[0].split('-')[0].split(':') 59 | return time.struct_time(int(year_month_day[0]), 60 | int(year_month_day[1]), 61 | int(year_month_day[2]), 62 | int(hour_minute_second[0]), 63 | int(hour_minute_second[1]), 64 | int(hour_minute_second[2].split('.')[0]), 65 | -1, -1, is_dst) 66 | 67 | 68 | def update_time(timezone=None): 69 | """ Update system date/time from WorldTimeAPI public server; 70 | no account required. Pass in time zone string 71 | (http://worldtimeapi.org/api/timezone for list) 72 | or None to use IP geolocation. Returns current local time as a 73 | time.struct_time and UTC offset as string. This may throw an 74 | exception on fetch_data() - it is NOT CAUGHT HERE, should be 75 | handled in the calling code because different behaviors may be 76 | needed in different situations (e.g. reschedule for later). 77 | """ 78 | if timezone: # Use timezone api 79 | time_url = f'http://worldtimeapi.org/api/timezone/{timezone}' 80 | else: # Use IP geolocation 81 | time_url = 'http://worldtimeapi.org/api/ip' 82 | 83 | time_data = NETWORK.fetch_data(time_url, 84 | json_path=[['datetime'], ['dst'], 85 | ['utc_offset']]) 86 | time_struct = parse_time(time_data[0], time_data[1]) 87 | RTC().datetime = time_struct 88 | return time_struct, time_data[2] 89 | 90 | 91 | def hh_mm(time_struct): 92 | """ Given a time.struct_time, return a string as H:MM or HH:MM, either 93 | 12- or 24-hour style depending on global TWELVE_HOUR setting. 94 | This is ONLY for 'clock time,' NOT for countdown time, which is 95 | handled separately in the one spot where it's needed. 96 | """ 97 | if TWELVE_HOUR: 98 | if time_struct.tm_hour > 12: 99 | hour_string = str(time_struct.tm_hour - 12) # 13-23 -> 1-11 (pm) 100 | elif time_struct.tm_hour > 0: 101 | hour_string = str(time_struct.tm_hour) # 1-12 102 | else: 103 | hour_string = '12' # 0 -> 12 (am) 104 | else: 105 | hour_string = '{0:0>2}'.format(time_struct.tm_hour) 106 | return f'{hour_string}:' + '{0:0>2}'.format(time_struct.tm_min) 107 | 108 | # pylint: disable=too-few-public-methods 109 | class SugarData(): 110 | def __init__(self): 111 | url = f'{str(NIGHTSCOUT)}/api/v1/entries.json?count=5&token={str(TOKEN)}' 112 | print('Fetching sugar data via', url) 113 | 114 | # pylint: disable=bare-except 115 | for _ in range(1): # Retries 116 | try: 117 | full_data = json.loads(NETWORK.fetch_data(url)) 118 | self.sugarList = [] 119 | for entry in full_data: 120 | sugarDetails = { 121 | 'sgv': entry['sgv'], 122 | 'date': entry['date'], 123 | 'direction': entry['direction'], 124 | 'type': entry['type'], 125 | } 126 | self.sugarList.append(sugarDetails) 127 | 128 | print(f"DUMP: {self.sugarList}") 129 | return # Success! 130 | except Exception: 131 | # server error (maybe), try again after 15 seconds. 132 | time.sleep(15) 133 | 134 | # ONE-TIME INITIALIZATION -------------------------------------------------- 135 | 136 | MATRIX = Matrix(bit_depth=BITPLANES) 137 | DISPLAY = MATRIX.display 138 | ACCEL = adafruit_lis3dh.LIS3DH_I2C(busio.I2C(board.SCL, board.SDA), 139 | address=0x19) 140 | _ = ACCEL.acceleration # Dummy reading to blow out any startup residue 141 | time.sleep(0.1) 142 | # Rotate display depending on board orientation 143 | DISPLAY.rotation = (int(((math.atan2(-ACCEL.acceleration.y, 144 | -ACCEL.acceleration.x) + math.pi) / 145 | (math.pi * 2) + 0.875) * 4) % 4) * 90 146 | 147 | LARGE_FONT = bitmap_font.load_font('/fonts/helvB12.bdf') 148 | SMALL_FONT = bitmap_font.load_font('/fonts/helvR10.bdf') 149 | SYMBOL_FONT = bitmap_font.load_font('/fonts/6x10.bdf') 150 | LARGE_FONT.load_glyphs('0123456789:') 151 | SMALL_FONT.load_glyphs('0123456789:/.%') 152 | # include blood sugar specific glyphs 153 | SYMBOL_FONT.load_glyphs('0123456789.\u2191\u2193\u2192\u2197\u2198\u21D1\u21D3') 154 | 155 | # Display group is set up once, then we just shuffle items around later. 156 | # Order of creation here determines their stacking order. 157 | GROUP = displayio.Group(max_size=10) 158 | #except: 159 | GROUP.append(adafruit_display_text.label.Label(SYMBOL_FONT, color=GREEN, 160 | text='Loading...')) 161 | GROUP[0].x = (DISPLAY.width - GROUP[0].bounding_box[2] + 1) // 2 162 | GROUP[0].y = DISPLAY.height // 2 - 1 163 | 164 | # Elements 1-4 are an outline around the moon percentage -- text labels 165 | # offset by 1 pixel up/down/left/right. Initial position is off the matrix, 166 | # updated on first refresh. Initial text value must be long enough for 167 | # longest anticipated string later. 168 | # for i in range(4): 169 | # GROUP.append(adafruit_display_text.label.Label(SMALL_FONT, color=0, 170 | # text='99.9%', y=-99)) 171 | # # Element 5 is the moon percentage (on top of the outline labels) 172 | # GROUP.append(adafruit_display_text.label.Label(SMALL_FONT, color=0xFFFF00, 173 | # text='99.9%', y=-99)) 174 | # # Element 6 is the current time 175 | # GROUP.append(adafruit_display_text.label.Label(LARGE_FONT, color=0x808080, 176 | # text='12:00', y=-99)) 177 | # # Element 7 is the current date 178 | # GROUP.append(adafruit_display_text.label.Label(SMALL_FONT, color=0x808080, 179 | # text='12/31', y=-99)) 180 | # # Element 8 is a symbol indicating next rise or set 181 | # GROUP.append(adafruit_display_text.label.Label(SYMBOL_FONT, color=0x00FF00, 182 | # text='x', y=-99)) 183 | # # Element 9 is the time of (or time to) next rise/set event 184 | # GROUP.append(adafruit_display_text.label.Label(SMALL_FONT, color=0x00FF00, 185 | # text='12:00', y=-99)) 186 | DISPLAY.show(GROUP) 187 | 188 | NETWORK = Network(status_neopixel=board.NEOPIXEL, debug=True) 189 | NETWORK.connect() 190 | 191 | try: 192 | TIMEZONE = secrets['timezone'] # e.g. 'America/New_York' 193 | except Exception: 194 | TIMEZONE = None 195 | 196 | try: 197 | TOKEN = secrets['token'] 198 | except Exception: 199 | TOKEN = None 200 | 201 | try: 202 | NIGHTSCOUT = secrets['nightscout'] 203 | except Exception: 204 | NIGHTSCOUT = None 205 | 206 | # pylint: disable=bare-except 207 | try: 208 | DATETIME, UTC_OFFSET = update_time(TIMEZONE) 209 | except Exception: 210 | DATETIME, UTC_OFFSET = time.localtime(), '+00:00' 211 | LAST_SYNC = time.mktime(DATETIME) 212 | 213 | # MAIN LOOP ---------------------------------------------------------------- 214 | while True: 215 | gc.collect() 216 | NOW = time.time() # Current epoch time in seconds 217 | 218 | # Sync with time server every ~5 min 219 | if NOW - LAST_SYNC > 5 * 60: 220 | try: 221 | DATETIME, UTC_OFFSET = update_time(TIMEZONE) 222 | LAST_SYNC = time.mktime(DATETIME) 223 | continue # Time may have changed; refresh NOW value 224 | except Exception: 225 | # try again in a minute 226 | LAST_SYNC += 1 * 60 227 | 228 | SUGAR = SugarData() 229 | 230 | 231 | #TILE_GRID.x = 0 232 | #TILE_GRID.y = MOON_Y 233 | #GROUP[0] = TILE_GRID 234 | 235 | # Update percent value (5 labels: GROUP[1-4] for outline, [5] for text) 236 | # if PERCENT >= 99.95: 237 | # STRING = '100%' 238 | # else: 239 | # STRING = '{:.1f}'.format(PERCENT + 0.05) + '%' 240 | #print(NOW, "test", 'full') 241 | # Set element 5 first, use its size and position for setting others 242 | #GROUP[0].text = SUGAR[0].sugar_text 243 | #GROUP[0].color_index = GREEN 244 | 245 | 246 | 247 | TEXTCOLOR = GREEN 248 | 249 | CURRENTSUGAR = SUGAR.sugarList[0]["sgv"] 250 | 251 | if CURRENTSUGAR > 200: 252 | TEXTCOLOR = RED 253 | elif CURRENTSUGAR > 150: 254 | TEXTCOLOR = YELLOW 255 | elif CURRENTSUGAR < 60: 256 | TEXTCOLOR = RED 257 | 258 | CURRENTDIRECTION = SUGAR.sugarList[0]["direction"] 259 | 260 | if CURRENTDIRECTION == "Flat": 261 | TEXTDIRECTION = RIGHTARROW 262 | elif CURRENTDIRECTION == "FortyFiveUp": 263 | TEXTDIRECTION = UPRIGHTARROW 264 | elif CURRENTDIRECTION == "FortyFiveDown": 265 | TEXTDIRECTION = DOWNRIGHTARROW 266 | elif CURRENTDIRECTION == "SingleUp": 267 | TEXTDIRECTION = UPARROW 268 | elif CURRENTDIRECTION == "SingleDown": 269 | TEXTDIRECTION = DOWNARROW 270 | elif CURRENTDIRECTION == "DoubleUp": 271 | TEXTDIRECTION = DOUBLEUPARROW 272 | elif CURRENTDIRECTION == "DoubleDown": 273 | TEXTDIRECTION = DOUBLEDOWNARROW 274 | 275 | GROUP[0].color_index = TEXTCOLOR 276 | GROUP[0].text = str(SUGAR.sugarList[0]["sgv"]) + " " + TEXTDIRECTION 277 | GROUP[0].x = (DISPLAY.width - GROUP[0].bounding_box[2] + 1) // 2 278 | GROUP[0].y = DISPLAY.height // 2 - 1 279 | 280 | #GROUP[0].text = SUGAR[0].sugar_text 281 | #print(SUGAR[0].sgv) 282 | 283 | # GROUP[5].text = STRING 284 | # GROUP[5].x = 16 - GROUP[5].bounding_box[2] // 2 285 | # GROUP[5].y = MOON_Y + 16 286 | # for _ in range(1, 5): 287 | # GROUP[_].text = GROUP[5].text 288 | # GROUP[1].x, GROUP[1].y = GROUP[5].x, GROUP[5].y - 1 # Up 1 pixel 289 | # GROUP[2].x, GROUP[2].y = GROUP[5].x - 1, GROUP[5].y # Left 290 | # GROUP[3].x, GROUP[3].y = GROUP[5].x + 1, GROUP[5].y # Right 291 | # GROUP[4].x, GROUP[4].y = GROUP[5].x, GROUP[5].y + 1 # Down 292 | 293 | DISPLAY.refresh() # Force full repaint (splash screen sometimes sticks) 294 | time.sleep(5) 295 | -------------------------------------------------------------------------------- /lib/adafruit_matrixportal/network.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | """ 5 | `adafruit_matrixportal.network` 6 | ================================================================================ 7 | 8 | Helper library for the Adafruit RGB Matrix Shield + Metro M4 Airlift Lite. 9 | 10 | * Author(s): Melissa LeBlanc-Williams 11 | 12 | Implementation Notes 13 | -------------------- 14 | 15 | **Hardware:** 16 | 17 | * `Adafruit Metro M4 Express AirLift `_ 18 | * `Adafruit RGB Matrix Shield `_ 19 | * `64x32 RGB LED Matrix `_ 20 | 21 | **Software and Dependencies:** 22 | 23 | * Adafruit CircuitPython firmware for the supported boards: 24 | https://github.com/adafruit/circuitpython/releases 25 | 26 | """ 27 | 28 | import os 29 | import time 30 | import gc 31 | import adafruit_esp32spi.adafruit_esp32spi_socket as socket 32 | from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError 33 | import adafruit_requests as requests 34 | import supervisor 35 | import rtc 36 | from adafruit_matrixportal.wifi import WiFi 37 | from adafruit_matrixportal.fakerequests import Fake_Requests 38 | 39 | try: 40 | from secrets import secrets 41 | except ImportError: 42 | print( 43 | """WiFi settings are kept in secrets.py, please add them there! 44 | the secrets dictionary must contain 'ssid' and 'password' at a minimum""" 45 | ) 46 | raise 47 | 48 | __version__ = "1.8.1" 49 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MatrixPortal.git" 50 | 51 | # pylint: disable=line-too-long 52 | # pylint: disable=too-many-lines 53 | # you'll need to pass in an io username and key 54 | TIME_SERVICE = ( 55 | "https://io.adafruit.com/api/v2/%s/integrations/time/strftime?x-aio-key=%s" 56 | ) 57 | # our strftime is %Y-%m-%d %H:%M:%S.%L %j %u %z %Z see http://strftime.net/ for decoding details 58 | # See https://apidock.com/ruby/DateTime/strftime for full options 59 | TIME_SERVICE_STRFTIME = ( 60 | "&fmt=%25Y-%25m-%25d+%25H%3A%25M%3A%25S.%25L+%25j+%25u+%25z+%25Z" 61 | ) 62 | LOCALFILE = "local.txt" 63 | # pylint: enable=line-too-long 64 | 65 | STATUS_NO_CONNECTION = (100, 0, 0) 66 | STATUS_CONNECTING = (0, 0, 100) 67 | STATUS_FETCHING = (200, 100, 0) 68 | STATUS_DOWNLOADING = (0, 100, 100) 69 | STATUS_CONNECTED = (0, 100, 0) 70 | STATUS_DATA_RECEIVED = (0, 0, 100) 71 | STATUS_OFF = (0, 0, 0) 72 | 73 | 74 | class Network: 75 | """Class representing the Adafruit RGB Matrix Portal. 76 | 77 | :param status_neopixel: The pin for the status NeoPixel. Use ``board.NEOPIXEL`` for the on-board 78 | NeoPixel. Defaults to ``None``, not the status LED 79 | :param esp: A passed ESP32 object, Can be used in cases where the ESP32 chip needs to be used 80 | before calling the pyportal class. Defaults to ``None``. 81 | :param busio.SPI external_spi: A previously declared spi object. Defaults to ``None``. 82 | :param bool extract_values: If true, single-length fetched values are automatically extracted 83 | from lists and tuples. Defaults to ``True``. 84 | :param debug: Turn on debug print outs. Defaults to False. 85 | 86 | """ 87 | 88 | # pylint: disable=too-many-instance-attributes, too-many-locals, too-many-branches, too-many-statements 89 | def __init__( 90 | self, 91 | *, 92 | status_neopixel=None, 93 | esp=None, 94 | external_spi=None, 95 | extract_values=True, 96 | debug=False, 97 | ): 98 | self._wifi = WiFi( 99 | status_neopixel=status_neopixel, esp=esp, external_spi=external_spi 100 | ) 101 | self._debug = debug 102 | self.json_transform = [] 103 | self._extract_values = extract_values 104 | 105 | try: 106 | os.stat(LOCALFILE) 107 | self.uselocal = True 108 | except OSError: 109 | self.uselocal = False 110 | 111 | requests.set_socket(socket, self._wifi.esp) 112 | 113 | gc.collect() 114 | 115 | def neo_status(self, value): 116 | """The status NeoPixel. 117 | 118 | :param value: The color to change the NeoPixel. 119 | 120 | """ 121 | self._wifi.neo_status(value) 122 | 123 | @staticmethod 124 | def json_traverse(json, path): 125 | """ 126 | Traverse down the specified JSON path and return the value or values 127 | 128 | :param json: JSON data to traverse 129 | :param list path: The path that we want to follow 130 | 131 | """ 132 | value = json 133 | if not isinstance(path, (list, tuple)): 134 | raise ValueError( 135 | "The json_path parameter should be enclosed in a list or tuple." 136 | ) 137 | for x in path: 138 | value = value[x] 139 | gc.collect() 140 | return value 141 | 142 | def add_json_transform(self, json_transform): 143 | """Add a function that is applied to JSON data when data is fetched 144 | 145 | :param json_transform: A function or a list of functions to call with the parsed JSON. 146 | Changes and additions are permitted for the ``dict`` object. 147 | """ 148 | if callable(json_transform): 149 | self.json_transform.append(json_transform) 150 | else: 151 | self.json_transform.extend(filter(callable, json_transform)) 152 | 153 | def get_local_time(self, location=None): 154 | # pylint: disable=line-too-long 155 | """Fetch and "set" the local time of this microcontroller to the local time at the location, using an internet time API. 156 | 157 | :param str location: Your city and country, e.g. ``"New York, US"``. 158 | 159 | """ 160 | # pylint: enable=line-too-long 161 | self.connect() 162 | api_url = None 163 | try: 164 | aio_username = secrets["aio_username"] 165 | aio_key = secrets["aio_key"] 166 | except KeyError: 167 | raise KeyError( 168 | "\n\nOur time service requires a login/password to rate-limit. Please register for a free adafruit.io account and place the user/key in your secrets file under 'aio_username' and 'aio_key'" # pylint: disable=line-too-long 169 | ) from KeyError 170 | 171 | location = secrets.get("timezone", location) 172 | if location: 173 | print("Getting time for timezone", location) 174 | api_url = (TIME_SERVICE + "&tz=%s") % (aio_username, aio_key, location) 175 | else: # we'll try to figure it out from the IP address 176 | print("Getting time from IP address") 177 | api_url = TIME_SERVICE % (aio_username, aio_key) 178 | api_url += TIME_SERVICE_STRFTIME 179 | try: 180 | response = requests.get(api_url, timeout=10) 181 | if response.status_code != 200: 182 | error_message = ( 183 | "Error connection to Adafruit IO. The response was: " 184 | + response.text 185 | ) 186 | raise ValueError(error_message) 187 | if self._debug: 188 | print("Time request: ", api_url) 189 | print("Time reply: ", response.text) 190 | times = response.text.split(" ") 191 | the_date = times[0] 192 | the_time = times[1] 193 | year_day = int(times[2]) 194 | week_day = int(times[3]) 195 | is_dst = None # no way to know yet 196 | except KeyError: 197 | raise KeyError( 198 | "Was unable to lookup the time, try setting secrets['timezone'] according to http://worldtimeapi.org/timezones" # pylint: disable=line-too-long 199 | ) from KeyError 200 | year, month, mday = [int(x) for x in the_date.split("-")] 201 | the_time = the_time.split(".")[0] 202 | hours, minutes, seconds = [int(x) for x in the_time.split(":")] 203 | now = time.struct_time( 204 | (year, month, mday, hours, minutes, seconds, week_day, year_day, is_dst) 205 | ) 206 | rtc.RTC().datetime = now 207 | 208 | # now clean up 209 | response.close() 210 | response = None 211 | gc.collect() 212 | 213 | def wget(self, url, filename, *, chunk_size=12000): 214 | """Download a url and save to filename location, like the command wget. 215 | 216 | :param url: The URL from which to obtain the data. 217 | :param filename: The name of the file to save the data to. 218 | :param chunk_size: how much data to read/write at a time. 219 | 220 | """ 221 | print("Fetching stream from", url) 222 | 223 | self.neo_status(STATUS_FETCHING) 224 | response = requests.get(url, stream=True) 225 | 226 | if self._debug: 227 | print(response.headers) 228 | content_length = int(response.headers["content-length"]) 229 | remaining = content_length 230 | print("Saving data to ", filename) 231 | stamp = time.monotonic() 232 | file = open(filename, "wb") 233 | for i in response.iter_content(min(remaining, chunk_size)): # huge chunks! 234 | self.neo_status(STATUS_DOWNLOADING) 235 | remaining -= len(i) 236 | file.write(i) 237 | if self._debug: 238 | print( 239 | "Read %d bytes, %d remaining" 240 | % (content_length - remaining, remaining) 241 | ) 242 | else: 243 | print(".", end="") 244 | if not remaining: 245 | break 246 | self.neo_status(STATUS_FETCHING) 247 | file.close() 248 | 249 | response.close() 250 | stamp = time.monotonic() - stamp 251 | print( 252 | "Created file of %d bytes in %0.1f seconds" % (os.stat(filename)[6], stamp) 253 | ) 254 | self.neo_status(STATUS_OFF) 255 | if not content_length == os.stat(filename)[6]: 256 | raise RuntimeError 257 | 258 | def connect(self): 259 | """ 260 | Connect to WiFi using the settings found in secrets.py 261 | """ 262 | self._wifi.neo_status(STATUS_CONNECTING) 263 | while not self._wifi.esp.is_connected: 264 | # secrets dictionary must contain 'ssid' and 'password' at a minimum 265 | print("Connecting to AP", secrets["ssid"]) 266 | if secrets["ssid"] == "CHANGE ME" or secrets["password"] == "CHANGE ME": 267 | change_me = "\n" + "*" * 45 268 | change_me += "\nPlease update the 'secrets.py' file on your\n" 269 | change_me += "CIRCUITPY drive to include your local WiFi\n" 270 | change_me += "access point SSID name in 'ssid' and SSID\n" 271 | change_me += "password in 'password'. Then save to reload!\n" 272 | change_me += "*" * 45 273 | raise OSError(change_me) 274 | self._wifi.neo_status(STATUS_NO_CONNECTION) # red = not connected 275 | try: 276 | self._wifi.esp.connect(secrets) 277 | except RuntimeError as error: 278 | print("Could not connect to internet", error) 279 | print("Retrying in 3 seconds...") 280 | time.sleep(3) 281 | 282 | def _get_io_client(self): 283 | self.connect() 284 | 285 | try: 286 | aio_username = secrets["aio_username"] 287 | aio_key = secrets["aio_key"] 288 | except KeyError: 289 | raise KeyError( 290 | "Adafruit IO secrets are kept in secrets.py, please add them there!\n\n" 291 | ) from KeyError 292 | 293 | return IO_HTTP(aio_username, aio_key, self._wifi.manager(secrets)) 294 | 295 | def push_to_io(self, feed_key, data): 296 | """Push data to an adafruit.io feed 297 | 298 | :param str feed_key: Name of feed key to push data to. 299 | :param data: data to send to feed 300 | 301 | """ 302 | 303 | io_client = self._get_io_client() 304 | 305 | while True: 306 | try: 307 | feed_id = io_client.get_feed(feed_key) 308 | except AdafruitIO_RequestError: 309 | # If no feed exists, create one 310 | feed_id = io_client.create_new_feed(feed_key) 311 | except RuntimeError as exception: 312 | print("An error occured, retrying! 1 -", exception) 313 | continue 314 | break 315 | 316 | while True: 317 | try: 318 | io_client.send_data(feed_id["key"], data) 319 | except RuntimeError as exception: 320 | print("An error occured, retrying! 2 -", exception) 321 | continue 322 | except NameError as exception: 323 | print(feed_id["key"], data, exception) 324 | continue 325 | break 326 | 327 | def get_io_feed(self, feed_key, detailed=False): 328 | """Return the Adafruit IO Feed that matches the feed key 329 | 330 | :param str feed_key: Name of feed key to match. 331 | :param bool detailed: Whether to return additional detailed information 332 | 333 | """ 334 | io_client = self._get_io_client() 335 | 336 | while True: 337 | try: 338 | return io_client.get_feed(feed_key, detailed=detailed) 339 | except RuntimeError as exception: 340 | print("An error occured, retrying! 1 -", exception) 341 | continue 342 | break 343 | 344 | def get_io_group(self, group_key): 345 | """Return the Adafruit IO Group that matches the group key 346 | 347 | :param str group_key: Name of group key to match. 348 | 349 | """ 350 | io_client = self._get_io_client() 351 | 352 | while True: 353 | try: 354 | return io_client.get_group(group_key) 355 | except RuntimeError as exception: 356 | print("An error occured, retrying! 1 -", exception) 357 | continue 358 | break 359 | 360 | def get_io_data(self, feed_key): 361 | """Return all values from Adafruit IO Feed Data that matches the feed key 362 | 363 | :param str feed_key: Name of feed key to receive data from. 364 | 365 | """ 366 | io_client = self._get_io_client() 367 | 368 | while True: 369 | try: 370 | return io_client.receive_all_data(feed_key) 371 | except RuntimeError as exception: 372 | print("An error occured, retrying! 1 -", exception) 373 | continue 374 | break 375 | 376 | def fetch(self, url, *, headers=None, timeout=10): 377 | """Fetch data from the specified url and return a response object""" 378 | gc.collect() 379 | if self._debug: 380 | print("Free mem: ", gc.mem_free()) # pylint: disable=no-member 381 | 382 | response = None 383 | if self.uselocal: 384 | print("*** USING LOCALFILE FOR DATA - NOT INTERNET!!! ***") 385 | response = Fake_Requests(LOCALFILE) 386 | 387 | if not response: 388 | self.connect() 389 | # great, lets get the data 390 | print("Retrieving data...", end="") 391 | self.neo_status(STATUS_FETCHING) # yellow = fetching data 392 | gc.collect() 393 | response = requests.get(url, headers=headers, timeout=timeout) 394 | gc.collect() 395 | self.neo_status(STATUS_DATA_RECEIVED) # green = got data 396 | print("Reply is OK!") 397 | 398 | return response 399 | 400 | def fetch_data( 401 | self, url, *, headers=None, json_path=None, regexp_path=None, timeout=10, 402 | ): 403 | """Fetch data from the specified url and perfom any parsing""" 404 | json_out = None 405 | values = [] 406 | 407 | print(url) 408 | response = self.fetch(url, headers=headers, timeout=timeout) 409 | 410 | if json_path is not None: 411 | if isinstance(json_path, (list, tuple)) and ( 412 | not json_path or not isinstance(json_path[0], (list, tuple)) 413 | ): 414 | json_path = (json_path,) 415 | try: 416 | gc.collect() 417 | json_out = response.json() 418 | if self._debug: 419 | print(json_out) 420 | gc.collect() 421 | except ValueError: # failed to parse? 422 | print("Couldn't parse json: ", response.text) 423 | raise 424 | except MemoryError: 425 | supervisor.reload() 426 | 427 | if regexp_path: 428 | import re # pylint: disable=import-outside-toplevel 429 | 430 | # optional JSON post processing, apply any transformations 431 | # these MAY change/add element 432 | for idx, json_transform in enumerate(self.json_transform): 433 | try: 434 | json_transform(json_out) 435 | except Exception as error: 436 | print("Exception from json_transform: ", idx, error) 437 | raise 438 | 439 | # extract desired text/values from json 440 | if json_path: 441 | for path in json_path: 442 | try: 443 | values.append(self.json_traverse(json_out, path)) 444 | except KeyError: 445 | print(json_out) 446 | raise 447 | elif regexp_path: 448 | for regexp in regexp_path: 449 | values.append(re.search(regexp, response.text).group(1)) 450 | else: 451 | values = response.text 452 | 453 | # we're done with the requests object, lets delete it so we can do more! 454 | json_out = None 455 | response = None 456 | gc.collect() 457 | if self._extract_values and len(values) == 1: 458 | return values[0] 459 | 460 | return values 461 | 462 | @property 463 | def ip_address(self): 464 | """Return the IP Address nicely formatted""" 465 | return self._wifi.esp.pretty_ip(self._wifi.esp.ip_address) 466 | --------------------------------------------------------------------------------