diff --git a/GW-custom/uPyLoRaWAN/.vscode/extensions.json b/GW-custom/uPyLoRaWAN/.vscode/extensions.json deleted file mode 100644 index 2e8d346480b49a2f5651b00a5a3ca12c25b89f11..0000000000000000000000000000000000000000 --- a/GW-custom/uPyLoRaWAN/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - // List of extensions which should be recommended for users of this workspace. - "recommendations": [ - "ms-python.python", // micropy-cli: required for vscode micropython integrations - "VisualStudioExptTeam.vscodeintellicode" // micropy-cli: optional for advanced intellisense - ] -} \ No newline at end of file diff --git a/GW-custom/uPyLoRaWAN/.vscode/settings.json b/GW-custom/uPyLoRaWAN/.vscode/settings.json deleted file mode 100644 index 292a63e1f79f9f016a2d07191fdd0d194d0742fd..0000000000000000000000000000000000000000 --- a/GW-custom/uPyLoRaWAN/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "python.linting.enabled": true, - "python.jediEnabled": false, - - // Loaded Stubs: esp32-micropython-1.12.0 - "python.autoComplete.extraPaths": [".micropy/BradenM-micropy-stubs-9569403/frozen", ".micropy/BradenM-micropy-stubs-4c2702f/frozen", ".micropy/BradenM-micropy-stubs-9569403/stubs", ".micropy/uPyLora"], - "python.autoComplete.typeshedPaths": [".micropy/BradenM-micropy-stubs-9569403/frozen", ".micropy/BradenM-micropy-stubs-4c2702f/frozen", ".micropy/BradenM-micropy-stubs-9569403/stubs", ".micropy/uPyLora"], - "python.analysis.typeshedPaths": [".micropy/BradenM-micropy-stubs-9569403/frozen", ".micropy/BradenM-micropy-stubs-4c2702f/frozen", ".micropy/BradenM-micropy-stubs-9569403/stubs", ".micropy/uPyLora"], - - "python.linting.pylintEnabled": true -} \ No newline at end of file diff --git a/GW-custom/uPyLoRaWAN/uPySensors/LICENSE b/GW-custom/uPyLoRaWAN/uPySensors/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/GW-custom/uPyLoRaWAN/uPySensors/README.md b/GW-custom/uPyLoRaWAN/uPySensors/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6847f8148028945379da7a10d5f6ac6b257e4100 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/README.md @@ -0,0 +1,22 @@ +# uPySensors +MicroPython sensor/actuator libraries + + +# Added Drivers + +| | | | +|:--|:--|:--| +|`hcsr04.py`|Ultrasonic Sensor - HC-SR04 | [about](https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf) - [buy](https://www.banggood.com/de/Wholesale-Ultrasonic-Module-HC-SR04-Distance-Measuring-Ranging-Transducer-Sensor-p-40313.html?p=QW0903761303201409LG) - [blog article](https://lemariva.com/blog/2018/06/tutorial-getting-started-with-micropython-sensors)| +|`imu.py & vector3d.py`| MPU6055 - Triple Axis Accelerometer and Gyro |[about](https://www.invensense.com/products/motion-tracking/6-axis/mpu-6500/) - [buy](https://www.banggood.com/de/5pcs-GY-6500-MPU6500-6DOF-6-Axis-Attitude-Acceleration-Gyroscope-Sensor-Module-SPI-Interface-p-1291399.html?p=QW0903761303201409LG) - [blog article](https://lemariva.com/blog/2018/06/micropython-camera-stabilisation-application)|| +|`imu.py, vector3d.py & mpu9250.py`| MPU9250/5 - 9-axis Motion Processing Unit |[about](https://www.invensense.com/products/motion-tracking/9-axis/mpu-9250/) - [buy](https://www.banggood.com/de/GY-91-MPU9250-BMP280-10DOF-Acceleration-Gyroscope-Compass-Nine-Shaft-Sensor-Module-p-1129541.html?p=QW0903761303201409LG) - [blog article](https://lemariva.com/blog/2018/06/micropython-camera-stabilisation-application)| +|`ublox_gps.py` | NEO-M8N/6M - GPS Modules |[about](https://www.u-blox.com/de/product/neo-m8-series) - [buy](https://rover.ebay.com/rover/1/707-53477-19255-0/1?toolid=20001&campid=5338002758&customid=link&mpre=http%3A%2F%2Fwww.ebay.de%2Fitm%2F272470133068%3F_trksid%3Dp2060353.m2749.l2649%26ssPageName%3DSTRK%253AMEBIDX%253AIT) - [blog article](https://lemariva.com/blog/2017/04/wipy-2-0-weather-report-neo-m8n)| +|`vl53l0x.py` | Vl53l0X - laser-ranging module |[about](http://www.st.com/en/imaging-and-photonics-solutions/vl53l0x.html) - [buy](https://www.banggood.com/de/GY-530-VL53L0X-Laser-Ranging-Sensor-Module-IIC-Communication-Ranging-Module-p-1201341.html?p=QW0903761303201409LG) - [blog article](https://lemariva.com/blog/2018/12/happy-new-year-2019)| +|`ssd1306_i2c.py` `ssd1306.py` | SSD1306 - display drivers | [blog article](https://lemariva.com/blog/2018/10/micropython-esp32-sending-data-using-lora) | +|`ST7735.py` `sysfont.py` | ST7735 - display drivers for ESP32/ESP8266 | [blog article](https://lemariva.com/blog/2019/01/micropython-programming-an-esp-using-jupyter-notebook) | +|`ili934xnew.py` `fonts/*` | ILI934 - display drivers for ESP32/M5Stack | [blog article](https://lemariva.com/blog/2020/02/m5stack-micropython-and-bluetooth-ble) | +|`bmx280.py` | BME280 / BMP280 - temperature, pressure and humidity (BME) sensor| [about](https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/) - [blog article soon] | +|`bmx680.py` | BME680 - gas, temperature, pressure and humidity sensor| [about](https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors-bme680/) - [blog article](https://lemariva.com/blog/default/default/micropython-google-cloud-platform-getting-data-m5stack-atom-sensing-air-quality) | +|`pmsa003.py` | PMSA003(a) particle/dust sensor| [about](https://lemariva.com/storage/app/media/uploaded-files/PMSA003.pdf) - [blog article](https://lemariva.com/blog/2020/04/micropython-google-cloud-platform-getting-data-m5stack-atom-sensing-air-quality) | +|`stepper.py` | 6-wire steppers using LN298| [blog article soon] | +# Licenses +* check files diff --git a/GW-custom/uPyLoRaWAN/uPySensors/ST7735.py b/GW-custom/uPyLoRaWAN/uPySensors/ST7735.py new file mode 100644 index 0000000000000000000000000000000000000000..8ab4c5766d6b1eb40a07faae993116e5a833cebf --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/ST7735.py @@ -0,0 +1,890 @@ +#driver for Sainsmart 1.8" TFT display ST7735 +#Translated by Guy Carver from the ST7735 sample code. +#Modirfied for micropython-esp32 by boochow + +import machine +import time +from math import sqrt + +#TFTRotations and TFTRGB are bits to set +# on MADCTL to control display rotation/color layout +#Looking at display with pins on top. +#00 = upper left printing right +#10 = does nothing (MADCTL_ML) +#20 = upper left printing down (backwards) (Vertical flip) +#40 = upper right printing left (backwards) (X Flip) +#80 = lower left printing right (backwards) (Y Flip) +#04 = (MADCTL_MH) + +#60 = 90 right rotation +#C0 = 180 right rotation +#A0 = 270 right rotation +TFTRotations = [0x00, 0x60, 0xC0, 0xA0] +TFTBGR = 0x08 #When set color is bgr else rgb. +TFTRGB = 0x00 + +#@micropython.native +def clamp( aValue, aMin, aMax ) : + return max(aMin, min(aMax, aValue)) + +#@micropython.native +def TFTColor( aR, aG, aB ) : + '''Create a 16 bit rgb value from the given R,G,B from 0-255. + This assumes rgb 565 layout and will be incorrect for bgr.''' + return ((aR & 0xF8) << 8) | ((aG & 0xFC) << 3) | (aB >> 3) + +ScreenSize = (128, 160) + +class TFT(object) : + """Sainsmart TFT 7735 display driver.""" + + NOP = 0x0 + SWRESET = 0x01 + RDDID = 0x04 + RDDST = 0x09 + + SLPIN = 0x10 + SLPOUT = 0x11 + PTLON = 0x12 + NORON = 0x13 + + INVOFF = 0x20 + INVON = 0x21 + DISPOFF = 0x28 + DISPON = 0x29 + CASET = 0x2A + RASET = 0x2B + RAMWR = 0x2C + RAMRD = 0x2E + + COLMOD = 0x3A + MADCTL = 0x36 + + FRMCTR1 = 0xB1 + FRMCTR2 = 0xB2 + FRMCTR3 = 0xB3 + INVCTR = 0xB4 + DISSET5 = 0xB6 + + PWCTR1 = 0xC0 + PWCTR2 = 0xC1 + PWCTR3 = 0xC2 + PWCTR4 = 0xC3 + PWCTR5 = 0xC4 + VMCTR1 = 0xC5 + + RDID1 = 0xDA + RDID2 = 0xDB + RDID3 = 0xDC + RDID4 = 0xDD + + PWCTR6 = 0xFC + + GMCTRP1 = 0xE0 + GMCTRN1 = 0xE1 + + BLACK = 0 + RED = TFTColor(0xFF, 0x00, 0x00) + MAROON = TFTColor(0x80, 0x00, 0x00) + GREEN = TFTColor(0x00, 0xFF, 0x00) + FOREST = TFTColor(0x00, 0x80, 0x80) + BLUE = TFTColor(0x00, 0x00, 0xFF) + NAVY = TFTColor(0x00, 0x00, 0x80) + CYAN = TFTColor(0x00, 0xFF, 0xFF) + YELLOW = TFTColor(0xFF, 0xFF, 0x00) + PURPLE = TFTColor(0xFF, 0x00, 0xFF) + WHITE = TFTColor(0xFF, 0xFF, 0xFF) + GRAY = TFTColor(0x80, 0x80, 0x80) + + @staticmethod + def color( aB, aG, aR ) : + '''Create a 565 rgb TFTColor value''' + return TFTColor(aR, aG, aB) + + def __init__( self, spi, aDC, aReset, aCS) : + """aLoc SPI pin location is either 1 for 'X' or 2 for 'Y'. + aDC is the DC pin and aReset is the reset pin.""" + self._size = ScreenSize + self._offset = bytearray([0,0]) + self.rotate = 0 #Vertical with top toward pins. + self._rgb = True #color order of rgb. + self.dc = machine.Pin(aDC, machine.Pin.OUT, machine.Pin.PULL_DOWN) + self.reset = machine.Pin(aReset, machine.Pin.OUT, machine.Pin.PULL_DOWN) + self.cs = machine.Pin(aCS, machine.Pin.OUT, machine.Pin.PULL_DOWN) + self.cs(1) + self.spi = spi + self.colorData = bytearray(2) + self.windowLocData = bytearray(4) + + def size( self ) : + return self._size + +# @micropython.native + def on( self, aTF = True ) : + '''Turn display on or off.''' + self._writecommand(TFT.DISPON if aTF else TFT.DISPOFF) + +# @micropython.native + def invertcolor( self, aBool ) : + '''Invert the color data IE: Black = White.''' + self._writecommand(TFT.INVON if aBool else TFT.INVOFF) + +# @micropython.native + def rgb( self, aTF = True ) : + '''True = rgb else bgr''' + self._rgb = aTF + self._setMADCTL() + +# @micropython.native + def rotation( self, aRot ) : + '''0 - 3. Starts vertical with top toward pins and rotates 90 deg + clockwise each step.''' + if (0 <= aRot < 4): + rotchange = self.rotate ^ aRot + self.rotate = aRot + #If switching from vertical to horizontal swap x,y + # (indicated by bit 0 changing). + if (rotchange & 1): + self._size =(self._size[1], self._size[0]) + self._setMADCTL() + +# @micropython.native + def pixel( self, aPos, aColor ) : + '''Draw a pixel at the given position''' + if 0 <= aPos[0] < self._size[0] and 0 <= aPos[1] < self._size[1]: + self._setwindowpoint(aPos) + self._pushcolor(aColor) + +# @micropython.native + def text( self, aPos, aString, aColor, aFont, aSize = 1, nowrap = False ) : + '''Draw a text at the given position. If the string reaches the end of the + display it is wrapped to aPos[0] on the next line. aSize may be an integer + which will size the font uniformly on w,h or a or any type that may be + indexed with [0] or [1].''' + + if aFont == None: + return + + #Make a size either from single value or 2 elements. + if (type(aSize) == int) or (type(aSize) == float): + wh = (aSize, aSize) + else: + wh = aSize + + px, py = aPos + width = wh[0] * aFont["Width"] + 1 + for c in aString: + self.char((px, py), c, aColor, aFont, wh) + px += width + #We check > rather than >= to let the right (blank) edge of the + # character print off the right of the screen. + if px + width > self._size[0]: + if nowrap: + break + else: + py += aFont["Height"] * wh[1] + 1 + px = aPos[0] + +# @micropython.native + def char( self, aPos, aChar, aColor, aFont, aSizes ) : + '''Draw a character at the given position using the given font and color. + aSizes is a tuple with x, y as integer scales indicating the + # of pixels to draw for each pixel in the character.''' + + if aFont == None: + return + + startchar = aFont['Start'] + endchar = aFont['End'] + + ci = ord(aChar) + if (startchar <= ci <= endchar): + fontw = aFont['Width'] + fonth = aFont['Height'] + ci = (ci - startchar) * fontw + + charA = aFont["Data"][ci:ci + fontw] + px = aPos[0] + if aSizes[0] <= 1 and aSizes[1] <= 1 : + for c in charA : + py = aPos[1] + for r in range(fonth) : + if c & 0x01 : + self.pixel((px, py), aColor) + py += 1 + c >>= 1 + px += 1 + else: + for c in charA : + py = aPos[1] + for r in range(fonth) : + if c & 0x01 : + self.fillrect((px, py), aSizes, aColor) + py += aSizes[1] + c >>= 1 + px += aSizes[0] + +# @micropython.native + def line( self, aStart, aEnd, aColor ) : + '''Draws a line from aStart to aEnd in the given color. Vertical or horizontal + lines are forwarded to vline and hline.''' + if aStart[0] == aEnd[0]: + #Make sure we use the smallest y. + pnt = aEnd if (aEnd[1] < aStart[1]) else aStart + self.vline(pnt, abs(aEnd[1] - aStart[1]) + 1, aColor) + elif aStart[1] == aEnd[1]: + #Make sure we use the smallest x. + pnt = aEnd if aEnd[0] < aStart[0] else aStart + self.hline(pnt, abs(aEnd[0] - aStart[0]) + 1, aColor) + else: + px, py = aStart + ex, ey = aEnd + dx = ex - px + dy = ey - py + inx = 1 if dx > 0 else -1 + iny = 1 if dy > 0 else -1 + + dx = abs(dx) + dy = abs(dy) + if (dx >= dy): + dy <<= 1 + e = dy - dx + dx <<= 1 + while (px != ex): + self.pixel((px, py), aColor) + if (e >= 0): + py += iny + e -= dx + e += dy + px += inx + else: + dx <<= 1 + e = dx - dy + dy <<= 1 + while (py != ey): + self.pixel((px, py), aColor) + if (e >= 0): + px += inx + e -= dy + e += dx + py += iny + +# @micropython.native + def vline( self, aStart, aLen, aColor ) : + '''Draw a vertical line from aStart for aLen. aLen may be negative.''' + start = (clamp(aStart[0], 0, self._size[0]), clamp(aStart[1], 0, self._size[1])) + stop = (start[0], clamp(start[1] + aLen, 0, self._size[1])) + #Make sure smallest y 1st. + if (stop[1] < start[1]): + start, stop = stop, start + self._setwindowloc(start, stop) + self._setColor(aColor) + self._draw(aLen) + +# @micropython.native + def hline( self, aStart, aLen, aColor ) : + '''Draw a horizontal line from aStart for aLen. aLen may be negative.''' + start = (clamp(aStart[0], 0, self._size[0]), clamp(aStart[1], 0, self._size[1])) + stop = (clamp(start[0] + aLen, 0, self._size[0]), start[1]) + #Make sure smallest x 1st. + if (stop[0] < start[0]): + start, stop = stop, start + self._setwindowloc(start, stop) + self._setColor(aColor) + self._draw(aLen) + +# @micropython.native + def rect( self, aStart, aSize, aColor ) : + '''Draw a hollow rectangle. aStart is the smallest coordinate corner + and aSize is a tuple indicating width, height.''' + self.hline(aStart, aSize[0], aColor) + self.hline((aStart[0], aStart[1] + aSize[1] - 1), aSize[0], aColor) + self.vline(aStart, aSize[1], aColor) + self.vline((aStart[0] + aSize[0] - 1, aStart[1]), aSize[1], aColor) + +# @micropython.native + def fillrect( self, aStart, aSize, aColor ) : + '''Draw a filled rectangle. aStart is the smallest coordinate corner + and aSize is a tuple indicating width, height.''' + start = (clamp(aStart[0], 0, self._size[0]), clamp(aStart[1], 0, self._size[1])) + end = (clamp(start[0] + aSize[0] - 1, 0, self._size[0]), clamp(start[1] + aSize[1] - 1, 0, self._size[1])) + + if (end[0] < start[0]): + tmp = end[0] + end = (start[0], end[1]) + start = (tmp, start[1]) + if (end[1] < start[1]): + tmp = end[1] + end = (end[0], start[1]) + start = (start[0], tmp) + + self._setwindowloc(start, end) + numPixels = (end[0] - start[0] + 1) * (end[1] - start[1] + 1) + self._setColor(aColor) + self._draw(numPixels) + +# @micropython.native + def circle( self, aPos, aRadius, aColor ) : + '''Draw a hollow circle with the given radius and color with aPos as center.''' + self.colorData[0] = aColor >> 8 + self.colorData[1] = aColor + xend = int(0.7071 * aRadius) + 1 + rsq = aRadius * aRadius + for x in range(xend) : + y = int(sqrt(rsq - x * x)) + xp = aPos[0] + x + yp = aPos[1] + y + xn = aPos[0] - x + yn = aPos[1] - y + xyp = aPos[0] + y + yxp = aPos[1] + x + xyn = aPos[0] - y + yxn = aPos[1] - x + + self._setwindowpoint((xp, yp)) + self._writedata(self.colorData) + self._setwindowpoint((xp, yn)) + self._writedata(self.colorData) + self._setwindowpoint((xn, yp)) + self._writedata(self.colorData) + self._setwindowpoint((xn, yn)) + self._writedata(self.colorData) + self._setwindowpoint((xyp, yxp)) + self._writedata(self.colorData) + self._setwindowpoint((xyp, yxn)) + self._writedata(self.colorData) + self._setwindowpoint((xyn, yxp)) + self._writedata(self.colorData) + self._setwindowpoint((xyn, yxn)) + self._writedata(self.colorData) + +# @micropython.native + def fillcircle( self, aPos, aRadius, aColor ) : + '''Draw a filled circle with given radius and color with aPos as center''' + rsq = aRadius * aRadius + for x in range(aRadius) : + y = int(sqrt(rsq - x * x)) + y0 = aPos[1] - y + ey = y0 + y * 2 + y0 = clamp(y0, 0, self._size[1]) + ln = abs(ey - y0) + 1; + + self.vline((aPos[0] + x, y0), ln, aColor) + self.vline((aPos[0] - x, y0), ln, aColor) + + def fill( self, aColor = BLACK ) : + '''Fill screen with the given color.''' + self.fillrect((0, 0), self._size, aColor) + +# @micropython.native + def _setColor( self, aColor ) : + self.colorData[0] = aColor >> 8 + self.colorData[1] = aColor + self.buf = bytes(self.colorData) * 32 + +# @micropython.native + def _draw( self, aPixels ) : + '''Send given color to the device aPixels times.''' + + self.dc(1) + self.cs(0) + for i in range(aPixels//32): + self.spi.write(self.buf) + rest = (int(aPixels) % 32) + if rest > 0: + buf2 = bytes(self.colorData) * rest + self.spi.write(buf2) + self.cs(1) + +# @micropython.native + def _setwindowpoint( self, aPos ) : + '''Set a single point for drawing a color to.''' + x = self._offset[0] + int(aPos[0]) + y = self._offset[1] + int(aPos[1]) + self._writecommand(TFT.CASET) #Column address set. + self.windowLocData[0] = self._offset[0] + self.windowLocData[1] = x + self.windowLocData[2] = self._offset[0] + self.windowLocData[3] = x + self._writedata(self.windowLocData) + + self._writecommand(TFT.RASET) #Row address set. + self.windowLocData[0] = self._offset[1] + self.windowLocData[1] = y + self.windowLocData[2] = self._offset[1] + self.windowLocData[3] = y + self._writedata(self.windowLocData) + self._writecommand(TFT.RAMWR) #Write to RAM. + +# @micropython.native + def _setwindowloc( self, aPos0, aPos1 ) : + '''Set a rectangular area for drawing a color to.''' + self._writecommand(TFT.CASET) #Column address set. + self.windowLocData[0] = self._offset[0] + self.windowLocData[1] = self._offset[0] + int(aPos0[0]) + self.windowLocData[2] = self._offset[0] + self.windowLocData[3] = self._offset[0] + int(aPos1[0]) + self._writedata(self.windowLocData) + + self._writecommand(TFT.RASET) #Row address set. + self.windowLocData[0] = self._offset[1] + self.windowLocData[1] = self._offset[1] + int(aPos0[1]) + self.windowLocData[2] = self._offset[1] + self.windowLocData[3] = self._offset[1] + int(aPos1[1]) + self._writedata(self.windowLocData) + + self._writecommand(TFT.RAMWR) #Write to RAM. + + #@micropython.native + def _writecommand( self, aCommand ) : + '''Write given command to the device.''' + self.dc(0) + self.cs(0) + self.spi.write(bytearray([aCommand])) + self.cs(1) + + #@micropython.native + def _writedata( self, aData ) : + '''Write given data to the device. This may be + either a single int or a bytearray of values.''' + self.dc(1) + self.cs(0) + self.spi.write(aData) + self.cs(1) + + #@micropython.native + def _pushcolor( self, aColor ) : + '''Push given color to the device.''' + self.colorData[0] = aColor >> 8 + self.colorData[1] = aColor + self._writedata(self.colorData) + + #@micropython.native + def _setMADCTL( self ) : + '''Set screen rotation and RGB/BGR format.''' + self._writecommand(TFT.MADCTL) + rgb = TFTRGB if self._rgb else TFTBGR + self._writedata(bytearray([TFTRotations[self.rotate] | rgb])) + + #@micropython.native + def _reset( self ) : + '''Reset the device.''' + self.dc(0) + self.reset(1) + time.sleep_us(500) + self.reset(0) + time.sleep_us(500) + self.reset(1) + time.sleep_us(500) + + def initb( self ) : + '''Initialize blue tab version.''' + self._size = (ScreenSize[0] + 2, ScreenSize[1] + 1) + self._reset() + self._writecommand(TFT.SWRESET) #Software reset. + time.sleep_us(50) + self._writecommand(TFT.SLPOUT) #out of sleep mode. + time.sleep_us(500) + + data1 = bytearray(1) + self._writecommand(TFT.COLMOD) #Set color mode. + data1[0] = 0x05 #16 bit color. + self._writedata(data1) + time.sleep_us(10) + + data3 = bytearray([0x00, 0x06, 0x03]) #fastest refresh, 6 lines front, 3 lines back. + self._writecommand(TFT.FRMCTR1) #Frame rate control. + self._writedata(data3) + time.sleep_us(10) + + self._writecommand(TFT.MADCTL) + data1[0] = 0x08 #row address/col address, bottom to top refresh + self._writedata(data1) + + data2 = bytearray(2) + self._writecommand(TFT.DISSET5) #Display settings + data2[0] = 0x15 #1 clock cycle nonoverlap, 2 cycle gate rise, 3 cycle oscil, equalize + data2[1] = 0x02 #fix on VTL + self._writedata(data2) + + self._writecommand(TFT.INVCTR) #Display inversion control + data1[0] = 0x00 #Line inversion. + self._writedata(data1) + + self._writecommand(TFT.PWCTR1) #Power control + data2[0] = 0x02 #GVDD = 4.7V + data2[1] = 0x70 #1.0uA + self._writedata(data2) + time.sleep_us(10) + + self._writecommand(TFT.PWCTR2) #Power control + data1[0] = 0x05 #VGH = 14.7V, VGL = -7.35V + self._writedata(data1) + + self._writecommand(TFT.PWCTR3) #Power control + data2[0] = 0x01 #Opamp current small + data2[1] = 0x02 #Boost frequency + self._writedata(data2) + + self._writecommand(TFT.VMCTR1) #Power control + data2[0] = 0x3C #VCOMH = 4V + data2[1] = 0x38 #VCOML = -1.1V + self._writedata(data2) + time.sleep_us(10) + + self._writecommand(TFT.PWCTR6) #Power control + data2[0] = 0x11 + data2[1] = 0x15 + self._writedata(data2) + + #These different values don't seem to make a difference. +# dataGMCTRP = bytearray([0x0f, 0x1a, 0x0f, 0x18, 0x2f, 0x28, 0x20, 0x22, 0x1f, +# 0x1b, 0x23, 0x37, 0x00, 0x07, 0x02, 0x10]) + dataGMCTRP = bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, + 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10]) + self._writecommand(TFT.GMCTRP1) + self._writedata(dataGMCTRP) + +# dataGMCTRN = bytearray([0x0f, 0x1b, 0x0f, 0x17, 0x33, 0x2c, 0x29, 0x2e, 0x30, +# 0x30, 0x39, 0x3f, 0x00, 0x07, 0x03, 0x10]) + dataGMCTRN = bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, + 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10]) + self._writecommand(TFT.GMCTRN1) + self._writedata(dataGMCTRN) + time.sleep_us(10) + + self._writecommand(TFT.CASET) #Column address set. + self.windowLocData[0] = 0x00 + self.windowLocData[1] = 2 #Start at column 2 + self.windowLocData[2] = 0x00 + self.windowLocData[3] = self._size[0] - 1 + self._writedata(self.windowLocData) + + self._writecommand(TFT.RASET) #Row address set. + self.windowLocData[1] = 1 #Start at row 2. + self.windowLocData[3] = self._size[1] - 1 + self._writedata(self.windowLocData) + + self._writecommand(TFT.NORON) #Normal display on. + time.sleep_us(10) + + self._writecommand(TFT.RAMWR) + time.sleep_us(500) + + self._writecommand(TFT.DISPON) + self.cs(1) + time.sleep_us(500) + + def initr( self ) : + '''Initialize a red tab version.''' + self._reset() + + self._writecommand(TFT.SWRESET) #Software reset. + time.sleep_us(150) + self._writecommand(TFT.SLPOUT) #out of sleep mode. + time.sleep_us(500) + + data3 = bytearray([0x01, 0x2C, 0x2D]) #fastest refresh, 6 lines front, 3 lines back. + self._writecommand(TFT.FRMCTR1) #Frame rate control. + self._writedata(data3) + + self._writecommand(TFT.FRMCTR2) #Frame rate control. + self._writedata(data3) + + data6 = bytearray([0x01, 0x2c, 0x2d, 0x01, 0x2c, 0x2d]) + self._writecommand(TFT.FRMCTR3) #Frame rate control. + self._writedata(data6) + time.sleep_us(10) + + data1 = bytearray(1) + self._writecommand(TFT.INVCTR) #Display inversion control + data1[0] = 0x07 #Line inversion. + self._writedata(data1) + + self._writecommand(TFT.PWCTR1) #Power control + data3[0] = 0xA2 + data3[1] = 0x02 + data3[2] = 0x84 + self._writedata(data3) + + self._writecommand(TFT.PWCTR2) #Power control + data1[0] = 0xC5 #VGH = 14.7V, VGL = -7.35V + self._writedata(data1) + + data2 = bytearray(2) + self._writecommand(TFT.PWCTR3) #Power control + data2[0] = 0x0A #Opamp current small + data2[1] = 0x00 #Boost frequency + self._writedata(data2) + + self._writecommand(TFT.PWCTR4) #Power control + data2[0] = 0x8A #Opamp current small + data2[1] = 0x2A #Boost frequency + self._writedata(data2) + + self._writecommand(TFT.PWCTR5) #Power control + data2[0] = 0x8A #Opamp current small + data2[1] = 0xEE #Boost frequency + self._writedata(data2) + + self._writecommand(TFT.VMCTR1) #Power control + data1[0] = 0x0E + self._writedata(data1) + + self._writecommand(TFT.INVOFF) + + self._writecommand(TFT.MADCTL) #Power control + data1[0] = 0xC8 + self._writedata(data1) + + self._writecommand(TFT.COLMOD) + data1[0] = 0x05 + self._writedata(data1) + + self._writecommand(TFT.CASET) #Column address set. + self.windowLocData[0] = 0x00 + self.windowLocData[1] = 0x00 + self.windowLocData[2] = 0x00 + self.windowLocData[3] = self._size[0] - 1 + self._writedata(self.windowLocData) + + self._writecommand(TFT.RASET) #Row address set. + self.windowLocData[3] = self._size[1] - 1 + self._writedata(self.windowLocData) + + dataGMCTRP = bytearray([0x0f, 0x1a, 0x0f, 0x18, 0x2f, 0x28, 0x20, 0x22, 0x1f, + 0x1b, 0x23, 0x37, 0x00, 0x07, 0x02, 0x10]) + self._writecommand(TFT.GMCTRP1) + self._writedata(dataGMCTRP) + + dataGMCTRN = bytearray([0x0f, 0x1b, 0x0f, 0x17, 0x33, 0x2c, 0x29, 0x2e, 0x30, + 0x30, 0x39, 0x3f, 0x00, 0x07, 0x03, 0x10]) + self._writecommand(TFT.GMCTRN1) + self._writedata(dataGMCTRN) + time.sleep_us(10) + + self._writecommand(TFT.DISPON) + time.sleep_us(100) + + self._writecommand(TFT.NORON) #Normal display on. + time.sleep_us(10) + + self.cs(1) + + def initb2( self ) : + '''Initialize another blue tab version.''' + self._size = (ScreenSize[0] + 2, ScreenSize[1] + 1) + self._offset[0] = 2 + self._offset[1] = 1 + self._reset() + self._writecommand(TFT.SWRESET) #Software reset. + time.sleep_us(50) + self._writecommand(TFT.SLPOUT) #out of sleep mode. + time.sleep_us(500) + + data3 = bytearray([0x01, 0x2C, 0x2D]) # + self._writecommand(TFT.FRMCTR1) #Frame rate control. + self._writedata(data3) + time.sleep_us(10) + + self._writecommand(TFT.FRMCTR2) #Frame rate control. + self._writedata(data3) + time.sleep_us(10) + + self._writecommand(TFT.FRMCTR3) #Frame rate control. + self._writedata(data3) + time.sleep_us(10) + + self._writecommand(TFT.INVCTR) #Display inversion control + data1 = bytearray(1) # + data1[0] = 0x07 + self._writedata(data1) + + self._writecommand(TFT.PWCTR1) #Power control + data3[0] = 0xA2 # + data3[1] = 0x02 # + data3[2] = 0x84 # + self._writedata(data3) + time.sleep_us(10) + + self._writecommand(TFT.PWCTR2) #Power control + data1[0] = 0xC5 # + self._writedata(data1) + + self._writecommand(TFT.PWCTR3) #Power control + data2 = bytearray(2) + data2[0] = 0x0A # + data2[1] = 0x00 # + self._writedata(data2) + + self._writecommand(TFT.PWCTR4) #Power control + data2[0] = 0x8A # + data2[1] = 0x2A # + self._writedata(data2) + + self._writecommand(TFT.PWCTR5) #Power control + data2[0] = 0x8A # + data2[1] = 0xEE # + self._writedata(data2) + + self._writecommand(TFT.VMCTR1) #Power control + data1[0] = 0x0E # + self._writedata(data1) + time.sleep_us(10) + + self._writecommand(TFT.MADCTL) + data1[0] = 0xC8 #row address/col address, bottom to top refresh + self._writedata(data1) + +#These different values don't seem to make a difference. +# dataGMCTRP = bytearray([0x0f, 0x1a, 0x0f, 0x18, 0x2f, 0x28, 0x20, 0x22, 0x1f, +# 0x1b, 0x23, 0x37, 0x00, 0x07, 0x02, 0x10]) + dataGMCTRP = bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, + 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10]) + self._writecommand(TFT.GMCTRP1) + self._writedata(dataGMCTRP) + +# dataGMCTRN = bytearray([0x0f, 0x1b, 0x0f, 0x17, 0x33, 0x2c, 0x29, 0x2e, 0x30, +# 0x30, 0x39, 0x3f, 0x00, 0x07, 0x03, 0x10]) + dataGMCTRN = bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, + 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10]) + self._writecommand(TFT.GMCTRN1) + self._writedata(dataGMCTRN) + time.sleep_us(10) + + self._writecommand(TFT.CASET) #Column address set. + self.windowLocData[0] = 0x00 + self.windowLocData[1] = 0x02 #Start at column 2 + self.windowLocData[2] = 0x00 + self.windowLocData[3] = self._size[0] - 1 + self._writedata(self.windowLocData) + + self._writecommand(TFT.RASET) #Row address set. + self.windowLocData[1] = 0x01 #Start at row 2. + self.windowLocData[3] = self._size[1] - 1 + self._writedata(self.windowLocData) + + data1 = bytearray(1) + self._writecommand(TFT.COLMOD) #Set color mode. + data1[0] = 0x05 #16 bit color. + self._writedata(data1) + time.sleep_us(10) + + self._writecommand(TFT.NORON) #Normal display on. + time.sleep_us(10) + + self._writecommand(TFT.RAMWR) + time.sleep_us(500) + + self._writecommand(TFT.DISPON) + self.cs(1) + time.sleep_us(500) + + #@micropython.native + def initg( self ) : + '''Initialize a green tab version.''' + self._reset() + + self._writecommand(TFT.SWRESET) #Software reset. + time.sleep_us(150) + self._writecommand(TFT.SLPOUT) #out of sleep mode. + time.sleep_us(255) + + data3 = bytearray([0x01, 0x2C, 0x2D]) #fastest refresh, 6 lines front, 3 lines back. + self._writecommand(TFT.FRMCTR1) #Frame rate control. + self._writedata(data3) + + self._writecommand(TFT.FRMCTR2) #Frame rate control. + self._writedata(data3) + + data6 = bytearray([0x01, 0x2c, 0x2d, 0x01, 0x2c, 0x2d]) + self._writecommand(TFT.FRMCTR3) #Frame rate control. + self._writedata(data6) + time.sleep_us(10) + + self._writecommand(TFT.INVCTR) #Display inversion control + self._writedata(bytearray([0x07])) + self._writecommand(TFT.PWCTR1) #Power control + data3[0] = 0xA2 + data3[1] = 0x02 + data3[2] = 0x84 + self._writedata(data3) + + self._writecommand(TFT.PWCTR2) #Power control + self._writedata(bytearray([0xC5])) + + data2 = bytearray(2) + self._writecommand(TFT.PWCTR3) #Power control + data2[0] = 0x0A #Opamp current small + data2[1] = 0x00 #Boost frequency + self._writedata(data2) + + self._writecommand(TFT.PWCTR4) #Power control + data2[0] = 0x8A #Opamp current small + data2[1] = 0x2A #Boost frequency + self._writedata(data2) + + self._writecommand(TFT.PWCTR5) #Power control + data2[0] = 0x8A #Opamp current small + data2[1] = 0xEE #Boost frequency + self._writedata(data2) + + self._writecommand(TFT.VMCTR1) #Power control + self._writedata(bytearray([0x0E])) + + self._writecommand(TFT.INVOFF) + + self._setMADCTL() + + self._writecommand(TFT.COLMOD) + self._writedata(bytearray([0x05])) + + self._writecommand(TFT.CASET) #Column address set. + self.windowLocData[0] = 0x00 + self.windowLocData[1] = 0x01 #Start at row/column 1. + self.windowLocData[2] = 0x00 + self.windowLocData[3] = self._size[0] - 1 + self._writedata(self.windowLocData) + + self._writecommand(TFT.RASET) #Row address set. + self.windowLocData[3] = self._size[1] - 1 + self._writedata(self.windowLocData) + + dataGMCTRP = bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, + 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10]) + self._writecommand(TFT.GMCTRP1) + self._writedata(dataGMCTRP) + + dataGMCTRN = bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e, + 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10]) + self._writecommand(TFT.GMCTRN1) + self._writedata(dataGMCTRN) + + self._writecommand(TFT.NORON) #Normal display on. + time.sleep_us(10) + + self._writecommand(TFT.DISPON) + time.sleep_us(100) + + self.cs(1) + +def maker( ) : + t = TFT(1, "X1", "X2") + print("Initializing") + t.initr() + t.fill(0) + return t + +def makeb( ) : + t = TFT(1, "X1", "X2") + print("Initializing") + t.initb() + t.fill(0) + return t + +def makeg( ) : + t = TFT(1, "X1", "X2") + print("Initializing") + t.initg() + t.fill(0) + return t diff --git a/GW-custom/uPyLoRaWAN/uPySensors/bme680.py b/GW-custom/uPyLoRaWAN/uPySensors/bme680.py new file mode 100644 index 0000000000000000000000000000000000000000..db0347a25e037020112a7757c462598a150802e4 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/bme680.py @@ -0,0 +1,489 @@ +''' +''' +# Power Modes +from machine import I2C, Pin +from micropython import const +from ustruct import unpack as unp +import utime + +epoch_offset = 946684800 + +NORMAL = const(0) + +# BME680 Temperature Registers +BME680_REG_DIG_T1 = const(0xE9) +BME680_REG_DIG_T2 = const(0x8A) +BME680_REG_DIG_T3 = const(0x8C) +# BME680 Pressure Registers +BME680_REG_DIG_P1 = const(0x8E) +BME680_REG_DIG_P2 = const(0x90) +BME680_REG_DIG_P3 = const(0x92) +BME680_REG_DIG_P4 = const(0x94) +BME680_REG_DIG_P5 = const(0x96) +BME680_REG_DIG_P6 = const(0x99) +BME680_REG_DIG_P7 = const(0x98) +BME680_REG_DIG_P8 = const(0x9C) +BME680_REG_DIG_P9 = const(0x9E) +BME680_REG_DIG_P10 = const(0xA0) +# BME680 Humidity Registers +BME680_REG_DIG_H1_LSB = const(0xE1) +BME680_REG_DIG_H1_MSB = const(0xE3) +BME680_REG_DIG_H2_LSB = const(0xE2) +BME680_REG_DIG_H2_MSB = const(0xE1) +BME680_REG_DIG_H3 = const(0xE4) +BME680_REG_DIG_H4 = const(0xE5) +BME680_REG_DIG_H5 = const(0xE6) +BME680_REG_DIG_H6 = const(0xE7) +BME680_REG_DIG_H7 = const(0xE8) +# BME680 Gas Sensor +BME680_REG_DIG_G1 = const(0xED) +BME680_REG_DIG_G2 = const(0xE7) +BME680_REG_DIG_G3 = const(0xEE) + +BME680_REG_ID = const(0xD0) +BME680_NEW_DATA_MSK = const(0x80) +BME680_REG_RESET = const(0xE0) +BME680_RES_HEAT_0 = const(0x5A) +BME680_GAS_WAIT_0 = const(0x64) +BME680_HEAT_STAB_MSK = const(0x10) + +BME680_GAS_INDEX_MSK = const(0x0F) +BME680_GAS_RANGE_MSK = const(0x0F) +BME680_GASM_VALID_MSK = const(0x20) + +BME680_REG_CTRL_GAS = const(0x71) +BME680_REG_CTRL_HUM = const(0x72) +BME680_REG_STATUS = const(0xF3) +BME680_REG_CTRL_MEAS = const(0x74) +BME680_REG_CONFIG = const(0x75) # IIR filter config + +BME680_REG_MEAS_STATUS = const(0x1D) +BME680_REG_PDATA = const(0x1F) +BME680_REG_TDATA = const(0x22) +BME680_REG_HDATA = const(0x25) +BME680_MAX_OVERFLOW_VAL =const(0x40000000) + +BMP680_I2C_ADDR = const(0x77) + +BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16) +BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127) +BME680_RUNGAS = const(0x10) + +_LOOKUP_TABLE_1 = ( + 2147483647.0, + 2147483647.0, + 2147483647.0, + 2147483647.0, + 2147483647.0, + 2126008810.0, + 2147483647.0, + 2130303777.0, + 2147483647.0, + 2147483647.0, + 2143188679.0, + 2136746228.0, + 2147483647.0, + 2126008810.0, + 2147483647.0, + 2147483647.0, +) + +_LOOKUP_TABLE_2 = ( + 4096000000.0, + 2048000000.0, + 1024000000.0, + 512000000.0, + 255744255.0, + 127110228.0, + 64000000.0, + 32258064.0, + 16016016.0, + 8000000.0, + 4000000.0, + 2000000.0, + 1000000.0, + 500000.0, + 250000.0, + 125000.0, +) + +class MPUException(OSError): + ''' + Exception for MPU devices + ''' + pass + +class GasSettings: + def __init__(self): + # Variable to store nb conversion + self.nb_conv = None + # Variable to store heater control + self.heatr_ctrl = None + # Run gas enable value + self.run_gas = None + # Pointer to store heater temperature + self.heatr_temp = None + # Pointer to store duration profile + self.heatr_dur = None + +class TPHSettings: + def __init__(self): + # Humidity oversampling + self.os_hum = None + # Temperature oversampling + self.os_temp = None + # Pressure oversampling + self.os_pres = None + # Filter coefficient + self.filter = None + +class BME680(): + _I2Cerror = "I2C failure when communicating with the BMP/E" + # BME680 = 0x61 + _chip_id = 0x61 + + _i2c_addr = BMP680_I2C_ADDR + + def __init__(self, i2c, pins): + + self._pins = pins + + self._buf1 = bytearray(1) + self._buf2 = bytearray(2) + + scl_pin = Pin(self._pins["scl"], Pin.OUT) + sda_pin = Pin(self._pins["sda"], Pin.IN) + + self._i2c = I2C(i2c, scl=scl_pin, sda=sda_pin) + + self.chip_id + + self._load_calibration() + + self.power_on() + + # Sensor settings + self.tph_settings = TPHSettings() + # Gas Sensor settings + self.gas_settings = GasSettings() + + # Default oversampling and filter register values. + self.tph_settings.os_pres = 0b011 + self.tph_settings.os_temp = 0b100 + self.tph_settings.os_hum = 0b010 + self.tph_settings.filter = 0b010 + + # gas measurements enabled + self._write(BME680_REG_CTRL_GAS, BME680_RUNGAS) + + # RAW measurements + self._p_raw = 0 + self._t_raw = 0 + self._h_raw = 0 + self._g_raw = 0 + + # Calibrated measurements + self._t_fine = 0 + self._t = 0 + self._h = 0 + self._p = 0 + self._g = 0 + + self._g_range = 0 + self._g_stable = 0 + + self._read_wait_ms = 100 + self._new_read_ms = 200 + self._last_read_ts = 0 + + def _read(self, memaddr, size=1): + data = self._i2c.readfrom_mem(self._i2c_addr, memaddr, size) + return data + + def _write(self, addr, b_arr): + if not type(b_arr) is bytearray: + b_arr = bytearray([b_arr]) + return self._i2c.writeto_mem(self._i2c_addr, addr, b_arr) + + def _load_calibration(self): + # read calibration data + # < little-endian + # H unsigned short + # h signed short + self._T1 = unp('<H', self._read(BME680_REG_DIG_T1, 2))[0] + self._T2 = unp('<h', self._read(BME680_REG_DIG_T2, 2))[0] + self._T3 = unp('<b', self._read(BME680_REG_DIG_T3, 1))[0] + + self._P1 = unp('<H', self._read(BME680_REG_DIG_P1, 2))[0] + self._P2 = unp('<h', self._read(BME680_REG_DIG_P2, 2))[0] + self._P3 = unp('<b', self._read(BME680_REG_DIG_P3, 1))[0] + self._P4 = unp('<h', self._read(BME680_REG_DIG_P4, 2))[0] + self._P5 = unp('<h', self._read(BME680_REG_DIG_P5, 2))[0] + self._P6 = unp('<h', self._read(BME680_REG_DIG_P6, 2))[0] + self._P7 = unp('<b', self._read(BME680_REG_DIG_P7, 2))[0] + self._P8 = unp('<h', self._read(BME680_REG_DIG_P8, 2))[0] + self._P9 = unp('<h', self._read(BME680_REG_DIG_P9, 2))[0] + self._P10 = unp('<b', self._read(BME680_REG_DIG_P10, 1))[0] + + self._H1 = ((unp('<b', self._read(BME680_REG_DIG_H1_MSB, 1))[0] << 4) + | (unp('<b', self._read(BME680_REG_DIG_H1_LSB, 1))[0] & 0x0F)) + self._H2 = ((unp('<b', self._read(BME680_REG_DIG_H2_MSB, 1))[0] << 4) + | (unp('<b', self._read(BME680_REG_DIG_H2_LSB, 1))[0] & 0x0F)) + self._H3 = unp('<b', self._read(BME680_REG_DIG_H3, 1))[0] + self._H4 = unp('<b', self._read(BME680_REG_DIG_H4, 1))[0] + self._H5 = unp('<b', self._read(BME680_REG_DIG_H5, 1))[0] + self._H6 = unp('<b', self._read(BME680_REG_DIG_H6, 1))[0] + self._H7 = unp('<b', self._read(BME680_REG_DIG_H7, 1))[0] + + self._G1 = unp('<b', self._read(BME680_REG_DIG_G1, 1))[0] + self._G2 = unp('<h', self._read(BME680_REG_DIG_G2, 2))[0] + self._G3 = unp('<b', self._read(BME680_REG_DIG_G3, 1))[0] + + self._heat_range = (unp('<b', self._read(0x02, 1))[0] & 0x30) / 16 + self._heat_val = unp('<b', self._read(0x00, 1))[0] + self._sw_err = (unp('<b', self._read(0x04, 1))[0] & 0xF0) / 16 + + def print_calibration(self): + print("T1: {} {}".format(self._T1, type(self._T1))) + print("T2: {} {}".format(self._T2, type(self._T2))) + print("T3: {} {}".format(self._T3, type(self._T3))) + print("P1: {} {}".format(self._P1, type(self._P1))) + print("P2: {} {}".format(self._P2, type(self._P2))) + print("P3: {} {}".format(self._P3, type(self._P3))) + print("P4: {} {}".format(self._P4, type(self._P4))) + print("P5: {} {}".format(self._P5, type(self._P5))) + print("P6: {} {}".format(self._P6, type(self._P6))) + print("P7: {} {}".format(self._P7, type(self._P7))) + print("P8: {} {}".format(self._P8, type(self._P8))) + print("P9: {} {}".format(self._P9, type(self._P9))) + print("P10: {} {}".format(self._P10, type(self._P10))) + print("H1: {} {}".format(self._H1, type(self._H1))) + print("H2: {} {}".format(self._H2, type(self._H2))) + print("H3: {} {}".format(self._H3, type(self._H3))) + print("H4: {} {}".format(self._H4, type(self._H4))) + print("H5: {} {}".format(self._H5, type(self._H5))) + print("H6: {} {}".format(self._H6, type(self._H6))) + print("G1: {} {}".format(self._G1, type(self._G1))) + print("G2: {} {}".format(self._G2, type(self._G2))) + print("G3: {} {}".format(self._G3, type(self._G3))) + print("heater_range: {} {}".format(self._heat_range, type(self._heat_range))) + print("heat_val: {} {}".format(self._heat_val, type(self._heat_val))) + print("sw_err: {} {}".format(self._sw_err, type(self._sw_err))) + + def power_off(self): + self._write(0xF4, 0) + + # normal mode + def power_on(self): + self._write(0xF4, 0x2F) + + def _gauge(self): + """Perform a single-shot reading from the sensor and fill internal data structure for + calculations""" + now = utime.ticks_ms() + if utime.ticks_diff(now, self._last_read_ts) > self._new_read_ms: + # set filter + self._write(BME680_REG_CONFIG, self.tph_settings.filter << 2) + # turn on temp oversample & pressure oversample + self._write( + BME680_REG_CTRL_MEAS, + (self.tph_settings.os_temp << 5) | (self.tph_settings.os_pres << 2), + ) + utime.sleep_ms(100) + + # turn on humidity oversample + self._write(BME680_REG_CTRL_HUM, self.tph_settings.os_hum) + + ctrl = unp('<b', self._read(BME680_REG_CTRL_MEAS, 1))[0] + ctrl = (ctrl & 0xFC) | 0x01 # enable single shot! + self._write(BME680_REG_CTRL_MEAS, ctrl) + + data_status = False + while not data_status: + regs = self._read(BME680_REG_MEAS_STATUS, 15) + data_status = regs[0] & BME680_NEW_DATA_MSK != 0 + utime.sleep_ms(5) + + self._last_read_ts = utime.ticks_ms() + + self._p_raw = (regs[2] << 12) | (regs[3] << 4) | (regs[4] >> 4) + self._t_raw = (regs[5] << 12) | (regs[6] << 4) | (regs[7] >> 4) + self._h_raw = (regs[8] << 8) | regs[9] + + self._g_raw = (regs[13] << 2) | (regs[14] >> 6) + self._g_range = regs[14] & BME680_GAS_RANGE_MSK + self._g_stable = (data_status & BME680_HEAT_STAB_MSK) > 0 + + self._t_fine = 0 + self._t = 0 + self._g = 0 + self._h = 0 + self._p = 0 + + def _calc_t_fine(self): + # From datasheet page 22 + self._gauge() + if self._t_fine == 0: + var1 = (((self._t_raw / 16384.0) - (self._T1 / 1024.0)) * self._T2) + var2 = ((((self._t_raw / 131072.0) - (self._T1 / 8192.0)) * + ((self._t_raw / 131072.0) - (self._T1 / 8192.0))) * + (self._T3 * 16.0)) + self._t_fine = var1 + var2 + + self._t = ((self._t_fine * 5) + 128) / 256 / 100 + + + def set_gas_heater_profile(self, temperature, duration, nb_profile=0): + + if nb_profile > 9 or nb_profile < 0: + raise ValueError("Profile '{}' should be between {} and {}".format(nb_profile, 0, 9)) + + # set temperature + self.gas_settings.heatr_temp = temperature + temp = int(self._calc_heater_resistance(temperature)) + self._write(BME680_RES_HEAT_0+ nb_profile, temp) + + # set duration + self.gas_settings.heatr_dur = duration + temp = self._calc_heater_duration(duration) + self._write(BME680_GAS_WAIT_0 + nb_profile, temp) + + + def _calc_heater_resistance(self, temperature): + temperature = min(max(temperature,200),400) + + var1 = ((self._t * self._G3) / 1000) * 256 + var2 = (self._G1 + 784) * (((((self._G2 + 154009) * temperature * 5) / 100) + 3276800) / 10) + var3 = var1 + (var2 / 2) + var4 = (var3 / (self._g_range + 4)) + var5 = (131 * self._heat_val) + 65536 + heatr_res_x100 = (((var4 / var5) - 250) * 34) + heatr_res = ((heatr_res_x100 + 50) / 100) + + return heatr_res + + def _calc_heater_duration(self, duration): + if duration < 0xfc0: + factor = 0 + + while duration > 0x3f: + duration /= 4 + factor += 1 + + return int(duration + (factor * 64)) + + return 0xff + + @property + def gas(self): + """The gas resistance in ohms""" + self._calc_t_fine() + if self._g == 0: + var1 = int( + (1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._g_range]) + ) >> 16 + var2 = ((self._g_raw << 15) - 16777216) + var1 + var3 = int(_LOOKUP_TABLE_2[self._g_range] * var1) >> 9 + self._g = (var3 + (var2 / 2)) / var2 + + return self._g + + @property + def filter_size(self): + """The filter size for the built in IIR filter""" + return BME680_FILTERSIZES[self._filter] + + @property + def humidity(self): + """The relative humidity in RH %""" + self._calc_t_fine() + if self._h == 0: + temp_scaled = ((self._t_fine * 5) + 128) / 256 + var1 = (self._h_raw - (self._H1 * 16)) - ( + (temp_scaled * self._H3) / 200 + ) + var2 = ( + self._H2 + * ( + ((temp_scaled * self._H4) / 100) + + ( + ( + ( + temp_scaled + * ((temp_scaled * self._H5) / 100) + ) + / 64 + ) + / 100 + ) + + 16384 + ) + ) / 1024 + var3 = var1 * var2 + var4 = self._H6 * 128 + var4 = (var4 + ((temp_scaled * self._H7) / 100)) / 16 + var5 = ((var3 / 16384) * (var3 / 16384)) / 1024 + var6 = (var4 * var5) / 2 + calc_hum = (((var3 + var6) / 1024) * 1000) / 4096 + self._h = calc_hum / 1000 # get back to RH + return self._h + + @property + def temperature(self): + self._calc_t_fine() + self._t = ((self._t_fine * 5) + 128) / 256 / 100 + return self._t + + @property + def pressure(self): + self._calc_t_fine() + if self._p == 0: + var1 = (int(self._t_fine) >> 1) - 64000 + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * self._P6) >> 2 + var2 = var2 + ((var1 * self._P5) << 1) + var2 = (var2 >> 2) + (self._P4 << 16) + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * \ + (self._P3 << 5)) >> 3) + ((self._P2 * var1) >> 1) + var1 = var1 >> 18 + var1 = ((32768 + var1) * self._P1) >> 15 + pressure_comp = 1048576 - self._p_raw + pressure_comp = int((pressure_comp - (var2 >> 12)) * 3125) + + if pressure_comp >= BME680_MAX_OVERFLOW_VAL: + pressure_comp = (int(pressure_comp / var1) << 1) + else: + pressure_comp = (pressure_comp << 1) / var1 + + + var1 = (self._P9 * ((pressure_comp >> 3) * \ + (pressure_comp >> 3)) >> 13) >> 12 + var2 = ((pressure_comp >> 2) * self._P8) >> 13 + var3 = ((pressure_comp >> 8) * (pressure_comp >> 8) * \ + (pressure_comp >> 8) * self._P10) >> 17 + + self._p = (pressure_comp + ((var1 + var2 + var3 + (self._P7 << 7)) >> 4)) / 100 + + return self._p + + @property + def measurements(self): + d = {} + d['time'] = utime.time() + epoch_offset + d['temp'] = self.temperature + d['hum'] = self.humidity + d['press'] = self.pressure + d['gas'] = self.gas + return d + + @property + def chip_id(self): + ''' + Returns Chip ID + ''' + try: + chip_id = unp('<b',self._read(const(0xD0), 1))[0] + except OSError: + raise MPUException(self._I2Cerror) + if chip_id != self._chip_id: + raise ValueError('Bad chip ID ({0}!={1}) retrieved: MPU communication failure'.format(chip_id, self._chip_id)) + return chip_id \ No newline at end of file diff --git a/GW-custom/uPyLoRaWAN/uPySensors/bmx280.py b/GW-custom/uPyLoRaWAN/uPySensors/bmx280.py new file mode 100644 index 0000000000000000000000000000000000000000..450ec399bc6aeb2eafac0aa310842ba724a66c54 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/bmx280.py @@ -0,0 +1,263 @@ +''' +''' +# Power Modes +from machine import I2C, Pin +from micropython import const +from ustruct import unpack as unp +import utime + +NORMAL = const(0) + +BMX280_TEMP_OS_SKIP = const(0) +BMX280_TEMP_OS_1 = const(1) +BMX280_TEMP_OS_2 = const(2) +BMX280_TEMP_OS_4 = const(3) +BMX280_TEMP_OS_8 = const(4) +BMX280_TEMP_OS_16 = const(5) + +BMX280_PRES_OS_SKIP = const(0) +BMX280_PRES_OS_1 = const(1) +BMX280_PRES_OS_2 = const(2) +BMX280_PRES_OS_4 = const(3) +BMX280_PRES_OS_8 = const(4) +BMX280_PRES_OS_16 = const(5) + +# BMP280 Temperature Registers +BMX280_REGISTER_DIG_T1 = const(0x88) +BMX280_REGISTER_DIG_T2 = const(0x8A) +BMX280_REGISTER_DIG_T3 = const(0x8C) +# BMP280 Pressure Registers +BMX280_REGISTER_DIG_P1 = const(0x8E) +BMX280_REGISTER_DIG_P2 = const(0x90) +BMX280_REGISTER_DIG_P3 = const(0x92) +BMX280_REGISTER_DIG_P4 = const(0x94) +BMX280_REGISTER_DIG_P5 = const(0x96) +BMX280_REGISTER_DIG_P6 = const(0x98) +BMX280_REGISTER_DIG_P7 = const(0x9A) +BMX280_REGISTER_DIG_P8 = const(0x9C) +BMX280_REGISTER_DIG_P9 = const(0x9E) + +BME280_REGISTER_DIG_H1 = const(0xA1) +BME280_REGISTER_DIG_H2 = const(0xE1) +BME280_REGISTER_DIG_H3 = const(0xE3) +BME280_REGISTER_DIG_H4 = const(0xE4) +BME280_REGISTER_DIG_H5 = const(0xE5) +BME280_REGISTER_DIG_H6 = const(0xE6) +BME280_REGISTER_DIG_H7 = const(0xE7) + +BMX280_REGISTER_ID = const(0xD0) +BMX280_REGISTER_RESET = const(0xE0) +BMX280_REGISTER_STATUS = const(0xF3) +BMX280_REGISTER_CONTROL = const(0xF4) +BMX280_REGISTER_CONFIG = const(0xF5) # IIR filter config + +BMX280_REGISTER_DATA = const(0xF7) + +BMX280_I2C_ADDR = const(0x76) + +class MPUException(OSError): + ''' + Exception for MPU devices + ''' + pass + +class BMX280(): + _I2Cerror = "I2C failure when communicating with the BMP/E" + # BMP280 = 0x58, BME280 = 0x60, BME680 = 0x61 + _chip_id = 0x58 + + _i2c_addr = BMX280_I2C_ADDR + + def __init__(self, i2c, pins): + + self._pins = pins + + self._buf1 = bytearray(1) + self._buf2 = bytearray(2) + + scl_pin = Pin(self._pins["scl"], Pin.OUT) + sda_pin = Pin(self._pins["sda"], Pin.IN) + + self._i2c = I2C(i2c, scl=scl_pin, sda=sda_pin) + + self.chip_id + + self._load_calibration() + + self._t_os = BMX280_TEMP_OS_2 # temperature oversampling + self._p_os = BMX280_PRES_OS_16 # pressure oversampling + + self._t_raw = 0 + self._t_fine = 0 + self._t = 0 + + self._p_raw = 0 + self._p = 0 + + self._read_wait_ms = 100 + self._new_read_ms = 200 + self._last_read_ts = 0 + + + def _read(self, memaddr, size=1): + data = self._i2c.readfrom_mem(self._i2c_addr, memaddr, size) + return data + + def _write(self, addr, b_arr): + if not type(b_arr) is bytearray: + b_arr = bytearray([b_arr]) + return self._i2c.writeto_mem(self._i2c_addr, addr, b_arr) + + def _load_calibration(self): + # read calibration data + # < little-endian + # H unsigned short + # h signed short + self._T1 = unp('<H', self._read(BMX280_REGISTER_DIG_T1, 2))[0] + self._T2 = unp('<h', self._read(BMX280_REGISTER_DIG_T2, 2))[0] + self._T3 = unp('<h', self._read(BMX280_REGISTER_DIG_T3, 2))[0] + self._P1 = unp('<H', self._read(BMX280_REGISTER_DIG_P1, 2))[0] + self._P2 = unp('<h', self._read(BMX280_REGISTER_DIG_P2, 2))[0] + self._P3 = unp('<h', self._read(BMX280_REGISTER_DIG_P3, 2))[0] + self._P4 = unp('<h', self._read(BMX280_REGISTER_DIG_P4, 2))[0] + self._P5 = unp('<h', self._read(BMX280_REGISTER_DIG_P5, 2))[0] + self._P6 = unp('<h', self._read(BMX280_REGISTER_DIG_P6, 2))[0] + self._P7 = unp('<h', self._read(BMX280_REGISTER_DIG_P7, 2))[0] + self._P8 = unp('<h', self._read(BMX280_REGISTER_DIG_P8, 2))[0] + self._P9 = unp('<h', self._read(BMX280_REGISTER_DIG_P9, 2))[0] + + if self._chip_id != 0x58: + self._H1 = unp('<b', self._read(BME280_REGISTER_DIG_H1, 1))[0] + self._H2 = unp('<h', self._read(BME280_REGISTER_DIG_H2, 2))[0] + self._H3 = unp('<b', self._read(BME280_REGISTER_DIG_H3, 1))[0] + self._H6 = unp('<b', self._read(BME280_REGISTER_DIG_H7, 1))[0] + + h4 = unp('<b', self._read(BME280_REGISTER_DIG_H4, 1))[0] + h4 = (h4 << 24) >> 20 + self._H4 = h4 | ( + unp('<b', self._read(BME280_REGISTER_DIG_H5, 1))[0] & 0x0F) + + h5 = unp('<b', self._read(BME280_REGISTER_DIG_H6, 1))[0] + h5 = (h5 << 24) >> 20 + self._H5 = h5 | ( + unp('<b', self._read(BME280_REGISTER_DIG_H5, 1))[0] >> 4 & 0x0F) + + + def print_calibration(self): + print("T1: {} {}".format(self._T1, type(self._T1))) + print("T2: {} {}".format(self._T2, type(self._T2))) + print("T3: {} {}".format(self._T3, type(self._T3))) + print("P1: {} {}".format(self._P1, type(self._P1))) + print("P2: {} {}".format(self._P2, type(self._P2))) + print("P3: {} {}".format(self._P3, type(self._P3))) + print("P4: {} {}".format(self._P4, type(self._P4))) + print("P5: {} {}".format(self._P5, type(self._P5))) + print("P6: {} {}".format(self._P6, type(self._P6))) + print("P7: {} {}".format(self._P7, type(self._P7))) + print("P8: {} {}".format(self._P8, type(self._P8))) + print("P9: {} {}".format(self._P9, type(self._P9))) + if self._chip_id != 0x58: + print("H1: {} {}".format(self._H1, type(self._H1))) + print("H2: {} {}".format(self._H2, type(self._H2))) + print("H3: {} {}".format(self._H3, type(self._H3))) + print("H4: {} {}".format(self._H4, type(self._H4))) + print("H5: {} {}".format(self._H5, type(self._H5))) + print("H6: {} {}".format(self._H6, type(self._H6))) + + def power_off(self): + self._write(0xF4, 0) + + # normal mode + def power_on(self): + self._write(0xF4, 0x2F) + + def _gauge(self): + now = utime.ticks_ms() + if utime.ticks_diff(now, self._last_read_ts) > self._new_read_ms: + self._last_read_ts = now + r = self._t_os + (self._p_os << 3) + (1 << 6) + self._write(BMX280_REGISTER_CONTROL, r) + utime.sleep_ms(100) # TODO calc sleep + if self._chip_id == 0x58: + d = self._read(BMX280_REGISTER_DATA, 6) # read all data at once (as by spec) + self._p_raw = (d[0] << 12) + (d[1] << 4) + (d[2] >> 4) + self._t_raw = (d[3] << 12) + (d[4] << 4) + (d[5] >> 4) + else: + d = self._read(BMX280_REGISTER_DATA, 8) # read all data at once (as by spec) + self._p_raw = (d[0] << 12) + (d[1] << 4) + (d[2] >> 4) + self._t_raw = (d[3] << 12) + (d[4] << 4) + (d[5] >> 4) + self._h_raw = (d[6] << 8) + d[7] + + self._t_fine = 0 + self._t = 0 + self._h = 0 + self._p = 0 + + def _calc_t_fine(self): + # From datasheet page 22 + self._gauge() + if self._t_fine == 0: + var1 = (((self._t_raw >> 3) - (self._T1 << 1)) * self._T2) >> 11 + var2 = (((((self._t_raw >> 4) - self._T1) * ((self._t_raw >> 4) - self._T1)) >> 12) * self._T3) >> 14 + self._t_fine = var1 + var2 + + @property + def humidity(self): + if self._chip_id != 0x58: + var1 = self._calc_t_fine - 76800 + var1 = (((((self._h_raw << 14) - (self._H5 << 20) - (self._H5 * var1)) + + 16384) >> 15) * (((((((var1 * self._H6) >> 10) * (((var1 * + self._H3) >> 11) + 32768)) >> 10) + 2097152) * + self._H2 + 8192) >> 14)) + var1 = var1 - (((((var1 >> 15) * (var1 >> 15)) >> 7) * self._H1) >> 4) + var1 = 0 if var1 < 0 else var1 + var1 = 419430400 if var1 > 419430400 else var1 + return var1 >> 12 + else: + print("This is a BMP not a BME, therefore it cannot measure humidity! :(") + return 0 + + @property + def temperature(self): + self._calc_t_fine() + if self._t == 0: + self._t = ((self._t_fine * 5 + 128) >> 8) / 100. + return self._t + + @property + def pressure(self): + # From datasheet page 22 (BMP) /25 (BME) + self._calc_t_fine() + if self._p == 0: + var1 = self._t_fine - 128000 + var2 = var1 * var1 * self._P6 + var2 = var2 + ((var1 * self._P5) << 17) + var2 = var2 + (self._P4 << 35) + var1 = ((var1 * var1 * self._P3) >> 8) + ((var1 * self._P2) << 12) + var1 = (((1 << 47) + var1) * self._P1) >> 33 + + if var1 == 0: + return 0 + + p = 1048576 - self._p_raw + p = int((((p << 31) - var2) * 3125) / var1) + var1 = (self._P9 * (p >> 13) * (p >> 13)) >> 25 + var2 = (self._P8 * p) >> 19 + + p = ((p + var1 + var2) >> 8) + (self._P7 << 4) + self._p = p / 256.0 + return self._p + + + @property + def chip_id(self): + ''' + Returns Chip ID + ''' + try: + chip_id = unp('<b',self._read(const(0xD0), 1))[0] + except OSError: + raise MPUException(self._I2Cerror) + if chip_id != self._chip_id: + raise ValueError('Bad chip ID ({0}!={1}) retrieved: MPU communication failure'.format(chip_id, self._chip_id)) + return chip_id \ No newline at end of file diff --git a/GW-custom/uPyLoRaWAN/uPySensors/fonts/glcdfont.py b/GW-custom/uPyLoRaWAN/uPySensors/fonts/glcdfont.py new file mode 100644 index 0000000000000000000000000000000000000000..c76e4d6a1de114088eba68190ceda0b87f417e5e --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/fonts/glcdfont.py @@ -0,0 +1,294 @@ +# Original Adafruit_GFX 5x7 font + +def height(): + return 8 + +def max_width(): + return 6 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return True + +def min_ch(): + return 0 + +def max_ch(): + return 255 + + +_font = \ + b'\x00\x00\x00\x00\x00'\ + b'\x3E\x5B\x4F\x5B\x3E'\ + b'\x3E\x6B\x4F\x6B\x3E'\ + b'\x1C\x3E\x7C\x3E\x1C'\ + b'\x18\x3C\x7E\x3C\x18'\ + b'\x1C\x57\x7D\x57\x1C'\ + b'\x1C\x5E\x7F\x5E\x1C'\ + b'\x00\x18\x3C\x18\x00'\ + b'\xFF\xE7\xC3\xE7\xFF'\ + b'\x00\x18\x24\x18\x00'\ + b'\xFF\xE7\xDB\xE7\xFF'\ + b'\x30\x48\x3A\x06\x0E'\ + b'\x26\x29\x79\x29\x26'\ + b'\x40\x7F\x05\x05\x07'\ + b'\x40\x7F\x05\x25\x3F'\ + b'\x5A\x3C\xE7\x3C\x5A'\ + b'\x7F\x3E\x1C\x1C\x08'\ + b'\x08\x1C\x1C\x3E\x7F'\ + b'\x14\x22\x7F\x22\x14'\ + b'\x5F\x5F\x00\x5F\x5F'\ + b'\x06\x09\x7F\x01\x7F'\ + b'\x00\x66\x89\x95\x6A'\ + b'\x60\x60\x60\x60\x60'\ + b'\x94\xA2\xFF\xA2\x94'\ + b'\x08\x04\x7E\x04\x08'\ + b'\x10\x20\x7E\x20\x10'\ + b'\x08\x08\x2A\x1C\x08'\ + b'\x08\x1C\x2A\x08\x08'\ + b'\x1E\x10\x10\x10\x10'\ + b'\x0C\x1E\x0C\x1E\x0C'\ + b'\x30\x38\x3E\x38\x30'\ + b'\x06\x0E\x3E\x0E\x06'\ + b'\x00\x00\x00\x00\x00'\ + b'\x00\x00\x5F\x00\x00'\ + b'\x00\x07\x00\x07\x00'\ + b'\x14\x7F\x14\x7F\x14'\ + b'\x24\x2A\x7F\x2A\x12'\ + b'\x23\x13\x08\x64\x62'\ + b'\x36\x49\x56\x20\x50'\ + b'\x00\x08\x07\x03\x00'\ + b'\x00\x1C\x22\x41\x00'\ + b'\x00\x41\x22\x1C\x00'\ + b'\x2A\x1C\x7F\x1C\x2A'\ + b'\x08\x08\x3E\x08\x08'\ + b'\x00\x80\x70\x30\x00'\ + b'\x08\x08\x08\x08\x08'\ + b'\x00\x00\x60\x60\x00'\ + b'\x20\x10\x08\x04\x02'\ + b'\x3E\x51\x49\x45\x3E'\ + b'\x00\x42\x7F\x40\x00'\ + b'\x72\x49\x49\x49\x46'\ + b'\x21\x41\x49\x4D\x33'\ + b'\x18\x14\x12\x7F\x10'\ + b'\x27\x45\x45\x45\x39'\ + b'\x3C\x4A\x49\x49\x31'\ + b'\x41\x21\x11\x09\x07'\ + b'\x36\x49\x49\x49\x36'\ + b'\x46\x49\x49\x29\x1E'\ + b'\x00\x00\x14\x00\x00'\ + b'\x00\x40\x34\x00\x00'\ + b'\x00\x08\x14\x22\x41'\ + b'\x14\x14\x14\x14\x14'\ + b'\x00\x41\x22\x14\x08'\ + b'\x02\x01\x59\x09\x06'\ + b'\x3E\x41\x5D\x59\x4E'\ + b'\x7C\x12\x11\x12\x7C'\ + b'\x7F\x49\x49\x49\x36'\ + b'\x3E\x41\x41\x41\x22'\ + b'\x7F\x41\x41\x41\x3E'\ + b'\x7F\x49\x49\x49\x41'\ + b'\x7F\x09\x09\x09\x01'\ + b'\x3E\x41\x41\x51\x73'\ + b'\x7F\x08\x08\x08\x7F'\ + b'\x00\x41\x7F\x41\x00'\ + b'\x20\x40\x41\x3F\x01'\ + b'\x7F\x08\x14\x22\x41'\ + b'\x7F\x40\x40\x40\x40'\ + b'\x7F\x02\x1C\x02\x7F'\ + b'\x7F\x04\x08\x10\x7F'\ + b'\x3E\x41\x41\x41\x3E'\ + b'\x7F\x09\x09\x09\x06'\ + b'\x3E\x41\x51\x21\x5E'\ + b'\x7F\x09\x19\x29\x46'\ + b'\x26\x49\x49\x49\x32'\ + b'\x03\x01\x7F\x01\x03'\ + b'\x3F\x40\x40\x40\x3F'\ + b'\x1F\x20\x40\x20\x1F'\ + b'\x3F\x40\x38\x40\x3F'\ + b'\x63\x14\x08\x14\x63'\ + b'\x03\x04\x78\x04\x03'\ + b'\x61\x59\x49\x4D\x43'\ + b'\x00\x7F\x41\x41\x41'\ + b'\x02\x04\x08\x10\x20'\ + b'\x00\x41\x41\x41\x7F'\ + b'\x04\x02\x01\x02\x04'\ + b'\x40\x40\x40\x40\x40'\ + b'\x00\x03\x07\x08\x00'\ + b'\x20\x54\x54\x78\x40'\ + b'\x7F\x28\x44\x44\x38'\ + b'\x38\x44\x44\x44\x28'\ + b'\x38\x44\x44\x28\x7F'\ + b'\x38\x54\x54\x54\x18'\ + b'\x00\x08\x7E\x09\x02'\ + b'\x18\xA4\xA4\x9C\x78'\ + b'\x7F\x08\x04\x04\x78'\ + b'\x00\x44\x7D\x40\x00'\ + b'\x20\x40\x40\x3D\x00'\ + b'\x7F\x10\x28\x44\x00'\ + b'\x00\x41\x7F\x40\x00'\ + b'\x7C\x04\x78\x04\x78'\ + b'\x7C\x08\x04\x04\x78'\ + b'\x38\x44\x44\x44\x38'\ + b'\xFC\x18\x24\x24\x18'\ + b'\x18\x24\x24\x18\xFC'\ + b'\x7C\x08\x04\x04\x08'\ + b'\x48\x54\x54\x54\x24'\ + b'\x04\x04\x3F\x44\x24'\ + b'\x3C\x40\x40\x20\x7C'\ + b'\x1C\x20\x40\x20\x1C'\ + b'\x3C\x40\x30\x40\x3C'\ + b'\x44\x28\x10\x28\x44'\ + b'\x4C\x90\x90\x90\x7C'\ + b'\x44\x64\x54\x4C\x44'\ + b'\x00\x08\x36\x41\x00'\ + b'\x00\x00\x77\x00\x00'\ + b'\x00\x41\x36\x08\x00'\ + b'\x02\x01\x02\x04\x02'\ + b'\x3C\x26\x23\x26\x3C'\ + b'\x1E\xA1\xA1\x61\x12'\ + b'\x3A\x40\x40\x20\x7A'\ + b'\x38\x54\x54\x55\x59'\ + b'\x21\x55\x55\x79\x41'\ + b'\x21\x54\x54\x78\x41'\ + b'\x21\x55\x54\x78\x40'\ + b'\x20\x54\x55\x79\x40'\ + b'\x0C\x1E\x52\x72\x12'\ + b'\x39\x55\x55\x55\x59'\ + b'\x39\x54\x54\x54\x59'\ + b'\x39\x55\x54\x54\x58'\ + b'\x00\x00\x45\x7C\x41'\ + b'\x00\x02\x45\x7D\x42'\ + b'\x00\x01\x45\x7C\x40'\ + b'\xF0\x29\x24\x29\xF0'\ + b'\xF0\x28\x25\x28\xF0'\ + b'\x7C\x54\x55\x45\x00'\ + b'\x20\x54\x54\x7C\x54'\ + b'\x7C\x0A\x09\x7F\x49'\ + b'\x32\x49\x49\x49\x32'\ + b'\x32\x48\x48\x48\x32'\ + b'\x32\x4A\x48\x48\x30'\ + b'\x3A\x41\x41\x21\x7A'\ + b'\x3A\x42\x40\x20\x78'\ + b'\x00\x9D\xA0\xA0\x7D'\ + b'\x39\x44\x44\x44\x39'\ + b'\x3D\x40\x40\x40\x3D'\ + b'\x3C\x24\xFF\x24\x24'\ + b'\x48\x7E\x49\x43\x66'\ + b'\x2B\x2F\xFC\x2F\x2B'\ + b'\xFF\x09\x29\xF6\x20'\ + b'\xC0\x88\x7E\x09\x03'\ + b'\x20\x54\x54\x79\x41'\ + b'\x00\x00\x44\x7D\x41'\ + b'\x30\x48\x48\x4A\x32'\ + b'\x38\x40\x40\x22\x7A'\ + b'\x00\x7A\x0A\x0A\x72'\ + b'\x7D\x0D\x19\x31\x7D'\ + b'\x26\x29\x29\x2F\x28'\ + b'\x26\x29\x29\x29\x26'\ + b'\x30\x48\x4D\x40\x20'\ + b'\x38\x08\x08\x08\x08'\ + b'\x08\x08\x08\x08\x38'\ + b'\x2F\x10\xC8\xAC\xBA'\ + b'\x2F\x10\x28\x34\xFA'\ + b'\x00\x00\x7B\x00\x00'\ + b'\x08\x14\x2A\x14\x22'\ + b'\x22\x14\x2A\x14\x08'\ + b'\xAA\x00\x55\x00\xAA'\ + b'\xAA\x55\xAA\x55\xAA'\ + b'\x00\x00\x00\xFF\x00'\ + b'\x10\x10\x10\xFF\x00'\ + b'\x14\x14\x14\xFF\x00'\ + b'\x10\x10\xFF\x00\xFF'\ + b'\x10\x10\xF0\x10\xF0'\ + b'\x14\x14\x14\xFC\x00'\ + b'\x14\x14\xF7\x00\xFF'\ + b'\x00\x00\xFF\x00\xFF'\ + b'\x14\x14\xF4\x04\xFC'\ + b'\x14\x14\x17\x10\x1F'\ + b'\x10\x10\x1F\x10\x1F'\ + b'\x14\x14\x14\x1F\x00'\ + b'\x10\x10\x10\xF0\x00'\ + b'\x00\x00\x00\x1F\x10'\ + b'\x10\x10\x10\x1F\x10'\ + b'\x10\x10\x10\xF0\x10'\ + b'\x00\x00\x00\xFF\x10'\ + b'\x10\x10\x10\x10\x10'\ + b'\x10\x10\x10\xFF\x10'\ + b'\x00\x00\x00\xFF\x14'\ + b'\x00\x00\xFF\x00\xFF'\ + b'\x00\x00\x1F\x10\x17'\ + b'\x00\x00\xFC\x04\xF4'\ + b'\x14\x14\x17\x10\x17'\ + b'\x14\x14\xF4\x04\xF4'\ + b'\x00\x00\xFF\x00\xF7'\ + b'\x14\x14\x14\x14\x14'\ + b'\x14\x14\xF7\x00\xF7'\ + b'\x14\x14\x14\x17\x14'\ + b'\x10\x10\x1F\x10\x1F'\ + b'\x14\x14\x14\xF4\x14'\ + b'\x10\x10\xF0\x10\xF0'\ + b'\x00\x00\x1F\x10\x1F'\ + b'\x00\x00\x00\x1F\x14'\ + b'\x00\x00\x00\xFC\x14'\ + b'\x00\x00\xF0\x10\xF0'\ + b'\x10\x10\xFF\x10\xFF'\ + b'\x14\x14\x14\xFF\x14'\ + b'\x10\x10\x10\x1F\x00'\ + b'\x00\x00\x00\xF0\x10'\ + b'\xFF\xFF\xFF\xFF\xFF'\ + b'\xF0\xF0\xF0\xF0\xF0'\ + b'\xFF\xFF\xFF\x00\x00'\ + b'\x00\x00\x00\xFF\xFF'\ + b'\x0F\x0F\x0F\x0F\x0F'\ + b'\x38\x44\x44\x38\x44'\ + b'\x7C\x2A\x2A\x3E\x14'\ + b'\x7E\x02\x02\x06\x06'\ + b'\x02\x7E\x02\x7E\x02'\ + b'\x63\x55\x49\x41\x63'\ + b'\x38\x44\x44\x3C\x04'\ + b'\x40\x7E\x20\x1E\x20'\ + b'\x06\x02\x7E\x02\x02'\ + b'\x99\xA5\xE7\xA5\x99'\ + b'\x1C\x2A\x49\x2A\x1C'\ + b'\x4C\x72\x01\x72\x4C'\ + b'\x30\x4A\x4D\x4D\x30'\ + b'\x30\x48\x78\x48\x30'\ + b'\xBC\x62\x5A\x46\x3D'\ + b'\x3E\x49\x49\x49\x00'\ + b'\x7E\x01\x01\x01\x7E'\ + b'\x2A\x2A\x2A\x2A\x2A'\ + b'\x44\x44\x5F\x44\x44'\ + b'\x40\x51\x4A\x44\x40'\ + b'\x40\x44\x4A\x51\x40'\ + b'\x00\x00\xFF\x01\x03'\ + b'\xE0\x80\xFF\x00\x00'\ + b'\x08\x08\x6B\x6B\x08'\ + b'\x36\x12\x36\x24\x36'\ + b'\x06\x0F\x09\x0F\x06'\ + b'\x00\x00\x18\x18\x00'\ + b'\x00\x00\x10\x10\x00'\ + b'\x30\x40\xFF\x01\x01'\ + b'\x00\x1F\x01\x01\x1E'\ + b'\x00\x19\x1D\x17\x12'\ + b'\x00\x3C\x3C\x3C\x3C'\ + b'\x00\x00\x00\x00\x00' + +_mvfont = memoryview(_font) + +def get_width(s): + return len(s)*6 + +def get_ch(ch): + ordch = ord(ch) + offset = ordch*5 + buf = bytearray(6) + buf[0] = 0 + buf[1:]=_mvfont[offset:offset+5] + return buf, 6 + diff --git a/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt14.py b/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt14.py new file mode 100644 index 0000000000000000000000000000000000000000..60100b11fc5c4c1172a15c59d3fbde3f4bf2eec1 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt14.py @@ -0,0 +1,159 @@ +# Code generated by font-to-py.py. +# Font: CM Sans Serif 2012.ttf +version = '0.2' + +def height(): + return 15 + +def max_width(): + return 12 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x08\x00\x08\x00\x0c\x00\x8c\x07\xcc\x07\xcc\x00\x7c\x00\x38\x00'\ +b'\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\xfc\x06'\ +b'\xfc\x06\x00\x00\x05\x00\x1c\x00\x3c\x00\x00\x00\x3c\x00\x00\x00'\ +b'\x07\x00\x40\x03\xf0\x03\xf0\x03\x30\x03\xf0\x03\x30\x03\x30\x00'\ +b'\x07\x00\x30\x00\x78\x03\x6c\x02\xfc\x07\xd8\x03\x90\x03\x00\x00'\ +b'\x0b\x00\x18\x00\x3c\x00\x3c\x04\x3c\x03\xd8\x01\xf0\x03\xd8\x07'\ +b'\xc4\x06\xc0\x07\x80\x03\x00\x00\x09\x00\x80\x03\xf8\x07\x7c\x06'\ +b'\x4c\x06\xfc\x06\x98\x07\xc0\x07\xc0\x05\x00\x04\x02\x00\x1c\x00'\ +b'\x3c\x00\x04\x00\xf0\x03\xfc\x0f\x0e\x1c\x02\x10\x05\x00\x02\x10'\ +b'\x0e\x1c\xfc\x0f\xf0\x03\x00\x00\x05\x00\x0c\x00\x3c\x00\x18\x00'\ +b'\x2c\x00\x0c\x00\x07\x00\xc0\x00\xc0\x00\xf0\x03\xf0\x03\xc0\x00'\ +b'\xc0\x00\x00\x00\x03\x00\x00\x16\x00\x0e\x00\x00\x04\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\x03\x00\x00\x06\x00\x06\x00\x00\x04\x00'\ +b'\x00\x06\xe0\x07\xfc\x00\x0e\x00\x08\x00\xf0\x01\xf8\x03\x1c\x07'\ +b'\x0c\x06\x1c\x07\xf8\x03\xf0\x01\x00\x00\x04\x00\x18\x00\xfc\x07'\ +b'\xfc\x07\x00\x00\x08\x00\x18\x04\x1c\x07\x8c\x06\x8c\x06\x4c\x06'\ +b'\x7c\x06\x38\x06\x00\x00\x07\x00\x08\x02\x0c\x06\x0c\x06\x6c\x06'\ +b'\x6c\x06\xfc\x07\xd8\x03\x07\x00\x80\x01\xe0\x01\xb0\x01\x9c\x01'\ +b'\xfc\x07\xfc\x07\x80\x01\x07\x00\x00\x02\xfc\x06\x6c\x06\x6c\x06'\ +b'\x6c\x06\xcc\x03\xc0\x03\x08\x00\xf0\x01\xf8\x03\x6c\x06\x6c\x06'\ +b'\x6c\x06\xe8\x07\xc0\x03\x00\x00\x06\x00\x0c\x00\x0c\x07\xcc\x07'\ +b'\xfc\x00\x1c\x00\x0c\x00\x07\x00\x98\x03\xfc\x07\x6c\x06\x6c\x06'\ +b'\x6c\x06\xfc\x07\x98\x03\x08\x00\x78\x02\xfc\x06\xcc\x06\xcc\x06'\ +b'\xcc\x06\xf8\x03\xf0\x01\x00\x00\x03\x00\x30\x06\x30\x06\x00\x00'\ +b'\x03\x00\x30\x16\x30\x0e\x00\x00\x06\x00\xc0\x01\xc0\x01\x60\x03'\ +b'\x60\x03\x30\x06\x00\x00\x06\x00\x60\x03\x60\x03\x60\x03\x60\x03'\ +b'\x60\x03\x60\x03\x06\x00\x30\x06\x60\x03\x60\x03\x40\x01\xc0\x01'\ +b'\x00\x00\x08\x00\x08\x00\x0c\x00\x8c\x07\xcc\x07\xcc\x00\x7c\x00'\ +b'\x38\x00\x00\x00\x0b\x00\xe0\x00\xf8\x01\x18\x03\xec\x07\xbc\x07'\ +b'\xfc\x06\xfc\x07\x9c\x03\xf8\x01\xf0\x00\x00\x00\x08\x00\x00\x06'\ +b'\x80\x07\xf0\x01\x9c\x01\x8c\x01\xf8\x01\xc0\x07\x00\x07\x08\x00'\ +b'\xfc\x07\xfc\x07\x6c\x06\x6c\x06\x6c\x06\xfc\x07\xd8\x03\x00\x00'\ +b'\x09\x00\xe0\x00\xf8\x03\x18\x03\x0c\x06\x0c\x06\x0c\x06\x1c\x07'\ +b'\x18\x03\x20\x01\x09\x00\xfc\x07\xfc\x07\x0c\x06\x0c\x06\x1c\x07'\ +b'\xf8\x03\xf0\x01\x00\x00\x00\x00\x08\x00\xfc\x07\xfc\x07\x6c\x06'\ +b'\x6c\x06\x6c\x06\x6c\x06\x0c\x06\x00\x00\x07\x00\xfc\x07\xfc\x07'\ +b'\xcc\x00\xcc\x00\xcc\x00\xcc\x00\x0c\x00\x0a\x00\xe0\x01\xf8\x03'\ +b'\x18\x07\x0c\x06\x0c\x06\xcc\x06\xdc\x02\xd8\x07\xe0\x07\x00\x00'\ +b'\x09\x00\xfc\x07\xfc\x07\x60\x00\x60\x00\x60\x00\xfc\x07\xfc\x07'\ +b'\x00\x00\x00\x00\x03\x00\xfc\x07\xfc\x07\x00\x00\x07\x00\x80\x03'\ +b'\x80\x07\x00\x06\x00\x06\xfc\x07\xfc\x03\x00\x00\x08\x00\xfc\x07'\ +b'\xfc\x07\x60\x00\xf0\x00\xd8\x01\x0c\x07\x04\x06\x04\x04\x07\x00'\ +b'\xfc\x07\xfc\x07\x00\x06\x00\x06\x00\x06\x00\x06\x00\x00\x0b\x00'\ +b'\xfc\x07\xfc\x07\x3c\x00\xe0\x03\x00\x06\xe0\x03\x3c\x00\xfc\x07'\ +b'\xfc\x07\x00\x00\x00\x00\x09\x00\xfc\x07\xfc\x07\x38\x00\xe0\x00'\ +b'\x80\x03\xfc\x07\xfc\x07\x00\x00\x00\x00\x0a\x00\xf0\x01\xf8\x03'\ +b'\x1c\x07\x0c\x06\x0c\x06\x0c\x06\x1c\x07\xf8\x03\xf0\x01\x00\x00'\ +b'\x09\x00\xfc\x07\xfc\x07\xcc\x00\xcc\x00\xcc\x00\xfc\x00\x78\x00'\ +b'\x00\x00\x00\x00\x0a\x00\xf0\x01\xf8\x03\x1c\x07\x0c\x06\x8c\x06'\ +b'\x8c\x07\x1c\x07\xf8\x07\xf0\x05\x00\x00\x09\x00\xfc\x07\xfc\x07'\ +b'\xcc\x00\xcc\x00\xcc\x03\x7c\x07\x78\x04\x00\x00\x00\x00\x09\x00'\ +b'\x38\x01\x3c\x03\x4c\x06\x4c\x06\x4c\x06\x4c\x06\x98\x07\x90\x03'\ +b'\x00\x00\x07\x00\x0c\x00\x0c\x00\xfc\x07\xfc\x07\x0c\x00\x0c\x00'\ +b'\x00\x00\x09\x00\xfc\x03\xfc\x07\x00\x06\x00\x06\x00\x06\xfc\x07'\ +b'\xfc\x01\x00\x00\x00\x00\x08\x00\x0c\x00\x7c\x00\xe0\x03\x00\x07'\ +b'\x80\x07\xf8\x01\x3c\x00\x04\x00\x0b\x00\x0c\x00\xfc\x00\xe0\x07'\ +b'\x80\x07\xf8\x03\x1c\x00\xfc\x00\xc0\x07\x80\x07\xfc\x01\x1c\x00'\ +b'\x08\x00\x04\x04\x0c\x07\xb8\x03\xf0\x00\xf0\x01\x9c\x07\x0c\x06'\ +b'\x04\x04\x08\x00\x04\x00\x1c\x00\x78\x00\xe0\x07\xe0\x07\x78\x00'\ +b'\x1c\x00\x04\x00\x07\x00\x0c\x06\x0c\x07\x8c\x07\xcc\x06\x2c\x06'\ +b'\x1c\x06\x0c\x06\x04\x00\xff\x1f\xff\x1f\x03\x18\x00\x00\x04\x00'\ +b'\x0e\x00\xfe\x00\xe0\x07\x00\x07\x04\x00\x03\x18\xff\x1f\xff\x1f'\ +b'\x00\x00\x07\x00\x80\x00\xe0\x00\x7c\x00\x0c\x00\x7c\x00\xe0\x00'\ +b'\x80\x00\x0a\x00\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18'\ +b'\x00\x18\x00\x18\x00\x18\x00\x18\x05\x00\x04\x00\x0c\x00\x08\x00'\ +b'\x00\x00\x00\x00\x08\x00\x00\x03\xe0\x07\xb0\x06\xb0\x06\xb0\x02'\ +b'\xf0\x07\xe0\x07\x00\x00\x09\x00\xfe\x07\xfe\x07\x30\x02\x30\x06'\ +b'\x30\x06\xe0\x07\xc0\x01\x00\x00\x00\x00\x07\x00\xc0\x01\xe0\x03'\ +b'\x30\x06\x30\x06\x30\x06\x60\x03\x40\x01\x08\x00\xc0\x01\xf0\x07'\ +b'\x30\x06\x30\x06\x30\x06\xfe\x07\xfe\x07\x00\x00\x07\x00\xc0\x01'\ +b'\xe0\x03\xb0\x07\xb0\x07\xb0\x07\xe0\x03\xc0\x01\x04\x00\x20\x00'\ +b'\xfc\x07\xfe\x07\x00\x00\x08\x00\xc0\x11\xf0\x1f\x30\x36\x30\x36'\ +b'\x20\x36\xf0\x1f\xf0\x0f\x00\x00\x08\x00\xfe\x07\xfe\x07\x30\x00'\ +b'\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x03\x00\xfc\x07\xfc\x07'\ +b'\x00\x00\x03\x00\xec\x7f\xec\x3f\x00\x00\x07\x00\xfe\x07\xfe\x07'\ +b'\xc0\x00\xe0\x01\xb0\x07\x10\x06\x10\x04\x03\x00\xfe\x07\xfe\x07'\ +b'\x00\x00\x0c\x00\xf0\x07\xf0\x07\x30\x00\x30\x00\xf0\x07\xe0\x07'\ +b'\x30\x00\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x08\x00\xf0\x07'\ +b'\xf0\x07\x30\x00\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x08\x00'\ +b'\xc0\x01\xe0\x03\x30\x06\x30\x06\x30\x06\xe0\x03\xc0\x01\x00\x00'\ +b'\x09\x00\xf0\x3f\xf0\x3f\x30\x06\x30\x06\x30\x06\xf0\x07\xc0\x01'\ +b'\x00\x00\x00\x00\x08\x00\xc0\x01\xf0\x07\x30\x06\x30\x06\x30\x06'\ +b'\xf0\x3f\xf0\x3f\x00\x00\x04\x00\xf0\x07\xf0\x07\x30\x00\x30\x00'\ +b'\x08\x00\x60\x00\x70\x03\xb0\x07\xf0\x06\xb0\x06\xa0\x07\x00\x03'\ +b'\x00\x00\x04\x00\x30\x00\xfe\x03\xfe\x07\x00\x00\x08\x00\xf0\x03'\ +b'\xf0\x07\x00\x06\x00\x06\xf0\x07\xf0\x07\x00\x00\x00\x00\x07\x00'\ +b'\x30\x00\xf0\x00\x80\x07\x00\x06\xc0\x03\xf0\x00\x10\x00\x0a\x00'\ +b'\x30\x00\xf0\x01\x80\x07\xc0\x07\xf0\x00\xf0\x00\x80\x07\x80\x07'\ +b'\xf0\x01\x30\x00\x07\x00\x10\x04\x30\x07\xe0\x03\xc0\x01\x70\x07'\ +b'\x10\x06\x00\x04\x07\x00\x30\x00\xf0\x30\xc0\x3f\x00\x1f\xe0\x03'\ +b'\x70\x00\x10\x00\x06\x00\x30\x06\x30\x07\xb0\x07\xf0\x06\x70\x06'\ +b'\x30\x06\x05\x00\x40\x00\xbe\x0f\xbe\x0f\x00\x00\x00\x00\x04\x00'\ +b'\xfe\x07\xfe\x07\x00\x00\x00\x00\x04\x00\xbe\x0f\xbe\x0f\x40\x00'\ +b'\x00\x00\x05\x00\x04\x00\x04\x00\x04\x00\x06\x00\x04\x00' + +_index =\ +b'\x00\x00\x12\x00\x1c\x00\x24\x00\x30\x00\x40\x00\x50\x00\x68\x00'\ +b'\x7c\x00\x82\x00\x8c\x00\x98\x00\xa4\x00\xb4\x00\xbc\x00\xc6\x00'\ +b'\xce\x00\xd8\x00\xea\x00\xf4\x00\x06\x01\x16\x01\x26\x01\x36\x01'\ +b'\x48\x01\x56\x01\x66\x01\x78\x01\x80\x01\x88\x01\x96\x01\xa4\x01'\ +b'\xb2\x01\xc4\x01\xdc\x01\xee\x01\x00\x02\x14\x02\x28\x02\x3a\x02'\ +b'\x4a\x02\x60\x02\x74\x02\x7c\x02\x8c\x02\x9e\x02\xae\x02\xc6\x02'\ +b'\xda\x02\xf0\x02\x04\x03\x1a\x03\x2e\x03\x42\x03\x52\x03\x66\x03'\ +b'\x78\x03\x90\x03\xa2\x03\xb4\x03\xc4\x03\xce\x03\xd8\x03\xe2\x03'\ +b'\xf2\x03\x08\x04\x14\x04\x26\x04\x3a\x04\x4a\x04\x5c\x04\x6c\x04'\ +b'\x76\x04\x88\x04\x9a\x04\xa2\x04\xaa\x04\xba\x04\xc2\x04\xdc\x04'\ +b'\xee\x04\x00\x05\x14\x05\x26\x05\x30\x05\x42\x05\x4c\x05\x5e\x05'\ +b'\x6e\x05\x84\x05\x94\x05\xa4\x05\xb2\x05\xbe\x05\xc8\x05\xd2\x05'\ +b'\xde\x05' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_width(s): + width = 0 + for ch in s: + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width += int.from_bytes(_font[offset:offset + 2], 'little') + return width + +def get_ch(ch): + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width = int.from_bytes(_font[offset:offset + 2], 'little') + next_offs = _chr_addr(ordch +1) + return _mvfont[offset + 2:next_offs], width + diff --git a/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt24.py b/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt24.py new file mode 100644 index 0000000000000000000000000000000000000000..bd6bd1372f37668816d079e18b0945e5961a203e --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt24.py @@ -0,0 +1,286 @@ +# Code generated by font-to-py.py. +# Font: CM Sans Serif 2012.ttf +version = '0.2' + +def height(): + return 24 + +def max_width(): + return 20 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x0c\x00\xc0\x00\x00\xf0\x00\x00\xf0\x00\x00\x78\x70\x07\x38\x78'\ +b'\x07\x38\x7c\x07\x78\x1e\x00\xf0\x0f\x00\xf0\x07\x00\xc0\x03\x00'\ +b'\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00'\ +b'\x00\x02\xf8\x3f\x07\xf8\x3f\x07\xf8\x3f\x02\x00\x00\x00\x00\x00'\ +b'\x00\x08\x00\xf8\x03\x00\xf8\x03\x00\x08\x00\x00\x00\x00\x00\xf8'\ +b'\x03\x00\xf8\x03\x00\x08\x00\x00\x00\x00\x00\x0c\x00\x00\xe0\x00'\ +b'\x80\xe3\x00\x80\xf3\x03\xe0\xff\x03\xe0\xff\x00\xe0\xe3\x00\x80'\ +b'\xe3\x03\x80\xff\x03\xe0\xff\x01\xe0\xe7\x00\x80\xe3\x00\x80\x03'\ +b'\x00\x0b\x00\x00\xe0\x00\xc0\xe3\x01\xe0\xc7\x01\x70\x8e\x03\xf8'\ +b'\xef\x07\xf8\xff\x07\x70\x8c\x03\xe0\x9d\x01\xc0\xf9\x01\x00\xf0'\ +b'\x00\x00\x00\x00\x12\x00\xe0\x01\x00\xf0\x03\x00\x38\x07\x00\x38'\ +b'\x07\x04\x38\x07\x07\x38\xc7\x07\xf0\xf3\x01\xe0\x79\x00\x00\x1e'\ +b'\x00\x80\x07\x00\xe0\xe3\x01\xf8\xf0\x03\x38\x38\x07\x08\x38\x07'\ +b'\x00\x38\x07\x00\xf0\x03\x00\xe0\x01\x00\x00\x00\x0f\x00\x00\xf0'\ +b'\x00\x00\xf8\x01\xe0\xfd\x03\xf0\x8f\x07\xf8\x07\x07\x38\x0e\x07'\ +b'\x38\x1f\x07\xf8\x3f\x07\xf0\xfb\x07\xe0\xf0\x07\x00\xe0\x03\x00'\ +b'\xf8\x07\x00\xf8\x07\x00\x78\x06\x00\x00\x04\x04\x00\xf8\x03\x00'\ +b'\xf8\x03\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x7f\x00\xe0\xff'\ +b'\x03\xf8\xff\x0f\x7c\x00\x1f\x0e\x00\x38\x02\x00\x20\x02\x00\x20'\ +b'\x07\x00\x02\x00\x20\x0e\x00\x38\x7c\x00\x1f\xf8\xff\x0f\xe0\xff'\ +b'\x03\x00\x7f\x00\x00\x00\x00\x09\x00\x60\x00\x00\x60\x00\x00\x78'\ +b'\x03\x00\xf0\x01\x00\xe0\x00\x00\xf8\x03\x00\x68\x01\x00\x60\x00'\ +b'\x00\x00\x00\x00\x0c\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\x38\x00\x80\xff\x03\x80\xff\x03\x80\xff\x03\x00\x38\x00\x00\x38'\ +b'\x00\x00\x38\x00\x00\x38\x00\x00\x00\x00\x05\x00\x00\x00\x37\x00'\ +b'\x00\x3f\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x07\x00\x00\x38\x00'\ +b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\x38\x00\x05\x00\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x00'\ +b'\x00\x00\x00\x08\x00\x00\x00\x06\x00\xe0\x07\x00\xfc\x07\xc0\xff'\ +b'\x01\xf8\x3f\x00\xfc\x03\x00\x7c\x00\x00\x04\x00\x00\x0d\x00\x80'\ +b'\x7f\x00\xe0\xff\x01\xf0\xff\x03\xf8\xc0\x07\x38\x00\x07\x38\x00'\ +b'\x07\x38\x00\x07\xf8\xc0\x07\xf0\xff\x03\xe0\xff\x01\x80\x7f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x08\x00\xc0\x01\x00\xc0\x01\x00\xe0\x01'\ +b'\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00'\ +b'\x0d\x00\xc0\x81\x07\xe0\xe1\x07\xf0\xe1\x07\x78\x70\x07\x38\x38'\ +b'\x07\x38\x38\x07\x38\x1c\x07\x78\x1e\x07\xf0\x0f\x07\xf0\x07\x07'\ +b'\xc0\x03\x07\x00\x00\x00\x00\x00\x00\x0c\x00\xc0\xc0\x00\xf0\xc0'\ +b'\x03\xf0\xc0\x03\x78\x80\x07\x38\x00\x07\x38\x0e\x07\x38\x0e\x07'\ +b'\x78\x9e\x07\xf0\xff\x03\xf0\xff\x03\xe0\xf1\x00\x00\x00\x00\x0c'\ +b'\x00\x00\xf0\x00\x00\xf8\x00\x00\xfe\x00\x00\xef\x00\xc0\xe7\x00'\ +b'\xf0\xe1\x00\xf8\xe0\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00'\ +b'\xe0\x00\x00\xe0\x00\x0c\x00\x00\xce\x00\xf8\xcf\x03\xf8\xcf\x03'\ +b'\x38\x8e\x07\x38\x07\x07\x38\x07\x07\x38\x07\x07\x38\x8f\x07\x38'\ +b'\xfe\x03\x38\xfc\x01\x00\xf8\x00\x00\x00\x00\x0d\x00\x00\x7f\x00'\ +b'\xe0\xff\x01\xf0\xff\x03\x78\x8e\x07\x38\x07\x07\x38\x07\x07\x38'\ +b'\x07\x07\x78\x8f\x07\xf0\xfe\x03\xe0\xfc\x01\xc0\xf8\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0b\x00\x38\x00\x00\x38\x00\x00\x38\xc0\x07\x38'\ +b'\xf8\x07\x38\xfe\x07\xb8\x3f\x00\xf8\x07\x00\xf8\x01\x00\xf8\x00'\ +b'\x00\x78\x00\x00\x38\x00\x00\x0c\x00\x00\xf0\x00\xe0\xf9\x01\xf0'\ +b'\xff\x03\xf0\x9f\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e'\ +b'\x07\xf0\x9f\x07\xf0\xff\x03\xe0\xf9\x01\x00\xf0\x00\x0d\x00\xc0'\ +b'\x07\x00\xe0\x0f\x01\xf0\x1f\x03\x78\x3c\x07\x38\x38\x07\x38\x38'\ +b'\x07\x38\x38\x07\x38\x38\x07\x70\x9c\x07\xf0\xff\x03\xe0\xff\x01'\ +b'\x80\x7f\x00\x00\x00\x00\x05\x00\x00\x07\x07\x00\x07\x07\x00\x07'\ +b'\x07\x00\x00\x00\x00\x00\x00\x05\x00\x00\x07\x37\x00\x07\x3f\x00'\ +b'\x07\x1f\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x38\x00\x00\x38\x00'\ +b'\x00\x7c\x00\x00\x6c\x00\x00\xee\x00\x00\xee\x00\x00\xc7\x01\x00'\ +b'\xc7\x01\x80\x83\x03\x00\x00\x00\x0b\x00\x00\xce\x01\x00\xce\x01'\ +b'\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00'\ +b'\xce\x01\x00\xce\x01\x00\xce\x01\x00\x00\x00\x0a\x00\x80\x83\x03'\ +b'\x80\x83\x03\x00\xc7\x01\x00\xc7\x01\x00\xee\x00\x00\xee\x00\x00'\ +b'\x7c\x00\x00\x7c\x00\x00\x38\x00\x00\x00\x00\x0c\x00\xc0\x00\x00'\ +b'\xf0\x00\x00\xf0\x00\x00\x78\x70\x07\x38\x78\x07\x38\x7c\x07\x78'\ +b'\x1e\x00\xf0\x0f\x00\xf0\x07\x00\xc0\x03\x00\x00\x00\x00\x00\x00'\ +b'\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xe1\x01\xf0\xc0\x03\x70'\ +b'\xbc\x03\x78\x7e\x07\x38\x7f\x07\xb8\x73\x07\xb8\x33\x07\xb8\x3f'\ +b'\x07\x38\x7f\x07\x70\xf3\x03\x70\x70\x03\xe0\x38\x02\xe0\x3f\x00'\ +b'\x80\x0f\x00\x00\x00\x00\x0f\x00\x00\x00\x06\x00\xc0\x07\x00\xf0'\ +b'\x07\x00\xfe\x03\xc0\xff\x00\xf8\xef\x00\xf8\xe1\x00\x78\xe0\x00'\ +b'\xf8\xe3\x00\xf0\xff\x00\xc0\xff\x00\x00\xfe\x07\x00\xf0\x07\x00'\ +b'\x80\x07\x00\x00\x04\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07'\ +b'\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38'\ +b'\x9e\x07\xf8\xff\x07\xf0\xff\x03\xe0\xf1\x01\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x10\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0'\ +b'\xe1\x03\x70\x80\x03\x78\x80\x07\x38\x00\x07\x38\x00\x07\x38\x00'\ +b'\x07\x38\x00\x07\x78\x80\x03\xf0\xc0\x03\xe0\xc0\x01\xc0\xc0\x00'\ +b'\x80\x40\x00\x00\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff'\ +b'\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07'\ +b'\x78\x80\x07\xf0\xc0\x03\xe0\xff\x01\xc0\xff\x00\x00\x3f\x00\x00'\ +b'\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07'\ +b'\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38'\ +b'\x0e\x07\x38\x0e\x07\x38\x00\x07\x38\x00\x07\x00\x00\x00\x00\x00'\ +b'\x00\x0d\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x38\x1c\x00\x38'\ +b'\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c'\ +b'\x00\x38\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x3f\x00\xc0'\ +b'\xff\x00\xe0\xff\x01\xf0\xe1\x03\x70\x80\x03\x78\x80\x07\x38\x00'\ +b'\x07\x38\x00\x07\x38\x1c\x07\x38\x1c\x07\x78\x9c\x03\xf0\xdc\x01'\ +b'\xe0\xfc\x07\xc0\xfc\x07\x80\xfc\x07\x00\x00\x00\x00\x00\x00\x0f'\ +b'\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x0e\x00\x00\x0e\x00'\ +b'\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\xf8'\ +b'\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00\x06\x00'\ +b'\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0c\x00\x00\xe0\x00\x00\xe0\x01\x00\xe0\x03\x00\x80\x07'\ +b'\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x80\x07\xf8\xff\x03\xf8'\ +b'\xff\x03\xf8\xff\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07'\ +b'\xf8\xff\x07\x00\x1e\x00\x00\x0f\x00\x80\x1f\x00\xc0\x7f\x00\xe0'\ +b'\xf9\x00\xf0\xf0\x03\x78\xc0\x07\x38\x80\x07\x18\x00\x07\x08\x00'\ +b'\x04\x00\x00\x00\x0c\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00'\ +b'\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00'\ +b'\x07\x00\x00\x07\x00\x00\x07\x00\x00\x00\x12\x00\xf8\xff\x07\xf8'\ +b'\xff\x07\xf8\xff\x07\xf8\x01\x00\xf8\x1f\x00\xc0\xff\x00\x00\xfc'\ +b'\x07\x00\xe0\x07\x00\xe0\x07\x00\xfc\x07\xc0\xff\x00\xf8\x1f\x00'\ +b'\xf8\x01\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00'\ +b'\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\xf8\x00\x00'\ +b'\xe0\x03\x00\xc0\x0f\x00\x00\x3f\x00\x00\xfc\x00\x00\xf0\x01\x00'\ +b'\xe0\x07\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00'\ +b'\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0\xe1\x03\x70'\ +b'\x80\x03\x78\x80\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x78\x80'\ +b'\x07\x70\x80\x03\xf0\xe1\x03\xe0\xff\x01\xc0\xff\x00\x00\x3f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff'\ +b'\x07\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00'\ +b'\x78\x1e\x00\xf0\x0f\x00\xf0\x0f\x00\xc0\x03\x00\x00\x00\x00\x00'\ +b'\x00\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0\xe1\x03'\ +b'\x70\x80\x03\x78\x80\x07\x38\x00\x07\x38\x10\x07\x38\x38\x07\x38'\ +b'\x70\x07\x78\xf0\x07\x70\xe0\x03\xf0\xe1\x03\xe0\xff\x07\xc0\xff'\ +b'\x03\x00\x3f\x02\x00\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8'\ +b'\xff\x07\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x3c\x00\x38\xfc'\ +b'\x00\x38\xfc\x03\x78\xfe\x07\xf0\x9f\x07\xf0\x0f\x07\xc0\x07\x04'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\xc0\x00\xe0\xc1\x01\xf0\xe3'\ +b'\x03\xf0\x87\x03\x78\x06\x07\x38\x0e\x07\x38\x0e\x07\x38\x0c\x07'\ +b'\x38\x0c\x07\x78\x9c\x07\xf0\xf8\x03\xe0\xf8\x03\xc0\xf0\x00\x00'\ +b'\x00\x00\x0c\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x38\x00\x00\x38\x00\x00\x38'\ +b'\x00\x00\x38\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x00\xf8\xff\x01'\ +b'\xf8\xff\x03\x00\x80\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00'\ +b'\x00\x07\x00\x80\x07\xf8\xff\x03\xf8\xff\x01\xf8\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x18\x00\x00\xf8\x00\x00\xf8\x07\x00\xf0'\ +b'\x3f\x00\x00\xff\x01\x00\xf8\x07\x00\x80\x07\x00\xe0\x07\x00\xfe'\ +b'\x07\xc0\x7f\x00\xf8\x0f\x00\xf8\x01\x00\x38\x00\x00\x08\x00\x00'\ +b'\x14\x00\x18\x00\x00\xf8\x01\x00\xf8\x1f\x00\xe0\xff\x00\x00\xfe'\ +b'\x07\x00\xe0\x07\x00\xf8\x07\xc0\xff\x07\xf8\x3f\x00\xf8\x03\x00'\ +b'\xf8\x01\x00\xf8\x3f\x00\xc0\xff\x07\x00\xf8\x07\x00\xc0\x07\x00'\ +b'\xfe\x07\xe0\xff\x01\xf8\x1f\x00\xf8\x01\x00\x38\x00\x00\x0e\x00'\ +b'\x08\x00\x04\x18\x00\x07\x78\x80\x07\xf8\xe0\x07\xf0\xf3\x01\xc0'\ +b'\xff\x00\x00\x3f\x00\x80\x7f\x00\xe0\xff\x00\xf0\xf1\x03\xf8\xc0'\ +b'\x07\x38\x80\x07\x18\x00\x06\x08\x00\x04\x0d\x00\x18\x00\x00\x38'\ +b'\x00\x00\xf8\x00\x00\xf0\x03\x00\xc0\x0f\x00\x00\xff\x07\x00\xfc'\ +b'\x07\x00\xff\x07\xc0\x0f\x00\xf0\x03\x00\xf8\x00\x00\x38\x00\x00'\ +b'\x18\x00\x00\x0d\x00\x00\x00\x07\x38\x80\x07\x38\xc0\x07\x38\xe0'\ +b'\x07\x38\x78\x07\x38\x3c\x07\x38\x1e\x07\x38\x0f\x07\xf8\x07\x07'\ +b'\xf8\x01\x07\xf8\x00\x07\x78\x00\x07\x38\x00\x07\x07\x00\xff\xff'\ +b'\x3f\xff\xff\x3f\xff\xff\x3f\x07\x00\x38\x07\x00\x38\x07\x00\x38'\ +b'\x00\x00\x00\x08\x00\x04\x00\x00\x3c\x00\x00\xfc\x01\x00\xfc\x1f'\ +b'\x00\xe0\xff\x00\x00\xfe\x07\x00\xf0\x07\x00\x00\x07\x07\x00\x07'\ +b'\x00\x38\x07\x00\x38\x07\x00\x38\xff\xff\x3f\xff\xff\x3f\xff\xff'\ +b'\x3f\x00\x00\x00\x0c\x00\x00\x18\x00\x00\x1f\x00\xc0\x1f\x00\xf8'\ +b'\x03\x00\xf8\x00\x00\x78\x00\x00\xf8\x03\x00\xe0\x0f\x00\x00\x1f'\ +b'\x00\x00\x1c\x00\x00\x10\x00\x00\x00\x00\x12\x00\x00\x00\x38\x00'\ +b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38'\ +b'\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00'\ +b'\x00\x38\x09\x00\x04\x00\x00\x04\x00\x00\x0c\x00\x00\x1c\x00\x00'\ +b'\x18\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\ +b'\x00\x00\xc0\x01\x00\xe4\x03\x00\xe6\x07\x00\x37\x07\x80\x37\x07'\ +b'\x80\x33\x07\x80\x33\x07\x80\x33\x07\x80\x93\x03\x80\xff\x07\x00'\ +b'\xff\x07\x00\xfe\x07\x00\x00\x00\x0e\x00\xfc\xff\x07\xfc\xff\x07'\ +b'\xfc\xff\x07\x00\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80'\ +b'\x03\x07\x80\x87\x07\x00\xff\x03\x00\xfe\x01\x00\xf8\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0d\x00\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00'\ +b'\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x87'\ +b'\x07\x00\x87\x03\x00\x86\x01\x00\x88\x00\x00\x00\x00\x0e\x00\x00'\ +b'\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87\x03\x80\x87\x07\x80\x03'\ +b'\x07\x80\x03\x07\x80\x03\x07\x80\x87\x07\x00\x87\x03\xfc\xff\x07'\ +b'\xfc\xff\x07\xfc\xff\x07\x00\x00\x00\x0d\x00\x00\x30\x00\x00\xfc'\ +b'\x00\x00\xfe\x01\x00\xff\x03\x80\x77\x07\x80\x73\x07\x80\x73\x07'\ +b'\x80\x73\x07\x80\xf7\x07\x00\xff\x03\x00\x7e\x03\x00\x7c\x00\x00'\ +b'\x60\x00\x07\x00\x80\x03\x00\xf0\xff\x07\xfc\xff\x07\xfc\xff\x07'\ +b'\x9e\x03\x00\x8e\x03\x00\x00\x00\x00\x0e\x00\x00\x78\x00\x00\xfe'\ +b'\x01\x00\xff\x23\x00\x87\x73\x80\x87\xf7\x80\x03\xe7\x80\x03\xe7'\ +b'\x80\x03\xe7\x00\x87\xe7\x00\x87\xf3\x80\xff\x7f\x80\xff\x3f\x80'\ +b'\xff\x1f\x00\x00\x00\x0d\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff\x07'\ +b'\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07\x00\x00'\ +b'\xff\x07\x00\xff\x07\x00\xfc\x07\x00\x00\x00\x00\x00\x00\x05\x00'\ +b'\xb8\xff\x07\xb8\xff\x07\xb8\xff\x07\x00\x00\x00\x00\x00\x00\x05'\ +b'\x00\x00\x00\xe0\x00\x00\xe0\xb8\xff\xff\xb8\xff\xff\xb8\xff\x7f'\ +b'\x0c\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff\x07\x00\x78\x00\x00\x3c'\ +b'\x00\x00\xfe\x00\x80\xff\x01\x80\xe7\x07\x80\x83\x07\x80\x01\x07'\ +b'\x80\x00\x04\x00\x00\x00\x05\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff'\ +b'\x07\x00\x00\x00\x00\x00\x00\x14\x00\x80\xff\x07\x80\xff\x07\x80'\ +b'\xff\x07\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07'\ +b'\x00\x00\xff\x07\x00\xfe\x07\x00\xff\x07\x80\x07\x00\x80\x03\x00'\ +b'\x80\x03\x00\x80\x07\x00\x00\xff\x07\x00\xff\x07\x00\xfc\x07\x00'\ +b'\x00\x00\x00\x00\x00\x0d\x00\x80\xff\x07\x80\xff\x07\x80\xff\x07'\ +b'\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07\x00\x00'\ +b'\xff\x07\x00\xff\x07\x00\xfc\x07\x00\x00\x00\x00\x00\x00\x0d\x00'\ +b'\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87\x03\x80\x03\x07\x80'\ +b'\x03\x07\x80\x03\x07\x80\x03\x07\x00\x87\x03\x00\xff\x03\x00\xfe'\ +b'\x01\x00\x78\x00\x00\x00\x00\x0e\x00\x80\xff\xff\x80\xff\xff\x80'\ +b'\xff\xff\x00\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x03'\ +b'\x07\x80\x87\x07\x00\xff\x03\x00\xfe\x01\x00\x7c\x00\x00\x00\x00'\ +b'\x00\x00\x00\x0e\x00\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87'\ +b'\x03\x80\x87\x07\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x87\x07'\ +b'\x00\x87\x03\x80\xff\xff\x80\xff\xff\x80\xff\xff\x00\x00\x00\x08'\ +b'\x00\x80\xff\x07\x80\xff\x07\x80\xff\x07\x00\x07\x00\x80\x03\x00'\ +b'\x80\x03\x00\x80\x03\x00\x00\x00\x00\x0c\x00\x00\x80\x00\x00\x8e'\ +b'\x01\x00\x9f\x03\x80\x9f\x07\x80\x3b\x07\x80\x33\x07\x80\x33\x07'\ +b'\x80\x33\x07\x00\xf7\x07\x00\xe7\x03\x00\xc6\x01\x00\x00\x00\x07'\ +b'\x00\x80\x03\x00\xfc\xff\x00\xfc\xff\x03\xfc\xff\x07\x80\x03\x07'\ +b'\x80\x03\x07\x00\x00\x00\x0d\x00\x80\xff\x00\x80\xff\x03\x80\xff'\ +b'\x03\x00\x80\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x80\x03'\ +b'\x80\xff\x07\x80\xff\x07\x80\xff\x07\x00\x00\x00\x00\x00\x00\x0c'\ +b'\x00\x80\x01\x00\x80\x0f\x00\x80\x3f\x00\x00\xfe\x01\x00\xf0\x07'\ +b'\x00\x80\x07\x00\xc0\x07\x00\xf8\x07\x00\xff\x00\x80\x3f\x00\x80'\ +b'\x07\x00\x80\x00\x00\x12\x00\x80\x01\x00\x80\x0f\x00\x80\xff\x00'\ +b'\x00\xfe\x07\x00\xe0\x07\x00\xc0\x07\x00\xfc\x07\x80\xff\x00\x80'\ +b'\x0f\x00\x80\x3f\x00\x00\xff\x01\x00\xf0\x07\x00\x80\x07\x00\xf8'\ +b'\x07\x80\xff\x03\x80\x3f\x00\x80\x07\x00\x80\x00\x00\x0c\x00\x80'\ +b'\x00\x04\x80\x01\x07\x80\x87\x07\x80\xef\x07\x00\xfe\x01\x00\x7c'\ +b'\x00\x00\xfe\x00\x00\xff\x03\x80\xc7\x07\x80\x03\x07\x80\x00\x06'\ +b'\x00\x00\x04\x0c\x00\x80\x01\x00\x80\x07\xe0\x80\x3f\xe0\x00\xff'\ +b'\xf0\x00\xf8\xff\x00\xc0\x7f\x00\xc0\x1f\x00\xf8\x07\x00\xff\x00'\ +b'\x80\x1f\x00\x80\x07\x00\x80\x00\x00\x0b\x00\x80\x03\x07\x80\x83'\ +b'\x07\x80\xc3\x07\x80\xe3\x07\x80\xf3\x07\x80\x7b\x07\x80\x3f\x07'\ +b'\x80\x1f\x07\x80\x0f\x07\x80\x07\x07\x00\x00\x07\x08\x00\x00\x0c'\ +b'\x00\x00\x1e\x00\xfc\xff\x0f\xfe\xff\x1f\xfe\xf3\x1f\x06\x00\x38'\ +b'\x06\x00\x38\x00\x00\x00\x05\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff'\ +b'\x07\x00\x00\x00\x00\x00\x00\x07\x00\x06\x00\x38\xfe\xf3\x1f\xfe'\ +b'\xff\x1f\xfc\xff\x0f\x00\x1e\x00\x00\x0c\x00\x00\x00\x00\x09\x00'\ +b'\x18\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x18\x00\x00\x18'\ +b'\x00\x00\x1c\x00\x00\x04\x00\x00\x00\x00\x00' + +_index =\ +b'\x00\x00\x26\x00\x3d\x00\x51\x00\x6b\x00\x91\x00\xb4\x00\xec\x00'\ +b'\x1b\x01\x29\x01\x40\x01\x57\x01\x74\x01\x9a\x01\xab\x01\xc2\x01'\ +b'\xd3\x01\xed\x01\x16\x02\x30\x02\x59\x02\x7f\x02\xa5\x02\xcb\x02'\ +b'\xf4\x02\x17\x03\x3d\x03\x66\x03\x77\x03\x88\x03\xa8\x03\xcb\x03'\ +b'\xeb\x03\x11\x04\x46\x04\x75\x04\xa4\x04\xd6\x04\x05\x05\x31\x05'\ +b'\x5a\x05\x8f\x05\xbe\x05\xd2\x05\xf8\x05\x24\x06\x4a\x06\x82\x06'\ +b'\xb1\x06\xe6\x06\x12\x07\x47\x07\x76\x07\xa2\x07\xc8\x07\xf4\x07'\ +b'\x20\x08\x5e\x08\x8a\x08\xb3\x08\xdc\x08\xf3\x08\x0d\x09\x24\x09'\ +b'\x4a\x09\x82\x09\x9f\x09\xc8\x09\xf4\x09\x1d\x0a\x49\x0a\x72\x0a'\ +b'\x89\x0a\xb5\x0a\xde\x0a\xef\x0a\x00\x0b\x26\x0b\x37\x0b\x75\x0b'\ +b'\x9e\x0b\xc7\x0b\xf3\x0b\x1f\x0c\x39\x0c\x5f\x0c\x76\x0c\x9f\x0c'\ +b'\xc5\x0c\xfd\x0c\x23\x0d\x49\x0d\x6c\x0d\x86\x0d\x97\x0d\xae\x0d'\ +b'\xcb\x0d' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_width(s): + width = 0 + for ch in s: + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width += int.from_bytes(_font[offset:offset + 2], 'little') + return width + +def get_ch(ch): + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width = int.from_bytes(_font[offset:offset + 2], 'little') + next_offs = _chr_addr(ordch +1) + return _mvfont[offset + 2:next_offs], width + diff --git a/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt32.py b/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt32.py new file mode 100644 index 0000000000000000000000000000000000000000..e98667e65d6b26c11c6edb1367ac3a564c59310c --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/fonts/tt32.py @@ -0,0 +1,430 @@ +# Code generated by font-to-py.py. +# Font: CM Sans Serif 2012.ttf +version = '0.2' + +def height(): + return 31 + +def max_width(): + return 26 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x0f\x00\x00\x07\x00\x00\xc0\x07\x00\x00\xe0\x07\x00\x00\xe0\x07'\ +b'\x00\x00\xf0\x01\x67\x00\xf0\xc0\xf7\x00\xf0\xe0\xf7\x00\xf0\xe0'\ +b'\x67\x00\xf0\xf1\x01\x00\xe0\xff\x00\x00\xe0\x7f\x00\x00\xc0\x3f'\ +b'\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x08\x00\xf0\xff\xf3\x00\xf0\xff\xf3\x00\xf0\xff'\ +b'\xf3\x00\xf0\xff\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0a\x00\xf0\x0f\x00\x00\xf0\x0f\x00\x00'\ +b'\xf0\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x0f\x00\x00'\ +b'\xf0\x0f\x00\x00\xf0\x0f\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0f\x00\x00\x00\x0e\x00\x00\x0e\x0e\x00\x00\x0e\x7e\x00\x00\xfe'\ +b'\x7f\x00\xe0\xff\x7f\x00\xe0\xff\x0f\x00\xe0\x0f\x0e\x00\x00\x0e'\ +b'\x0e\x00\x00\x0e\x7f\x00\x00\xfe\x7f\x00\xe0\xff\x7f\x00\xe0\xff'\ +b'\x0f\x00\xe0\x0f\x0e\x00\x00\x0e\x0e\x00\x00\x0e\x00\x00\x0e\x00'\ +b'\x00\x1f\x0e\x00\x80\x3f\x1e\x00\xc0\x3f\x3e\x00\xc0\x79\x3c\x00'\ +b'\xc0\x71\x38\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xc0\xe1\x38\x00'\ +b'\xc0\xe3\x38\x00\xc0\xe7\x3f\x00\x80\xc7\x1f\x00\x00\x86\x0f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x80\x07\x00\x00\xc0\x0f'\ +b'\x00\x00\xe0\x1f\x00\x00\xf0\x3c\x00\x00\x70\x38\x80\x00\x70\x38'\ +b'\xe0\x00\x70\x38\xf8\x00\xf0\x3c\x7e\x00\xe0\x1f\x1f\x00\xc0\xcf'\ +b'\x0f\x00\x80\xf7\x03\x00\x00\xfc\x00\x00\x00\x3f\x1e\x00\x80\x8f'\ +b'\x7f\x00\xe0\x87\x7f\x00\xf0\xc1\xf3\x00\x70\xc0\xe1\x00\x10\xc0'\ +b'\xe1\x00\x00\xc0\xf3\x00\x00\x80\x7f\x00\x00\x80\x7f\x00\x00\x00'\ +b'\x1e\x00\x00\x00\x00\x00\x13\x00\x00\x00\x0f\x00\x00\x80\x3f\x00'\ +b'\x00\xc0\x3f\x00\x80\xe7\x7f\x00\xe0\x7f\xf8\x00\xe0\x3f\xf8\x00'\ +b'\xf0\x3f\xf0\x00\xf0\x78\xf0\x00\xf0\xf8\xf0\x00\xf0\xff\xf3\x00'\ +b'\xe0\xdf\xff\x00\xe0\x8f\xff\x00\x80\x07\x7e\x00\x00\xc0\xff\x00'\ +b'\x00\xc0\xff\x00\x00\xc0\xff\x00\x00\xc0\xc3\x00\x00\x00\x80\x00'\ +b'\x00\x00\x00\x00\x05\x00\xf0\x0f\x00\x00\xf0\x0f\x00\x00\xf0\x0f'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\xfc\x03\x00'\ +b'\x80\xff\x1f\x00\xe0\xff\x7f\x00\xf8\xff\xff\x01\xfc\x01\xf8\x03'\ +b'\x3e\x00\xc0\x07\x0e\x00\x00\x07\x02\x00\x00\x04\x00\x00\x00\x00'\ +b'\x09\x00\x02\x00\x00\x04\x0e\x00\x00\x07\x3e\x00\xc0\x07\xfc\x01'\ +b'\xf8\x03\xf8\xff\xff\x01\xe0\xff\x7f\x00\x80\xff\x1f\x00\x00\xfc'\ +b'\x03\x00\x00\x00\x00\x00\x0b\x00\x80\x01\x00\x00\xa0\x09\x00\x00'\ +b'\xf0\x1f\x00\x00\xe0\x0f\x00\x00\xc0\x07\x00\x00\xe0\x0f\x00\x00'\ +b'\xf0\x1d\x00\x00\xa0\x09\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0f\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xfe\x7f\x00\x00\xfe'\ +b'\x7f\x00\x00\xfe\x7f\x00\x00\xfe\x7f\x00\x00\xc0\x03\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\x00'\ +b'\x00\x00\x06\x00\x00\x00\x60\x0c\x00\x00\xf0\x0c\x00\x00\xf0\x07'\ +b'\x00\x00\xe0\x03\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x06\x00\x00\x00\x60\x00\x00\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00'\ +b'\xe0\x00\x00\x00\xfc\x00\x00\xc0\xff\x00\x00\xf8\xff\x00\x80\xff'\ +b'\x1f\x00\xf8\xff\x03\x00\xfc\x3f\x00\x00\xfc\x07\x00\x00\x7c\x00'\ +b'\x00\x00\x04\x00\x00\x00\x11\x00\x00\xfc\x03\x00\x80\xff\x1f\x00'\ +b'\xc0\xff\x3f\x00\xe0\xff\x7f\x00\xe0\x07\x7e\x00\xf0\x01\xf8\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x01\xf8\x00'\ +b'\xe0\x07\x7e\x00\xe0\xff\x7f\x00\xc0\xff\x3f\x00\x80\xff\x1f\x00'\ +b'\x00\xfc\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x80\x03'\ +b'\x00\x00\x80\x03\x00\x00\xc0\x03\x00\x00\xe0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x10\x00\x00\x07\xf8\x00\xc0\x07\xfc\x00\xc0\x07\xfe\x00'\ +b'\xe0\x07\xff\x00\xf0\x81\xf7\x00\xf0\x80\xf3\x00\xf0\xc0\xf1\x00'\ +b'\xf0\xe0\xf1\x00\xf0\xe0\xf0\x00\xf0\xf1\xf0\x00\xe0\x7f\xf0\x00'\ +b'\xe0\x7f\xf0\x00\xc0\x3f\xf0\x00\x00\x0f\xf0\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x10\x00\x00\x00\x0e\x00\x00\x07\x3e\x00\xc0\x07'\ +b'\x3e\x00\xe0\x07\x7e\x00\xe0\x07\xf8\x00\xf0\x01\xf0\x00\xf0\xf0'\ +b'\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf9\xf0\x00\xf0\xff'\ +b'\xf9\x00\xe0\xff\x7f\x00\xc0\xdf\x7f\x00\x80\x8f\x3f\x00\x00\x00'\ +b'\x0f\x00\x00\x00\x00\x00\x10\x00\x00\x00\x0f\x00\x00\x80\x0f\x00'\ +b'\x00\xe0\x0f\x00\x00\xf8\x0f\x00\x00\x7c\x0f\x00\x00\x3f\x0f\x00'\ +b'\x80\x0f\x0f\x00\xe0\x07\x0f\x00\xf0\x01\x0f\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x0f\x00'\ +b'\x00\x00\x0f\x00\x00\x00\x00\x00\x0f\x00\x00\x70\x1c\x00\xf0\x7f'\ +b'\x3c\x00\xf0\x7f\x7c\x00\xf0\xff\x7c\x00\xf0\x78\xf8\x00\xf0\x3c'\ +b'\xf0\x00\xf0\x3c\xf0\x00\xf0\x3c\xf0\x00\xf0\x3c\xf0\x00\xf0\x7c'\ +b'\x78\x00\xf0\xf8\x7f\x00\xf0\xf0\x3f\x00\xf0\xe0\x1f\x00\x00\xc0'\ +b'\x0f\x00\x00\x00\x00\x00\x10\x00\x00\xfc\x07\x00\x00\xff\x1f\x00'\ +b'\xc0\xff\x3f\x00\xe0\xff\x7f\x00\xe0\xf3\x78\x00\xf0\x79\xf0\x00'\ +b'\xf0\x78\xf0\x00\xf0\x78\xf0\x00\xf0\x78\xf0\x00\xf0\xf9\xf8\x00'\ +b'\xe0\xf3\x7f\x00\xe0\xf3\x7f\x00\xc0\xe3\x3f\x00\x00\x83\x0f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\xf8\x00\xf0\x80\xff\x00\xf0\xe0'\ +b'\xff\x00\xf0\xf8\xff\x00\xf0\xfc\x07\x00\xf0\x7e\x00\x00\xf0\x1f'\ +b'\x00\x00\xf0\x07\x00\x00\xf0\x03\x00\x00\xf0\x01\x00\x00\xf0\x00'\ +b'\x00\x00\x10\x00\x00\x00\x0f\x00\x80\xc7\x3f\x00\xc0\xef\x7f\x00'\ +b'\xe0\xff\x7f\x00\xe0\xff\xf8\x00\xf0\x7d\xf0\x00\xf0\x78\xf0\x00'\ +b'\xf0\x78\xf0\x00\xf0\x78\xf0\x00\xf0\x7d\xf0\x00\xe0\xff\xf8\x00'\ +b'\xe0\xff\x7f\x00\xc0\xef\x7f\x00\x80\xc7\x3f\x00\x00\x00\x0f\x00'\ +b'\x00\x00\x00\x00\x10\x00\x00\x3f\x00\x00\x80\xff\x30\x00\xc0\xff'\ +b'\x71\x00\xe0\xff\x71\x00\xf0\xe1\xf3\x00\xf0\xc1\xf3\x00\xf0\xc0'\ +b'\xf3\x00\xf0\xc0\xf3\x00\xf0\xc0\xf3\x00\xf0\xc1\xf9\x00\xe0\xe1'\ +b'\x7d\x00\xe0\xff\x7f\x00\xc0\xff\x3f\x00\x80\xff\x1f\x00\x00\xfc'\ +b'\x03\x00\x00\x00\x00\x00\x06\x00\x00\x18\x60\x00\x00\x3c\xf0\x00'\ +b'\x00\x3c\xf0\x00\x00\x18\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x06\x00\x00\x18\x60\x0c\x00\x3c\xf0\x0c\x00\x3c\xf0\x07\x00\x18'\ +b'\xe0\x03\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\xc0\x03\x00'\ +b'\x00\xe0\x07\x00\x00\xe0\x07\x00\x00\xf0\x0f\x00\x00\xf0\x0f\x00'\ +b'\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x38\x1c\x00\x00\x3c\x3c\x00'\ +b'\x00\x1c\x38\x00\x00\x1e\x78\x00\x00\x00\x00\x00\x0e\x00\x00\x78'\ +b'\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78'\ +b'\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78'\ +b'\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x1e\x78\x00\x00\x3c\x3c\x00'\ +b'\x00\x3c\x3c\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x70\x0e\x00'\ +b'\x00\xf0\x0f\x00\x00\xe0\x07\x00\x00\xe0\x07\x00\x00\xc0\x07\x00'\ +b'\x00\xc0\x03\x00\x00\x00\x00\x00\x0f\x00\x00\x07\x00\x00\xc0\x07'\ +b'\x00\x00\xe0\x07\x00\x00\xe0\x07\x00\x00\xf0\x01\x67\x00\xf0\xc0'\ +b'\xf7\x00\xf0\xe0\xf7\x00\xf0\xe0\x67\x00\xf0\xf1\x01\x00\xe0\xff'\ +b'\x00\x00\xe0\x7f\x00\x00\xc0\x3f\x00\x00\x00\x1f\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x16\x00\x00\xf8\x01\x00\x00\xfe\x07\x00'\ +b'\x80\xff\x1f\x00\xc0\x0f\x1e\x00\xc0\x03\x3c\x00\xe0\xe1\x79\x00'\ +b'\xe0\xf8\x73\x00\xf0\xfc\xf7\x00\x70\x1c\xe7\x00\x70\x0e\xe7\x00'\ +b'\x70\x8e\xe3\x00\x70\xfe\xe1\x00\x70\xfc\xe3\x00\xf0\xfc\xe7\x00'\ +b'\xe0\x0c\x77\x00\xe0\x01\x77\x00\xc0\xc3\x23\x00\xc0\xff\x43\x00'\ +b'\x00\xff\x01\x00\x00\x7e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x13\x00\x00\x00\xc0\x00\x00\x00\xf8\x00\x00\x00\xfe\x00\x00\xc0'\ +b'\xff\x00\x00\xf8\x3f\x00\x00\xff\x0f\x00\xe0\xff\x0f\x00\xf0\x1f'\ +b'\x0f\x00\xf0\x03\x0f\x00\xf0\x00\x0f\x00\xf0\x07\x0f\x00\xf0\x3f'\ +b'\x0f\x00\xc0\xff\x0f\x00\x00\xfe\x0f\x00\x00\xf0\x7f\x00\x00\x80'\ +b'\xff\x00\x00\x00\xfc\x00\x00\x00\xf0\x00\x00\x00\x80\x00\x12\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00'\ +b'\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf9\xf0\x00'\ +b'\xf0\xff\xf9\x00\xe0\xff\x7f\x00\xc0\xdf\x7f\x00\x80\x8f\x3f\x00'\ +b'\x00\x00\x1f\x00\x00\x00\x00\x00\x14\x00\x00\xf8\x01\x00\x00\xfe'\ +b'\x07\x00\x80\xff\x1f\x00\xc0\xff\x3f\x00\xc0\x0f\x3f\x00\xe0\x03'\ +b'\x7c\x00\xe0\x01\x78\x00\xf0\x01\xf8\x00\xf0\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x01\xf8\x00\xe0\x01'\ +b'\x78\x00\xe0\x07\x7e\x00\xc0\x07\x3e\x00\x80\x07\x1e\x00\x00\x03'\ +b'\x0c\x00\x00\x04\x04\x00\x00\x00\x00\x00\x13\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf8\x00\xe0\x01\x78\x00\xe0\x07\x7e\x00\xc0\xff\x3f\x00'\ +b'\x80\xff\x1f\x00\x00\xff\x0f\x00\x00\xfc\x03\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xf0\xf0\x00\xf0\xf0'\ +b'\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0'\ +b'\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00'\ +b'\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00'\ +b'\xf0\xf0\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00'\ +b'\x15\x00\x00\xf8\x01\x00\x00\xfe\x0f\x00\x80\xff\x1f\x00\xc0\xff'\ +b'\x3f\x00\xc0\x0f\x7f\x00\xe0\x03\x7c\x00\xe0\x01\xf8\x00\xf0\x01'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\xe0\xf1\x00\xf0\xe0'\ +b'\xf1\x00\xf0\xe1\x79\x00\xe0\xe1\x79\x00\xe0\xe3\x3d\x00\xc0\xe7'\ +b'\xff\x00\x80\xe7\xff\x00\x00\xe3\xff\x00\x00\xe4\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x14\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\xf0\x00\x00\x00\xf0\x00\x00'\ +b'\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00'\ +b'\x00\xf0\x00\x00\x00\xf0\x00\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x3f\x00'\ +b'\x00\x00\x7f\x00\x00\x00\x7f\x00\x00\x00\xf8\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf8\x00'\ +b'\xf0\xff\x7f\x00\xf0\xff\x7f\x00\xf0\xff\x3f\x00\xf0\xff\x0f\x00'\ +b'\x00\x00\x00\x00\x12\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\x00\xf0\x01\x00\x00\xfc\x00\x00\x00\xfe'\ +b'\x00\x00\x00\xff\x01\x00\x80\xff\x07\x00\xc0\xef\x0f\x00\xe0\x87'\ +b'\x3f\x00\xf0\x01\x7f\x00\xf0\x00\xfc\x00\x70\x00\xf8\x00\x30\x00'\ +b'\xf0\x00\x10\x00\xc0\x00\x10\x00\x80\x00\x00\x00\x00\x00\x10\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x18\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\x07\x00\x00\xf0\x7f\x00\x00\x80\xff\x07\x00\x00\xfc'\ +b'\x3f\x00\x00\xc0\xff\x00\x00\x00\xfe\x00\x00\x00\xfe\x00\x00\xc0'\ +b'\xff\x00\x00\xfc\x3f\x00\x80\xff\x07\x00\xf0\x7f\x00\x00\xf0\x07'\ +b'\x00\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x14\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\x07\x00\x00\xc0\x1f\x00\x00\x00\x3f\x00\x00'\ +b'\x00\xfc\x00\x00\x00\xf0\x03\x00\x00\xc0\x0f\x00\x00\x80\x3f\x00'\ +b'\x00\x00\xfe\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x16\x00\x00\xf8\x01\x00\x00\xfe\x07\x00\x80\xff'\ +b'\x1f\x00\xc0\xff\x3f\x00\xc0\x0f\x3f\x00\xe0\x03\x7c\x00\xe0\x01'\ +b'\x78\x00\xf0\x01\xf8\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x01\xf8\x00\xe0\x01\x78\x00\xe0\x03'\ +b'\x7c\x00\xc0\x0f\x3f\x00\xc0\xff\x3f\x00\x80\xff\x1f\x00\x00\xfe'\ +b'\x07\x00\x00\xf8\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x01\x00'\ +b'\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xf1\x01\x00\xe0\xff\x00\x00'\ +b'\xe0\xff\x00\x00\xc0\x7f\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00\x00\xf8\x01\x00\x00\xff'\ +b'\x0f\x00\x80\xff\x1f\x00\xc0\xff\x3f\x00\xe0\x0f\x7e\x00\xe0\x03'\ +b'\x7c\x00\xf0\x01\xf8\x00\xf0\x00\xf0\x00\xf0\x00\xf1\x00\xf0\x80'\ +b'\xf3\x00\xf0\xc0\xf7\x00\xf0\x80\xff\x00\xf0\x01\xff\x00\xe0\x03'\ +b'\x7e\x00\xe0\x0f\x7e\x00\xc0\xff\x7f\x00\x80\xff\xff\x00\x00\xfe'\ +b'\x77\x00\x00\xf8\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x03\x00'\ +b'\xf0\xe0\x0f\x00\xf0\xe0\x3f\x00\xf0\xf1\x7f\x00\xe0\xff\xfc\x00'\ +b'\xe0\xff\xf0\x00\xc0\x7f\xe0\x00\x00\x1f\x80\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x04\x00\x80\x0f'\ +b'\x1e\x00\xc0\x1f\x3e\x00\xe0\x3f\x7e\x00\xe0\x3f\x7c\x00\xf0\x39'\ +b'\xf8\x00\xf0\x70\xf0\x00\xf0\x70\xf0\x00\xf0\x70\xf0\x00\xf0\x70'\ +b'\xf0\x00\xf0\x60\xf0\x00\xf0\xe1\xf8\x00\xe0\xe3\x78\x00\xe0\xc7'\ +b'\x7f\x00\xc0\xc3\x7f\x00\x80\x83\x3f\x00\x00\x02\x0f\x00\x00\x00'\ +b'\x00\x00\x0f\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00'\ +b'\x12\x00\xf0\xff\x07\x00\xf0\xff\x1f\x00\xf0\xff\x3f\x00\xf0\xff'\ +b'\x7f\x00\x00\x00\x7c\x00\x00\x00\xf8\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf8\x00\x00\x00'\ +b'\x7c\x00\xf0\xff\x7f\x00\xf0\xff\x3f\x00\xf0\xff\x1f\x00\xf0\xff'\ +b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x30\x00\x00\x00'\ +b'\xf0\x01\x00\x00\xf0\x0f\x00\x00\xf0\x7f\x00\x00\xc0\xff\x03\x00'\ +b'\x00\xfe\x1f\x00\x00\xe0\xff\x00\x00\x00\xff\x00\x00\x00\xf8\x00'\ +b'\x00\x00\xff\x00\x00\xe0\xff\x00\x00\xfe\x1f\x00\xc0\xff\x03\x00'\ +b'\xf0\x7f\x00\x00\xf0\x0f\x00\x00\xf0\x01\x00\x00\x30\x00\x00\x00'\ +b'\x19\x00\x70\x00\x00\x00\xf0\x03\x00\x00\xf0\x3f\x00\x00\xf0\xff'\ +b'\x01\x00\x80\xff\x1f\x00\x00\xf8\xff\x00\x00\x80\xff\x00\x00\x00'\ +b'\xfc\x00\x00\xe0\xff\x00\x00\xff\xff\x00\xf0\xff\x07\x00\xf0\x3f'\ +b'\x00\x00\xf0\x03\x00\x00\xf0\x1f\x00\x00\xf0\xff\x03\x00\x80\xff'\ +b'\x7f\x00\x00\xf8\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\xf0'\ +b'\xff\x00\x00\xff\x3f\x00\xf0\xff\x07\x00\xf0\x7f\x00\x00\xf0\x07'\ +b'\x00\x00\xf0\x00\x00\x00\x11\x00\x10\x00\x80\x00\x30\x00\xe0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x01\xfc\x00\xf0\x07\xfe\x00\xc0\x9f\x3f\x00'\ +b'\x80\xff\x0f\x00\x00\xfe\x07\x00\x00\xfc\x01\x00\x00\xfe\x07\x00'\ +b'\x80\xff\x0f\x00\xc0\x9f\x3f\x00\xf0\x07\x7f\x00\xf0\x03\xfc\x00'\ +b'\xf0\x00\xf8\x00\x70\x00\xe0\x00\x10\x00\x80\x00\x12\x00\x10\x00'\ +b'\x00\x00\x30\x00\x00\x00\xf0\x00\x00\x00\xf0\x03\x00\x00\xf0\x0f'\ +b'\x00\x00\xc0\x3f\x00\x00\x00\x7f\x00\x00\x00\xfc\xff\x00\x00\xf0'\ +b'\xff\x00\x00\xf0\xff\x00\x00\xfc\xff\x00\x00\x7f\x00\x00\xc0\x3f'\ +b'\x00\x00\xf0\x0f\x00\x00\xf0\x03\x00\x00\xf0\x00\x00\x00\x30\x00'\ +b'\x00\x00\x10\x00\x00\x00\x11\x00\x00\x00\xe0\x00\xf0\x00\xf8\x00'\ +b'\xf0\x00\xfc\x00\xf0\x00\xfe\x00\xf0\x00\xff\x00\xf0\x80\xff\x00'\ +b'\xf0\xc0\xf3\x00\xf0\xf0\xf1\x00\xf0\xf8\xf0\x00\xf0\x7c\xf0\x00'\ +b'\xf0\x3e\xf0\x00\xf0\x0f\xf0\x00\xf0\x07\xf0\x00\xf0\x03\xf0\x00'\ +b'\xf0\x01\xf0\x00\xf0\x00\xf0\x00\x00\x00\x00\x00\x09\x00\xff\xff'\ +b'\xff\x0f\xff\xff\xff\x0f\xff\xff\xff\x0f\xff\xff\xff\x0f\x0f\x00'\ +b'\x00\x0f\x0f\x00\x00\x0f\x0f\x00\x00\x0f\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0a\x00\x04\x00\x00\x00\x3c\x00\x00\x00\xfc\x03\x00\x00'\ +b'\xfc\x1f\x00\x00\xf8\xff\x01\x00\xc0\xff\x1f\x00\x00\xfc\xff\x00'\ +b'\x00\xe0\xff\x00\x00\x00\xfe\x00\x00\x00\xe0\x00\x09\x00\x0f\x00'\ +b'\x00\x0f\x0f\x00\x00\x0f\x0f\x00\x00\x0f\xff\xff\xff\x0f\xff\xff'\ +b'\xff\x0f\xff\xff\xff\x0f\xff\xff\xff\x0f\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0f\x00\x00\x80\x01\x00\x00\xf0\x01\x00\x00\xfc\x01\x00'\ +b'\x80\xff\x01\x00\xe0\x3f\x00\x00\xf0\x07\x00\x00\xf0\x01\x00\x00'\ +b'\xf0\x03\x00\x00\xf0\x1f\x00\x00\xc0\xff\x00\x00\x00\xfe\x01\x00'\ +b'\x00\xf8\x01\x00\x00\xc0\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00'\ +b'\x17\x00\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x0c\x00'\ +b'\x08\x00\x00\x00\x18\x00\x00\x00\x38\x00\x00\x00\x78\x00\x00\x00'\ +b'\x70\x00\x00\x00\x60\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x11\x00\x00\x40\x3c\x00\x00\x30\x7e\x00\x00\x38\x7f\x00\x00\x7c'\ +b'\xff\x00\x00\x3e\xf3\x00\x00\x1e\xf3\x00\x00\x1e\xf3\x00\x00\x1e'\ +b'\xf1\x00\x00\x9e\x71\x00\x00\xbe\x79\x00\x00\xfc\xff\x00\x00\xfc'\ +b'\xff\x00\x00\xf8\xff\x00\x00\xf0\xff\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x12\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\x00\x7c\x3c\x00\x00\x3c\x78\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00'\ +b'\x00\x3e\xf8\x00\x00\x7c\x7c\x00\x00\xfc\x7f\x00\x00\xf8\x3f\x00'\ +b'\x00\xf0\x1f\x00\x00\xc0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x10\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc'\ +b'\x7f\x00\x00\x7c\x7c\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00\x00\x1e'\ +b'\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x3c\xf8\x00\x00\x7c'\ +b'\x7c\x00\x00\x78\x38\x00\x00\x30\x18\x00\x00\x20\x08\x00\x00\x00'\ +b'\x00\x00\x12\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00'\ +b'\x00\xfc\x7f\x00\x00\x7e\xfc\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x3c\x78\x00\x00\x7c\x7c\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\xe0'\ +b'\x0f\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc\x7f\x00\x00\xdc'\ +b'\x7b\x00\x00\xde\xf3\x00\x00\xde\xf3\x00\x00\xde\xf3\x00\x00\xde'\ +b'\xf3\x00\x00\xde\xfb\x00\x00\xfc\x7b\x00\x00\xfc\x7b\x00\x00\xf8'\ +b'\x33\x00\x00\xf0\x03\x00\x00\x80\x03\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x1c\x00\x00\x00\x1c\x00\x00\xc0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf8\xff\xff\x00\x78\x1c\x00\x00\x3c\x1c\x00\x00'\ +b'\x3c\x1c\x00\x00\x11\x00\x00\xc0\x07\x08\x00\xf0\x1f\x1c\x00\xf8'\ +b'\x3f\x3c\x00\xfc\x7f\x3c\x00\x7e\xfc\x7c\x00\x3e\xf8\x78\x00\x1e'\ +b'\xf0\x78\x00\x1e\xf0\x78\x00\x1e\xf0\x78\x00\x3c\x78\x7c\x00\x78'\ +b'\x7c\x3e\x00\xfe\xff\x3f\x00\xfe\xff\x1f\x00\xfe\xff\x0f\x00\xfe'\ +b'\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\xfc\xff\xff\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\x00\x3c\x00\x00'\ +b'\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x3e\x00\x00\x00\xfc\xff\x00\x00\xfc\xff\x00\x00\xf8\xff\x00'\ +b'\x00\xe0\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x60\xfe'\ +b'\xff\x00\xf0\xfe\xff\x00\xf0\xfe\xff\x00\x60\xfe\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x78'\ +b'\x00\x00\x00\x78\x60\xfe\xff\x7f\xf0\xfe\xff\x3f\xf0\xfe\xff\x3f'\ +b'\x60\xfe\xff\x0f\x10\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff'\ +b'\xff\x00\xfc\xff\xff\x00\x00\xc0\x07\x00\x00\xe0\x03\x00\x00\xf0'\ +b'\x07\x00\x00\xfc\x0f\x00\x00\xfe\x3f\x00\x00\x3e\xff\x00\x00\x1e'\ +b'\xfc\x00\x00\x0e\xf8\x00\x00\x06\xe0\x00\x00\x02\xc0\x00\x00\x00'\ +b'\x80\x00\x00\x00\x00\x00\x07\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x1a\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe'\ +b'\xff\x00\x00\xfe\xff\x00\x00\x3c\x00\x00\x00\x1e\x00\x00\x00\x1e'\ +b'\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x3e\x00\x00\x00\xfc'\ +b'\xff\x00\x00\xfc\xff\x00\x00\xf8\xff\x00\x00\xfc\xff\x00\x00\x3e'\ +b'\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x3e'\ +b'\x00\x00\x00\xfc\xff\x00\x00\xfc\xff\x00\x00\xf8\xff\x00\x00\xe0'\ +b'\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00'\ +b'\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00'\ +b'\x00\x3c\x00\x00\x00\x1c\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x1e\x00\x00\x00\x3e\x00\x00\x00\xfc\xff\x00\x00\xfc\xff\x00'\ +b'\x00\xf8\xff\x00\x00\xe0\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x11\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc'\ +b'\x7f\x00\x00\x7c\x7c\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00\x00\x1e'\ +b'\xf0\x00\x00\x1e\xf0\x00\x00\x3e\xf8\x00\x00\x7c\x7c\x00\x00\xfc'\ +b'\x7f\x00\x00\xf8\x3f\x00\x00\xf0\x1f\x00\x00\xc0\x07\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x12\x00\x00\xfe\xff\x7f\x00\xfe\xff\x7f'\ +b'\x00\xfe\xff\x7f\x00\xfe\xff\x7f\x00\x7c\x7c\x00\x00\x3c\x78\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00'\ +b'\x00\x3e\xf8\x00\x00\x7c\x7c\x00\x00\xfc\x7f\x00\x00\xf8\x3f\x00'\ +b'\x00\xf0\x1f\x00\x00\xc0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x11\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc'\ +b'\x7f\x00\x00\x7e\xfc\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00\x00\x1e'\ +b'\xf0\x00\x00\x1e\xf0\x00\x00\x3c\x78\x00\x00\x7c\x7c\x00\x00\xfe'\ +b'\xff\x7f\x00\xfe\xff\x7f\x00\xfe\xff\x7f\x00\xfe\xff\x7f\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0a\x00\x00\xfe\xff\x00\x00\xfe\xff\x00'\ +b'\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\x3c\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x1e\x00\x00\x00\x0e\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00'\ +b'\x0f\x00\x00\x00\x08\x00\x00\x70\x18\x00\x00\xfc\x38\x00\x00\xfc'\ +b'\x7d\x00\x00\xfe\xf9\x00\x00\x9e\xf1\x00\x00\x9e\xf1\x00\x00\x9e'\ +b'\xf1\x00\x00\x9e\xf3\x00\x00\x3e\xf3\x00\x00\x3c\x7f\x00\x00\x3c'\ +b'\x7f\x00\x00\x38\x3e\x00\x00\x20\x1c\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x0e\x00\x00\x00\x0e\x00\x00\xfc\xff\x1f\x00\xfc\xff\x3f\x00'\ +b'\xfc\xff\x7f\x00\xfc\xff\xff\x00\x00\x0e\xf0\x00\x00\x0e\xf0\x00'\ +b'\x00\x0e\xf0\x00\x10\x00\x00\xfe\x0f\x00\x00\xfe\x3f\x00\x00\xfe'\ +b'\x7f\x00\x00\xfe\x7f\x00\x00\x00\xf8\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\x70\x00\x00\x00\x78\x00\x00\xfe'\ +b'\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x06\x00\x00\x00\x3e\x00\x00'\ +b'\x00\xfe\x00\x00\x00\xfe\x07\x00\x00\xf8\x3f\x00\x00\xc0\xff\x00'\ +b'\x00\x00\xfe\x00\x00\x00\xf0\x00\x00\x00\xfe\x00\x00\xc0\xff\x00'\ +b'\x00\xf8\x1f\x00\x00\xfe\x07\x00\x00\xfe\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x06\x00\x00\x16\x00\x00\x06\x00\x00\x00\x7e\x00\x00\x00\xfe'\ +b'\x03\x00\x00\xfe\x1f\x00\x00\xf0\xff\x00\x00\x00\xff\x00\x00\x00'\ +b'\xf8\x00\x00\x80\xff\x00\x00\xf8\xff\x00\x00\xfe\x0f\x00\x00\xfe'\ +b'\x00\x00\x00\xfe\x00\x00\x00\xfe\x07\x00\x00\xf8\x7f\x00\x00\x80'\ +b'\xff\x00\x00\x00\xf8\x00\x00\x00\xff\x00\x00\xf0\xff\x00\x00\xfe'\ +b'\x3f\x00\x00\xfe\x03\x00\x00\x7e\x00\x00\x00\x0e\x00\x00\x0f\x00'\ +b'\x00\x02\x80\x00\x00\x06\xe0\x00\x00\x1e\xf0\x00\x00\x3e\xfc\x00'\ +b'\x00\xfe\x7e\x00\x00\xf8\x3f\x00\x00\xe0\x0f\x00\x00\xe0\x07\x00'\ +b'\x00\xf8\x1f\x00\x00\xfc\x7f\x00\x00\x7e\xfc\x00\x00\x1e\xf8\x00'\ +b'\x00\x0e\xe0\x00\x00\x02\xc0\x00\x00\x00\x80\x00\x0f\x00\x00\x06'\ +b'\x00\x00\x00\x3e\x00\x00\x00\xfe\x00\x78\x00\xfe\x07\x78\x00\xf8'\ +b'\x1f\x7c\x00\xc0\xff\x7f\x00\x00\xfe\x3f\x00\x00\xf0\x1f\x00\x00'\ +b'\xfe\x07\x00\xc0\xff\x00\x00\xf8\x1f\x00\x00\xfe\x07\x00\x00\xfe'\ +b'\x00\x00\x00\x1e\x00\x00\x00\x06\x00\x00\x0e\x00\x00\x00\xe0\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf8\x00\x00\x1e\xfc\x00\x00\x1e\xfe\x00'\ +b'\x00\x1e\xff\x00\x00\x9e\xf7\x00\x00\xde\xf3\x00\x00\xfe\xf1\x00'\ +b'\x00\xfe\xf0\x00\x00\x7e\xf0\x00\x00\x3e\xf0\x00\x00\x1e\xf0\x00'\ +b'\x00\x00\x00\x00\x0a\x00\x00\x60\x00\x00\x00\xf0\x00\x00\x00\xf0'\ +b'\x00\x00\xf8\xff\xff\x01\xfc\xff\xff\x03\xfe\x9f\xff\x07\xfe\x0f'\ +b'\xff\x07\x0e\x00\x00\x07\x0e\x00\x00\x07\x00\x00\x00\x00\x08\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x09\x00\x0e\x00\x00\x07\x0e\x00\x00\x07\xfe\x0f\xff\x07\xfe\x9f'\ +b'\xff\x07\xfc\xff\xff\x03\xf8\xff\xff\x01\x00\xf0\x00\x00\x00\xf0'\ +b'\x00\x00\x00\x60\x00\x00\x0c\x00\x30\x00\x00\x00\x38\x00\x00\x00'\ +b'\x18\x00\x00\x00\x18\x00\x00\x00\x18\x00\x00\x00\x30\x00\x00\x00'\ +b'\x30\x00\x00\x00\x30\x00\x00\x00\x38\x00\x00\x00\x18\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00' + +_index =\ +b'\x00\x00\x3e\x00\x64\x00\x86\x00\xb0\x00\xee\x00\x28\x01\x86\x01'\ +b'\xd4\x01\xea\x01\x10\x02\x36\x02\x64\x02\xa2\x02\xbc\x02\xe2\x02'\ +b'\xfc\x02\x26\x03\x6c\x03\x92\x03\xd4\x03\x16\x04\x58\x04\x96\x04'\ +b'\xd8\x04\x12\x05\x54\x05\x96\x05\xb0\x05\xca\x05\xfc\x05\x36\x06'\ +b'\x68\x06\xa6\x06\x00\x07\x4e\x07\x98\x07\xea\x07\x38\x08\x7e\x08'\ +b'\xc0\x08\x16\x09\x68\x09\x86\x09\xc4\x09\x0e\x0a\x50\x0a\xb2\x0a'\ +b'\x04\x0b\x5e\x0b\xa8\x0b\xfe\x0b\x48\x0c\x92\x0c\xd0\x0c\x1a\x0d'\ +b'\x60\x0d\xc6\x0d\x0c\x0e\x56\x0e\x9c\x0e\xc2\x0e\xec\x0e\x12\x0f'\ +b'\x50\x0f\xae\x0f\xe0\x0f\x26\x10\x70\x10\xb2\x10\xfc\x10\x3e\x11'\ +b'\x64\x11\xaa\x11\xec\x11\x0a\x12\x24\x12\x66\x12\x84\x12\xee\x12'\ +b'\x30\x13\x76\x13\xc0\x13\x06\x14\x30\x14\x6e\x14\x94\x14\xd6\x14'\ +b'\x14\x15\x6e\x15\xac\x15\xea\x15\x24\x16\x4e\x16\x70\x16\x96\x16'\ +b'\xc8\x16' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_width(s): + width = 0 + for ch in s: + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width += int.from_bytes(_font[offset:offset + 2], 'little') + return width + +def get_ch(ch): + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width = int.from_bytes(_font[offset:offset + 2], 'little') + next_offs = _chr_addr(ordch +1) + return _mvfont[offset + 2:next_offs], width + diff --git a/GW-custom/uPyLoRaWAN/uPySensors/hcsr04.py b/GW-custom/uPyLoRaWAN/uPySensors/hcsr04.py new file mode 100644 index 0000000000000000000000000000000000000000..91df95e25733d83cd93944bca1b1ee52e2f4aaf6 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/hcsr04.py @@ -0,0 +1,93 @@ +import time +from uos import uname +from machine import Pin + +try: + from machine import time_pulse_us +except: + from pycom import pulses_get + +#__author__ = 'Roberto Sánchez' +#__license__= "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0" +#__author-update__ = "Mauro Riva" + +class HCSR04: + """ + Driver to use the untrasonic sensor HC-SR04. + The sensor range is between 2cm and 4m. + + The timeouts received listening to echo pin are converted to OSError('Out of range') + + """ + # echo_timeout_us is based in chip range limit (400cm) + def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30): + """ + trigger_pin: Output pin to send pulses + echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor + echo_timeout_us: Timeout in microseconds to listen to echo pin. + By default is based in sensor limit range (4m) + """ + self.echo_timeout_us = echo_timeout_us + # Init trigger pin (out) + self.trigger = Pin(trigger_pin, mode=Pin.OUT) + self.trigger.value(0) + # Init echo pin (in) + if (uname().sysname == 'WiPy'): + self.echo = Pin(echo_pin, mode=Pin.OPEN_DRAIN) + else: + self.echo = Pin(echo_pin, mode=Pin.IN) + + def _send_pulse_and_wait(self): + """ + Send the pulse to trigger and listen on echo pin. + We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received. + """ + self.trigger.value(0) # Stabilize the sensor + time.sleep_us(5) + self.trigger.value(1) + # Send a 10us pulse. + time.sleep_us(10) + self.trigger.value(0) + try: + if (uname().sysname == 'WiPy'): + pulse_list = pulses_get(self.echo, self.echo_timeout_us) + if(len(pulse_list) == 0): + pulse_time = -1 + else: + pulse_time = pulse_list[0][1] + else: + pulse_time = time_pulse_us(self.echo, 1, self.echo_timeout_us) + + return pulse_time + except OSError as ex: + if ex.args[0] == 110: # 110 = ETIMEDOUT + raise OSError('Out of range') + raise ex + + def distance_mm(self): + """ + Get the distance in milimeters without floating point operations. + """ + pulse_time = self._send_pulse_and_wait() + + # To calculate the distance we get the pulse_time and divide it by 2 + # (the pulse walk the distance twice) and by 29.1 becasue + # the sound speed on air (343.2 m/s), that It's equivalent to + # 0.34320 mm/us that is 1mm each 2.91us + # pulse_time // 2 // 2.91 -> pulse_time // 5.82 -> pulse_time * 100 // 582 + mm = pulse_time * 100 // 582 + return mm + + def distance_cm(self): + """ + Get the distance in centimeters with floating point operations. + It returns a float + """ + pulse_time = self._send_pulse_and_wait() + + # To calculate the distance we get the pulse_time and divide it by 2 + # (the pulse walk the distance twice) and by 29.1 becasue + # the sound speed on air (343.2 m/s), that It's equivalent to + # 0.034320 cm/us that is 1cm each 29.1us + cms = (pulse_time / 2) / 29.1 + return cms diff --git a/GW-custom/uPyLoRaWAN/uPySensors/ili934xnew.py b/GW-custom/uPyLoRaWAN/uPySensors/ili934xnew.py new file mode 100644 index 0000000000000000000000000000000000000000..44be116454469c6fa05ecef00cbe77a12c5b7681 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/ili934xnew.py @@ -0,0 +1,326 @@ +# This is an adapted version of the ILI934X driver as below. +# It works with multiple fonts and also works with the esp32 H/W SPI implementation +# Also includes a word wrap print function +# Proportional fonts are generated by Peter Hinch's Font-to-py +# MIT License; Copyright (c) 2017 Jeffrey N. Magee + +# This file is part of MicroPython ILI934X driver +# Copyright (c) 2016 - 2017 Radomir Dopieralski, Mika Tuupola +# +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# Project home: +# https://github.com/tuupola/micropython-ili934x + +import time +import ustruct +import framebuf +from micropython import const + +_RDDSDR = const(0x0f) # Read Display Self-Diagnostic Result +_SLPOUT = const(0x11) # Sleep Out +_GAMSET = const(0x26) # Gamma Set +_DISPOFF = const(0x28) # Display Off +_DISPON = const(0x29) # Display On +_CASET = const(0x2a) # Column Address Set +_PASET = const(0x2b) # Page Address Set +_RAMWR = const(0x2c) # Memory Write +_RAMRD = const(0x2e) # Memory Read +_MADCTL = const(0x36) # Memory Access Control +_VSCRSADD = const(0x37) # Vertical Scrolling Start Address +_PIXSET = const(0x3a) # Pixel Format Set +_PWCTRLA = const(0xcb) # Power Control A +_PWCRTLB = const(0xcf) # Power Control B +_DTCTRLA = const(0xe8) # Driver Timing Control A +_DTCTRLB = const(0xea) # Driver Timing Control B +_PWRONCTRL = const(0xed) # Power on Sequence Control +_PRCTRL = const(0xf7) # Pump Ratio Control +_PWCTRL1 = const(0xc0) # Power Control 1 +_PWCTRL2 = const(0xc1) # Power Control 2 +_VMCTRL1 = const(0xc5) # VCOM Control 1 +_VMCTRL2 = const(0xc7) # VCOM Control 2 +_FRMCTR1 = const(0xb1) # Frame Rate Control 1 +_DISCTRL = const(0xb6) # Display Function Control +_ENA3G = const(0xf2) # Enable 3G +_PGAMCTRL = const(0xe0) # Positive Gamma Control +_NGAMCTRL = const(0xe1) # Negative Gamma Control + +_CHUNK = const(1024) #maximum number of pixels per spi write + +def color565(r, g, b): + return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 + +class ILI9341: + + def __init__(self, spi, cs, dc, rst, w, h, r, font): + self.spi = spi + self.cs = cs + self.dc = dc + self.rst = rst + self._init_width = w + self._init_height = h + self.width = w + self.height = h + self.rotation = r + self.cs.init(self.cs.OUT, value=1) + self.dc.init(self.dc.OUT, value=0) + self.rst.init(self.rst.OUT, value=0) + self.reset() + self.init() + self._scroll = 0 + self._buf = bytearray(_CHUNK * 2) + self._colormap = bytearray(b'\x00\x00\xFF\xFF') #default white foregraound, black background + self._x = 0 + self._y = 0 + self._font = font + self.scrolling = False + + def set_color(self, fg, bg): + self._colormap[0] = bg>>8 + self._colormap[1] = bg & 255 + self._colormap[2] = fg>>8 + self._colormap[3] = fg & 255 + + def set_pos(self, x, y): + self._x = x + self._y = y + + def reset_scroll(self): + self.scrolling = False + self._scroll = 0 + self.scroll(0) + + def set_font(self, font): + self._font = font + + def init(self): + for command, data in ( + (_RDDSDR, b"\x03\x80\x02"), + (_PWCRTLB, b"\x00\xc1\x30"), + (_PWRONCTRL, b"\x64\x03\x12\x81"), + (_DTCTRLA, b"\x85\x00\x78"), + (_PWCTRLA, b"\x39\x2c\x00\x34\x02"), + (_PRCTRL, b"\x20"), + (_DTCTRLB, b"\x00\x00"), + (_PWCTRL1, b"\x23"), + (_PWCTRL2, b"\x10"), + (_VMCTRL1, b"\x3e\x28"), + (_VMCTRL2, b"\x86")): + self._write(command, data) + + if self.rotation == 0: # 0 deg + self._write(_MADCTL, b"\x48") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 1: # 90 deg + self._write(_MADCTL, b"\x28") + self.width = self._init_width + self.height = self._init_height + elif self.rotation == 2: # 180 deg + self._write(_MADCTL, b"\x88") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 3: # 270 deg + self._write(_MADCTL, b"\xE8") + self.width = self._init_width + self.height = self._init_height + elif self.rotation == 4: # Mirrored + 0 deg + self._write(_MADCTL, b"\xC8") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 5: # Mirrored + 90 deg + self._write(_MADCTL, b"\x68") + self.width = self._init_width + self.height = self._init_height + elif self.rotation == 6: # Mirrored + 180 deg + self._write(_MADCTL, b"\x08") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 7: # Mirrored + 270 deg + self._write(_MADCTL, b"\xA8") + self.width = self._init_width + self.height = self._init_height + else: + self._write(_MADCTL, b"\x08") + + for command, data in ( + (_PIXSET, b"\x55"), + (_FRMCTR1, b"\x00\x18"), + (_DISCTRL, b"\x08\x82\x27"), + (_ENA3G, b"\x00"), + (_GAMSET, b"\x01"), + (_PGAMCTRL, b"\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"), + (_NGAMCTRL, b"\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f")): + self._write(command, data) + self._write(_SLPOUT) + time.sleep_ms(120) + self._write(_DISPON) + + def reset(self): + self.rst(0) + time.sleep_ms(50) + self.rst(1) + time.sleep_ms(50) + + def _write(self, command, data=None): + self.dc(0) + self.cs(0) + self.spi.write(bytearray([command])) + self.cs(1) + if data is not None: + self._data(data) + + def _data(self, data): + self.dc(1) + self.cs(0) + self.spi.write(data) + self.cs(1) + + def _writeblock(self, x0, y0, x1, y1, data=None): + self._write(_CASET, ustruct.pack(">HH", x0, x1)) + self._write(_PASET, ustruct.pack(">HH", y0, y1)) + self._write(_RAMWR, data) + + def _readblock(self, x0, y0, x1, y1): + self._write(_CASET, ustruct.pack(">HH", x0, x1)) + self._write(_PASET, ustruct.pack(">HH", y0, y1)) + if data is None: + return self._read(_RAMRD, (x1 - x0 + 1) * (y1 - y0 + 1) * 3) + + def _read(self, command, count): + self.dc(0) + self.cs(0) + self.spi.write(bytearray([command])) + data = self.spi.read(count) + self.cs(1) + return data + + def pixel(self, x, y, color=None): + if color is None: + r, b, g = self._readblock(x, y, x, y) + return color565(r, g, b) + if not 0 <= x < self.width or not 0 <= y < self.height: + return + self._writeblock(x, y, x, y, ustruct.pack(">H", color)) + + def fill_rectangle(self, x, y, w, h, color=None): + x = min(self.width - 1, max(0, x)) + y = min(self.height - 1, max(0, y)) + w = min(self.width - x, max(1, w)) + h = min(self.height - y, max(1, h)) + if color: + color = ustruct.pack(">H", color) + else: + color = self._colormap[0:2] #background + for i in range(_CHUNK): + self._buf[2*i]=color[0]; self._buf[2*i+1]=color[1] + chunks, rest = divmod(w * h, _CHUNK) + self._writeblock(x, y, x + w - 1, y + h - 1, None) + if chunks: + for count in range(chunks): + self._data(self._buf) + if rest != 0: + mv = memoryview(self._buf) + self._data(mv[:rest*2]) + + def erase(self): + self.fill_rectangle(0, 0, self.width, self.height) + + def blit(self, bitbuff, x, y, w, h): + x = min(self.width - 1, max(0, x)) + y = min(self.height - 1, max(0, y)) + w = min(self.width - x, max(1, w)) + h = min(self.height - y, max(1, h)) + chunks, rest = divmod(w * h, _CHUNK) + self._writeblock(x, y, x + w - 1, y + h - 1, None) + written = 0 + for iy in range(h): + for ix in range(w): + index = ix+iy*w - written + if index >=_CHUNK: + self._data(self._buf) + written += _CHUNK + index -= _CHUNK + c = bitbuff.pixel(ix,iy) + self._buf[index*2] = self._colormap[c*2] + self._buf[index*2+1] = self._colormap[c*2+1] + rest = w*h - written + if rest != 0: + mv = memoryview(self._buf) + self._data(mv[:rest*2]) + + def chars(self, str, x, y): + str_w = self._font.get_width(str) + div, rem = divmod(self._font.height(),8) + nbytes = div+1 if rem else div + buf = bytearray(str_w * nbytes) + pos = 0 + for ch in str: + glyph, char_w = self._font.get_ch(ch) + for row in range(nbytes): + index = row*str_w + pos + for i in range(char_w): + buf[index+i] = glyph[nbytes*i+row] + pos += char_w + fb = framebuf.FrameBuffer(buf,str_w, self._font.height(), framebuf.MONO_VLSB) + self.blit(fb,x,y,str_w,self._font.height()) + return x+str_w + + def scroll(self, dy): + self._scroll = (self._scroll + dy) % self.height + self._write(_VSCRSADD, ustruct.pack(">H", self._scroll)) + + def next_line(self, cury, char_h): + global scrolling + if not self.scrolling: + res = cury + char_h + self.scrolling = (res >= self.height) + if self.scrolling: + self.scroll(char_h) + res = (self.height - char_h + self._scroll)%self.height + self.fill_rectangle(0, res, self.width, self._font.height()) + return res + + def write(self, text): #does character wrap, compatible with stream output + curx = self._x; cury = self._y + char_h = self._font.height() + width = 0 + written = 0 + for pos, ch in enumerate(text): + if ch == '\n': + if pos>0: + self.chars(text[written:pos],curx,cury) + curx = 0; written = pos+1; width = 0 + cury = self.next_line(cury,char_h) + else: + char_w = self._font.get_width(ch) + if curx + width + char_w >= self.width: + self.chars(text[written:pos], curx,cury) + curx = 0 ; written = pos; width = char_h + cury = self.next_line(cury,char_h) + else: + width += char_w + if written<len(text): + curx = self.chars(text[written:], curx,cury) + self._x = curx; self._y = cury + + + def print(self, text): #does word wrap, leaves self._x unchanged + cury = self._y; curx = self._x + char_h = self._font.height() + char_w = self._font.max_width() + lines = text.split('\n') + for line in lines: + words = line.split(' ') + for word in words: + if curx + self._font.get_width(word) >= self.width: + curx = self._x; cury = self.next_line(cury,char_h) + while self._font.get_width(word) > self.width: + self.chars(word[:self.width//char_w],curx,cury) + word = word[self.width//char_w:] + cury = self.next_line(cury,char_h) + if len(word)>0: + curx = self.chars(word+' ', curx,cury) + curx = self._x; cury = self.next_line(cury,char_h) + self._y = cury diff --git a/GW-custom/uPyLoRaWAN/uPySensors/imu.py b/GW-custom/uPyLoRaWAN/uPySensors/imu.py new file mode 100644 index 0000000000000000000000000000000000000000..d1f99b21a058015d08219b017ce2ef5897bd14b5 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/imu.py @@ -0,0 +1,409 @@ +# -*- coding: utf-8 -*- +# imu.py MicroPython driver for the InvenSense inertial measurement units +# This is the base class +# Adapted from Sebastian Plamauer's MPU9150 driver: +# https://github.com/micropython-IMU/micropython-mpu9150.git +# Authors Peter Hinch, Sebastian Plamauer +# V0.2 17th May 2017 Platform independent: utime and machine replace pyb + +''' +mpu9250 is a micropython module for the InvenSense MPU9250 sensor. +It measures acceleration, turn rate and the magnetic field in three axis. +mpu9150 driver modified for the MPU9250 by Peter Hinch + +The MIT License (MIT) +Copyright (c) 2014 Sebastian Plamauer, oeplse@gmail.com, Peter Hinch +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +''' + +# User access is now by properties e.g. +# myimu = MPU9250('X') +# magx = myimu.mag.x +# accelxyz = myimu.accel.xyz +# Error handling: on code used for initialisation, abort with message +# At runtime try to continue returning last good data value. We don't want aircraft +# crashing. However if the I2C has crashed we're probably stuffed. + +from utime import sleep_ms +from machine import I2C, Pin +from .vector3d import Vector3d +from os import uname + + +class MPUException(OSError): + ''' + Exception for MPU devices + ''' + pass + + +def bytes_toint(msb, lsb): + ''' + Convert two bytes to signed integer (big endian) + for little endian reverse msb, lsb arguments + Can be used in an interrupt handler + ''' + if not msb & 0x80: + return msb << 8 | lsb # +ve + return - (((msb ^ 255) << 8) | (lsb ^ 255) + 1) + + +class MPU6050(object): + ''' + Module for InvenSense IMUs. Base class implements MPU6050 6DOF sensor, with + features common to MPU9150 and MPU9250 9DOF sensors. + ''' + + _I2Cerror = "I2C failure when communicating with IMU" + _mpu_addr = (104, 105) # addresses of MPU9150/MPU6050. There can be two devices + _chip_id = 104 # MPU6050 + + def __init__(self, side_str, dev_pin=(15, 4), device_addr=None, transposition=(0, 1, 2), scaling=(1, 1, 1)): + + self._accel = Vector3d(transposition, scaling, self._accel_callback) + self._gyro = Vector3d(transposition, scaling, self._gyro_callback) + self.buf1 = bytearray(1) # Pre-allocated buffers for reads: allows reads to + self.buf2 = bytearray(2) # be done in interrupt handlers + self.buf3 = bytearray(3) + self.buf6 = bytearray(6) + + sleep_ms(200) # Ensure PSU and device have settled + + #if(uname().sysname == 'WiPy'): + # self._mpu_i2c = I2C(side_str) + if isinstance(side_str, str): # Non-pyb targets may use other than X or Y + self._mpu_i2c = I2C(side_str) + elif isinstance(side_str, int): # WiPY &ESP32 targets + if (side_str == -1): + self._mpu_i2c = I2C(side_str, scl=Pin(dev_pin[0], mode=Pin.OUT), sda=Pin(dev_pin[1], mode=Pin.IN)) # ESP32 + else: + self._mpu_i2c = I2C(side_str) # WiPy + elif hasattr(side_str, 'readfrom'): # Soft or hard I2C instance. See issue #3097 + self._mpu_i2c = side_str + else: + raise ValueError("Invalid I2C instance") + + if device_addr is None: + devices = set(self._mpu_i2c.scan()) + mpus = devices.intersection(set(self._mpu_addr)) + number_of_mpus = len(mpus) + if number_of_mpus == 0: + raise MPUException("No MPU's detected") + elif number_of_mpus == 1: + self.mpu_addr = mpus.pop() + else: + raise ValueError("Two MPU's detected: must specify a device address") + else: + if device_addr not in (0, 1): + raise ValueError('Device address must be 0 or 1') + self.mpu_addr = self._mpu_addr[device_addr] + + self.chip_id # Test communication by reading chip_id: throws exception on error + # Can communicate with chip. Set it up. + self.wake() # wake it up + self.passthrough = True # Enable mag access from main I2C bus + self.accel_range = 0 # default to highest sensitivity + self.gyro_range = 0 # Likewise for gyro + + # read from device + def _read(self, buf, memaddr, addr): # addr = I2C device address, memaddr = memory location within the I2C device + ''' + Read bytes to pre-allocated buffer Caller traps OSError. + ''' + self._mpu_i2c.readfrom_mem_into(addr, memaddr, buf) + + # write to device + def _write(self, data, memaddr, addr): + ''' + Perform a memory write. Caller should trap OSError. + ''' + self.buf1[0] = data + self._mpu_i2c.writeto_mem(addr, memaddr, self.buf1) + + # wake + def wake(self): + ''' + Wakes the device. + ''' + try: + self._write(0x01, 0x6B, self.mpu_addr) # Use best clock source + except OSError: + raise MPUException(self._I2Cerror) + return 'awake' + + # mode + def sleep(self): + ''' + Sets the device to sleep mode. + ''' + try: + self._write(0x40, 0x6B, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + return 'asleep' + + # chip_id + @property + def chip_id(self): + ''' + Returns Chip ID + ''' + try: + self._read(self.buf1, 0x75, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + chip_id = int(self.buf1[0]) + if chip_id != self._chip_id: + raise ValueError('Bad chip ID ({0}!={1}) retrieved: MPU communication failure'.format(chip_id, self._chip_id)) + return chip_id + + @property + def sensors(self): + ''' + returns sensor objects accel, gyro + ''' + return self._accel, self._gyro + + # get temperature + @property + def temperature(self): + ''' + Returns the temperature in degree C. + ''' + try: + self._read(self.buf2, 0x41, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + return bytes_toint(self.buf2[0], self.buf2[1])/340 + 35 # I think + + # passthrough + @property + def passthrough(self): + ''' + Returns passthrough mode True or False + ''' + try: + self._read(self.buf1, 0x37, self.mpu_addr) + return self.buf1[0] & 0x02 > 0 + except OSError: + raise MPUException(self._I2Cerror) + + @passthrough.setter + def passthrough(self, mode): + ''' + Sets passthrough mode True or False + ''' + if type(mode) is bool: + val = 2 if mode else 0 + try: + self._write(val, 0x37, self.mpu_addr) # I think this is right. + self._write(0x00, 0x6A, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + else: + raise ValueError('pass either True or False') + + # sample rate. Not sure why you'd ever want to reduce this from the default. + @property + def sample_rate(self): + ''' + Get sample rate as per Register Map document section 4.4 + SAMPLE_RATE= Internal_Sample_Rate / (1 + rate) + default rate is zero i.e. sample at internal rate. + ''' + try: + self._read(self.buf1, 0x19, self.mpu_addr) + return self.buf1[0] + except OSError: + raise MPUException(self._I2Cerror) + + @sample_rate.setter + def sample_rate(self, rate): + ''' + Set sample rate as per Register Map document section 4.4 + ''' + if rate < 0 or rate > 255: + raise ValueError("Rate must be in range 0-255") + try: + self._write(rate, 0x19, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + + # Low pass filters. Using the filter_range property of the MPU9250 is + # harmless but gyro_filter_range is preferred and offers an extra setting. + @property + def filter_range(self): + ''' + Returns the gyro and temperature sensor low pass filter cutoff frequency + Pass: 0 1 2 3 4 5 6 + Cutoff (Hz): 250 184 92 41 20 10 5 + Sample rate (KHz): 8 1 1 1 1 1 1 + ''' + try: + self._read(self.buf1, 0x1A, self.mpu_addr) + res = self.buf1[0] & 7 + except OSError: + raise MPUException(self._I2Cerror) + return res + + @filter_range.setter + def filter_range(self, filt): + ''' + Sets the gyro and temperature sensor low pass filter cutoff frequency + Pass: 0 1 2 3 4 5 6 + Cutoff (Hz): 250 184 92 41 20 10 5 + Sample rate (KHz): 8 1 1 1 1 1 1 + ''' + # set range + if filt in range(7): + try: + self._write(filt, 0x1A, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + else: + raise ValueError('Filter coefficient must be between 0 and 6') + + # accelerometer range + @property + def accel_range(self): + ''' + Accelerometer range + Value: 0 1 2 3 + for range +/-: 2 4 8 16 g + ''' + try: + self._read(self.buf1, 0x1C, self.mpu_addr) + ari = self.buf1[0]//8 + except OSError: + raise MPUException(self._I2Cerror) + return ari + + @accel_range.setter + def accel_range(self, accel_range): + ''' + Set accelerometer range + Pass: 0 1 2 3 + for range +/-: 2 4 8 16 g + ''' + ar_bytes = (0x00, 0x08, 0x10, 0x18) + if accel_range in range(len(ar_bytes)): + try: + self._write(ar_bytes[accel_range], 0x1C, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + else: + raise ValueError('accel_range can only be 0, 1, 2 or 3') + + # gyroscope range + @property + def gyro_range(self): + ''' + Gyroscope range + Value: 0 1 2 3 + for range +/-: 250 500 1000 2000 degrees/second + ''' + # set range + try: + self._read(self.buf1, 0x1B, self.mpu_addr) + gri = self.buf1[0]//8 + except OSError: + raise MPUException(self._I2Cerror) + return gri + + @gyro_range.setter + def gyro_range(self, gyro_range): + ''' + Set gyroscope range + Pass: 0 1 2 3 + for range +/-: 250 500 1000 2000 degrees/second + ''' + gr_bytes = (0x00, 0x08, 0x10, 0x18) + if gyro_range in range(len(gr_bytes)): + try: + self._write(gr_bytes[gyro_range], 0x1B, self.mpu_addr) # Sets fchoice = b11 which enables filter + except OSError: + raise MPUException(self._I2Cerror) + else: + raise ValueError('gyro_range can only be 0, 1, 2 or 3') + + # Accelerometer + @property + def accel(self): + ''' + Acceleremoter object + ''' + return self._accel + + def _accel_callback(self): + ''' + Update accelerometer Vector3d object + ''' + try: + self._read(self.buf6, 0x3B, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + self._accel._ivector[0] = bytes_toint(self.buf6[0], self.buf6[1]) + self._accel._ivector[1] = bytes_toint(self.buf6[2], self.buf6[3]) + self._accel._ivector[2] = bytes_toint(self.buf6[4], self.buf6[5]) + scale = (16384, 8192, 4096, 2048) + self._accel._vector[0] = self._accel._ivector[0]/scale[self.accel_range] + self._accel._vector[1] = self._accel._ivector[1]/scale[self.accel_range] + self._accel._vector[2] = self._accel._ivector[2]/scale[self.accel_range] + + def get_accel_irq(self): + ''' + For use in interrupt handlers. Sets self._accel._ivector[] to signed + unscaled integer accelerometer values + ''' + self._read(self.buf6, 0x3B, self.mpu_addr) + self._accel._ivector[0] = bytes_toint(self.buf6[0], self.buf6[1]) + self._accel._ivector[1] = bytes_toint(self.buf6[2], self.buf6[3]) + self._accel._ivector[2] = bytes_toint(self.buf6[4], self.buf6[5]) + + # Gyro + @property + def gyro(self): + ''' + Gyroscope object + ''' + return self._gyro + + def _gyro_callback(self): + ''' + Update gyroscope Vector3d object + ''' + try: + self._read(self.buf6, 0x43, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + self._gyro._ivector[0] = bytes_toint(self.buf6[0], self.buf6[1]) + self._gyro._ivector[1] = bytes_toint(self.buf6[2], self.buf6[3]) + self._gyro._ivector[2] = bytes_toint(self.buf6[4], self.buf6[5]) + scale = (131, 65.5, 32.8, 16.4) + self._gyro._vector[0] = self._gyro._ivector[0]/scale[self.gyro_range] + self._gyro._vector[1] = self._gyro._ivector[1]/scale[self.gyro_range] + self._gyro._vector[2] = self._gyro._ivector[2]/scale[self.gyro_range] + + def get_gyro_irq(self): + ''' + For use in interrupt handlers. Sets self._gyro._ivector[] to signed + unscaled integer gyro values. Error trapping disallowed. + ''' + self._read(self.buf6, 0x43, self.mpu_addr) + self._gyro._ivector[0] = bytes_toint(self.buf6[0], self.buf6[1]) + self._gyro._ivector[1] = bytes_toint(self.buf6[2], self.buf6[3]) + self._gyro._ivector[2] = bytes_toint(self.buf6[4], self.buf6[5]) diff --git a/GW-custom/uPyLoRaWAN/uPySensors/mpu9250.py b/GW-custom/uPyLoRaWAN/uPySensors/mpu9250.py new file mode 100644 index 0000000000000000000000000000000000000000..3ad8c29e8af42f25fb986b8e5e8ac1bbed46ac58 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/mpu9250.py @@ -0,0 +1,218 @@ +# mpu9250.py MicroPython driver for the InvenSense MPU9250 inertial measurement unit +# Authors Peter Hinch, Sebastian Plamauer +# V0.5 17th June 2015 + +''' +mpu9250 is a micropython module for the InvenSense MPU9250 sensor. +It measures acceleration, turn rate and the magnetic field in three axis. + +The MIT License (MIT) +Copyright (c) 2014 Sebastian Plamauer, oeplse@gmail.com, Peter Hinch +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +''' + +from .imu import MPU6050, bytes_toint, MPUException +from .vector3d import Vector3d + + +class MPU9250(MPU6050): + ''' + MPU9250 constructor arguments + 1. side_str 'X' or 'Y' depending on the Pyboard I2C interface being used + 2. optional device_addr 0, 1 depending on the voltage applied to pin AD0 (Drotek default is 1) + if None driver will scan for a device (if one device only is on bus) + 3, 4. transposition, scaling optional 3-tuples allowing for outputs to be based on vehicle + coordinates rather than those of the sensor itself. See readme. + ''' + + _mag_addr = 12 # Magnetometer address + _chip_id = 113 + + def __init__(self, side_str, dev_pin=(15, 4), device_addr=None, transposition=(0, 1, 2), scaling=(1, 1, 1)): + + super().__init__(side_str, dev_pin, device_addr, transposition, scaling) + self._mag = Vector3d(transposition, scaling, self._mag_callback) + self.accel_filter_range = 0 # fast filtered response + self.gyro_filter_range = 0 + self._mag_stale_count = 0 # MPU9250 count of consecutive reads where old data was returned + self.mag_correction = self._magsetup() # 16 bit, 100Hz update.Return correction factors. + self._mag_callback() # Seems neccessary to kick the mag off else 1st reading is zero (?) + + def enable_irq_mode(self, level=0x22, freq=0x03): + self.filter_range = 1 # set accel lpf to 184Hz + self._write(0x40, 0x38, self.mpu_addr) # enable motion interrupt + self._write(0xC0, 0x69, self.mpu_addr) # enable accel hardware intelligence + self._write(level, 0x1F, self.mpu_addr) # motion threshold: 1~255 LSBs (0~1020mg) + self._write(freq, 0x1E, self.mpu_addr) # motion frequency: [3:0] = 0.24Hz ~ 500Hz + self._write(0x21, 0x6B, self.mpu_addr) # enable cycle mode (Accel Low Power Mode) + + def disable_irq_mode(self): + self._write(0x00, 0x69, self.mpu_addr) # disable accel hardware intelligence + self._write(0x01, 0x6B, self.mpu_addr) # enable sensor + + + @property + def sensors(self): + ''' + returns sensor objects accel, gyro and mag + ''' + return self._accel, self._gyro, self._mag + + # get temperature + @property + def temperature(self): + ''' + Returns the temperature in degree C. + ''' + try: + self._read(self.buf2, 0x41, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + return bytes_toint(self.buf2[0], self.buf2[1])/333.87 + 21 # I think + + # Low pass filters + @property + def gyro_filter_range(self): + ''' + Returns the gyro and temperature sensor low pass filter cutoff frequency + Pass: 0 1 2 3 4 5 6 7 + Cutoff (Hz): 250 184 92 41 20 10 5 3600 + Sample rate (KHz): 8 1 1 1 1 1 1 8 + ''' + try: + self._read(self.buf1, 0x1A, self.mpu_addr) + res = self.buf1[0] & 7 + except OSError: + raise MPUException(self._I2Cerror) + return res + + @gyro_filter_range.setter + def gyro_filter_range(self, filt): + ''' + Sets the gyro and temperature sensor low pass filter cutoff frequency + Pass: 0 1 2 3 4 5 6 7 + Cutoff (Hz): 250 184 92 41 20 10 5 3600 + Sample rate (KHz): 8 1 1 1 1 1 1 8 + ''' + if filt in range(8): + try: + self._write(filt, 0x1A, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + else: + raise ValueError('Filter coefficient must be between 0 and 7') + + @property + def accel_filter_range(self): + ''' + Returns the accel low pass filter cutoff frequency + Pass: 0 1 2 3 4 5 6 7 BEWARE 7 doesn't work on device I tried. + Cutoff (Hz): 460 184 92 41 20 10 5 460 + Sample rate (KHz): 1 1 1 1 1 1 1 1 + ''' + try: + self._read(self.buf1, 0x1D, self.mpu_addr) + res = self.buf1[0] & 7 + except OSError: + raise MPUException(self._I2Cerror) + return res + + @accel_filter_range.setter + def accel_filter_range(self, filt): + ''' + Sets the accel low pass filter cutoff frequency + Pass: 0 1 2 3 4 5 6 7 BEWARE 7 doesn't work on device I tried. + Cutoff (Hz): 460 184 92 41 20 10 5 460 + Sample rate (KHz): 1 1 1 1 1 1 1 1 + ''' + if filt in range(8): + try: + self._write(filt, 0x1D, self.mpu_addr) + except OSError: + raise MPUException(self._I2Cerror) + else: + raise ValueError('Filter coefficient must be between 0 and 7') + + def _magsetup(self): # mode 2 100Hz continuous reads, 16 bit + ''' + Magnetometer initialisation: use 16 bit continuous mode. + Mode 1 is 8Hz mode 2 is 100Hz repetition + returns correction values + ''' + try: + self._write(0x0F, 0x0A, self._mag_addr) # fuse ROM access mode + self._read(self.buf3, 0x10, self._mag_addr) # Correction values + self._write(0, 0x0A, self._mag_addr) # Power down mode (AK8963 manual 6.4.6) + self._write(0x16, 0x0A, self._mag_addr) # 16 bit (0.15uT/LSB not 0.015), mode 2 + except OSError: + raise MPUException(self._I2Cerror) + mag_x = (0.5*(self.buf3[0] - 128))/128 + 1 + mag_y = (0.5*(self.buf3[1] - 128))/128 + 1 + mag_z = (0.5*(self.buf3[2] - 128))/128 + 1 + return (mag_x, mag_y, mag_z) + + @property + def mag(self): + ''' + Magnetomerte object + ''' + return self._mag + + def _mag_callback(self): + ''' + Update magnetometer Vector3d object (if data available) + ''' + try: # If read fails, returns last valid data and + self._read(self.buf1, 0x02, self._mag_addr) # increments mag_stale_count + if self.buf1[0] & 1 == 0: + return self._mag # Data not ready: return last value + self._read(self.buf6, 0x03, self._mag_addr) + self._read(self.buf1, 0x09, self._mag_addr) + except OSError: + raise MPUException(self._I2Cerror) + if self.buf1[0] & 0x08 > 0: # An overflow has occurred + self._mag_stale_count += 1 # Error conditions retain last good value + return # user should check for ever increasing stale_counts + self._mag._ivector[1] = bytes_toint(self.buf6[1], self.buf6[0]) # Note axis twiddling and little endian + self._mag._ivector[0] = bytes_toint(self.buf6[3], self.buf6[2]) + self._mag._ivector[2] = -bytes_toint(self.buf6[5], self.buf6[4]) + scale = 0.15 # scale is 0.15uT/LSB + self._mag._vector[0] = self._mag._ivector[0]*self.mag_correction[0]*scale + self._mag._vector[1] = self._mag._ivector[1]*self.mag_correction[1]*scale + self._mag._vector[2] = self._mag._ivector[2]*self.mag_correction[2]*scale + self._mag_stale_count = 0 + + @property + def mag_stale_count(self): + ''' + Number of consecutive times where old data was returned + ''' + return self._mag_stale_count + + def get_mag_irq(self): + ''' + Uncorrected values because floating point uses heap + ''' + self._read(self.buf1, 0x02, self._mag_addr) + if self.buf1[0] == 1: # Data is ready + self._read(self.buf6, 0x03, self._mag_addr) + self._read(self.buf1, 0x09, self._mag_addr) # Mandatory status2 read + self._mag._ivector[1] = 0 + if self.buf1[0] & 0x08 == 0: # No overflow has occurred + self._mag._ivector[1] = bytes_toint(self.buf6[1], self.buf6[0]) + self._mag._ivector[0] = bytes_toint(self.buf6[3], self.buf6[2]) + self._mag._ivector[2] = -bytes_toint(self.buf6[5], self.buf6[4]) diff --git a/GW-custom/uPyLoRaWAN/uPySensors/pmsa003.py b/GW-custom/uPyLoRaWAN/uPySensors/pmsa003.py new file mode 100644 index 0000000000000000000000000000000000000000..0972a7fe620c0d5dc90592f6e53fa836b9876ac4 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/pmsa003.py @@ -0,0 +1,99 @@ + +import machine +import utime + +epoch_offset = 946684800 + +class PMSA003: + def __init__(self, uart, pins): + + self._uart = uart + self._pins = pins + + self._set = machine.Pin(self._pins["set"], machine.Pin.OUT, value=0) + self._rst = machine.Pin(self._pins["rst"], machine.Pin.OUT, value=0) + self._uart.init(tx=self._pins["tx"], rx=self._pins["rx"]) + self.power_off() + + def wake_up(self): + self._set(True) + self._rst(True) + # sleep for 7 seconds to initialize the sensor properly + self._set_normal() + utime.sleep_ms(7000) + # warning init + for idx in range(10): + data = self.measurements + utime.sleep_ms(500) + + def _set_idle(self): + idelcmd = b'\x42\x4d\xe4\x00\x00\x01\x73' + ary = bytearray(idelcmd) + self._uart.write(ary) + + def _set_normal(self): + normalcmd = b'\x42\x4d\xe4\x00\x01\x01\x74' + ary = bytearray(normalcmd) + self._uart.write(ary) + + def power_off(self): + self._set_idle() + self._set(False) + self._rst(False) + + def reset(self): + self._rst(False) + utime.sleep_ms(2000) + self._rst(True) + + @property + def measurements(self): + # flush the buffer to read fresh data + ret_data = None + self._wait_for_data(32) + + while self._uart.read(1) != b'\x42': + machine.idle() + + if self._uart.read(1) == b'\x4D': + self._wait_for_data(30) + try: + self._data = self._uart.read(30) + if self._data: + ret_data = self._PMdata() + except ValueError as e: + print('error reading frame: {}'.format(e.message)) + pass + + return ret_data + + def _wait_for_data(self, byte_count): + u = self._uart.any() + while u < byte_count: + u = self._uart.any() + # 32*8*1000/9600 (32 bytes @9600kbps) + # but let's assume byte is 10 bits to skip complex math + utime.sleep_ms(10) + + def _PMdata(self): + d = {} + check = False + # check data + control_sum = 0x42 + 0x4d + for b in range(len(self._data)-2): + control_sum += self._data[b] + + control_sum_data = self._data[28] * 256 + self._data[29] + print() + if control_sum == control_sum_data: + check = True + + d['time'] = utime.time() + epoch_offset + d['cpm10'] = self._data[4] * 256 + self._data[5] + d['cpm25'] = self._data[6] * 256 + self._data[7] + d['cpm100'] = self._data[8] * 256 + self._data[9] + d['apm10'] = self._data[10] * 256 + self._data[11] + d['apm25'] = self._data[12] * 256 + self._data[13] + d['apm100'] = self._data[14] * 256 + self._data[15] + + return [check, d] \ No newline at end of file diff --git a/GW-custom/uPyLoRaWAN/uPySensors/sdcard.py b/GW-custom/uPyLoRaWAN/uPySensors/sdcard.py new file mode 100644 index 0000000000000000000000000000000000000000..b0786e8af745e5886628dc0853cc3ea4455fe0be --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/sdcard.py @@ -0,0 +1,279 @@ +""" +MicroPython driver for SD cards using SPI bus. + +Requires an SPI bus and a CS pin. Provides readblocks and writeblocks +methods so the device can be mounted as a filesystem. + +Example usage on pyboard: + + import pyb, sdcard, os + sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5) + pyb.mount(sd, '/sd2') + os.listdir('/') + +Example usage on ESP8266: + + import machine, sdcard, os + sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15)) + os.mount(sd, '/sd') + os.listdir('/') + +""" + +from micropython import const +from machine import Pin +import time + + +_CMD_TIMEOUT = const(100) + +_R1_IDLE_STATE = const(1 << 0) +#R1_ERASE_RESET = const(1 << 1) +_R1_ILLEGAL_COMMAND = const(1 << 2) +#R1_COM_CRC_ERROR = const(1 << 3) +#R1_ERASE_SEQUENCE_ERROR = const(1 << 4) +#R1_ADDRESS_ERROR = const(1 << 5) +#R1_PARAMETER_ERROR = const(1 << 6) +_TOKEN_CMD25 = const(0xfc) +_TOKEN_STOP_TRAN = const(0xfd) +_TOKEN_DATA = const(0xfe) + + +class SDCard: + def __init__(self, spi, cs): + self.spi = spi + self.cs = cs + + self.cmdbuf = bytearray(6) + self.dummybuf = bytearray(512) + self.tokenbuf = bytearray(1) + for i in range(512): + self.dummybuf[i] = 0xff + self.dummybuf_memoryview = memoryview(self.dummybuf) + + # initialise the card + self.init_card() + + def init_spi(self, baudrate): + try: + master = self.spi.MASTER + except AttributeError: + # on ESP8266 + self.spi.init(baudrate=baudrate, phase=0, polarity=0) + else: + # on pyboard + self.spi.init(master, baudrate=baudrate, phase=0, polarity=0) + + def init_card(self): + # init CS pin + self.cs.init(self.cs.OUT, value=1) + + # init SPI bus; use low data rate for initialisation + self.init_spi(10000000) + + # clock card at least 100 cycles with cs high + for i in range(16): + self.spi.write(b'\xff') + + # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts) + for _ in range(5): + if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE: + break + else: + raise OSError("no SD card") + + # CMD8: determine card version + r = self.cmd(8, 0x01aa, 0x87, 4) + if r == _R1_IDLE_STATE: + self.init_card_v2() + elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND): + self.init_card_v1() + else: + raise OSError("couldn't determine SD card version") + + # get the number of sectors + # CMD9: response R2 (R1 byte + 16-byte block read) + if self.cmd(9, 0, 0, 0, False) != 0: + raise OSError("no response from SD card") + csd = bytearray(16) + self.readinto(csd) + if csd[0] & 0xc0 == 0x40: # CSD version 2.0 + self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024 + elif csd[0] & 0xc0 == 0x00: # CSD version 1.0 (old, <=2GB) + c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4 + c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 7 + self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2)) + else: + raise OSError("SD card CSD format not supported") + #print('sectors', self.sectors) + + # CMD16: set block length to 512 bytes + if self.cmd(16, 512, 0) != 0: + raise OSError("can't set 512 block size") + + # set to high data rate now that it's initialised + self.init_spi(1320000) + + def init_card_v1(self): + for i in range(_CMD_TIMEOUT): + self.cmd(55, 0, 0) + if self.cmd(41, 0, 0) == 0: + self.cdv = 512 + #print("[SDCard] v1 card") + return + raise OSError("timeout waiting for v1 card") + + def init_card_v2(self): + for i in range(_CMD_TIMEOUT): + time.sleep_ms(50) + self.cmd(58, 0, 0, 4) + self.cmd(55, 0, 0) + if self.cmd(41, 0x40000000, 0) == 0: + self.cmd(58, 0, 0, 4) + self.cdv = 1 + #print("[SDCard] v2 card") + return + raise OSError("timeout waiting for v2 card") + + def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False): + self.cs(0) + + # create and send the command + buf = self.cmdbuf + buf[0] = 0x40 | cmd + buf[1] = arg >> 24 + buf[2] = arg >> 16 + buf[3] = arg >> 8 + buf[4] = arg + buf[5] = crc + self.spi.write(buf) + + if skip1: + self.spi.readinto(self.tokenbuf, 0xff) + + # wait for the response (response[7] == 0) + for i in range(_CMD_TIMEOUT): + self.spi.readinto(self.tokenbuf, 0xff) + response = self.tokenbuf[0] + if not (response & 0x80): + # this could be a big-endian integer that we are getting here + for j in range(final): + self.spi.write(b'\xff') + if release: + self.cs(1) + self.spi.write(b'\xff') + return response + + # timeout + self.cs(1) + self.spi.write(b'\xff') + return -1 + + def readinto(self, buf): + self.cs(0) + + # read until start byte (0xff) + while True: + self.spi.readinto(self.tokenbuf, 0xff) + if self.tokenbuf[0] == _TOKEN_DATA: + break + + # read data + mv = self.dummybuf_memoryview + if len(buf) != len(mv): + mv = mv[:len(buf)] + self.spi.write_readinto(mv, buf) + + # read checksum + self.spi.write(b'\xff') + self.spi.write(b'\xff') + + self.cs(1) + self.spi.write(b'\xff') + + def write(self, token, buf): + self.cs(0) + + # send: start of block, data, checksum + self.spi.read(1, token) + self.spi.write(buf) + self.spi.write(b'\xff') + self.spi.write(b'\xff') + + # check the response + if (self.spi.read(1, 0xff)[0] & 0x1f) != 0x05: + self.cs(1) + self.spi.write(b'\xff') + return + + # wait for write to finish + while self.spi.read(1, 0xff)[0] == 0: + pass + + self.cs(1) + self.spi.write(b'\xff') + + def write_token(self, token): + self.cs(0) + self.spi.read(1, token) + self.spi.write(b'\xff') + # wait for write to finish + while self.spi.read(1, 0xff)[0] == 0x00: + pass + + self.cs(1) + self.spi.write(b'\xff') + + def readblocks(self, block_num, buf): + nblocks = len(buf) // 512 + assert nblocks and not len(buf) % 512, 'Buffer length is invalid' + if nblocks == 1: + # CMD17: set read address for single block + if self.cmd(17, block_num * self.cdv, 0, release=False) != 0: + # release the card + self.cs(1) + raise OSError(5) # EIO + # receive the data and release card + self.readinto(buf) + else: + # CMD18: set read address for multiple blocks + if self.cmd(18, block_num * self.cdv, 0, release=False) != 0: + # release the card + self.cs(1) + raise OSError(5) # EIO + offset = 0 + mv = memoryview(buf) + while nblocks: + # receive the data and release card + self.readinto(mv[offset : offset + 512]) + offset += 512 + nblocks -= 1 + if self.cmd(12, 0, 0xff, skip1=True): + raise OSError(5) # EIO + + def writeblocks(self, block_num, buf): + nblocks, err = divmod(len(buf), 512) + assert nblocks and not err, 'Buffer length is invalid' + if nblocks == 1: + # CMD24: set write address for single block + if self.cmd(24, block_num * self.cdv, 0) != 0: + raise OSError(5) # EIO + + # send the data + self.write(_TOKEN_DATA, buf) + else: + # CMD25: set write address for first block + if self.cmd(25, block_num * self.cdv, 0) != 0: + raise OSError(5) # EIO + # send the data + offset = 0 + mv = memoryview(buf) + while nblocks: + self.write(_TOKEN_CMD25, mv[offset : offset + 512]) + offset += 512 + nblocks -= 1 + self.write_token(_TOKEN_STOP_TRAN) + + def ioctl(self, op, arg): + if op == 4: # get number of blocks + return self.sectors diff --git a/GW-custom/uPyLoRaWAN/uPySensors/ssd1306.py b/GW-custom/uPyLoRaWAN/uPySensors/ssd1306.py new file mode 100644 index 0000000000000000000000000000000000000000..a666a2d144841cf6a15a0edf942afb9835924849 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/ssd1306.py @@ -0,0 +1,162 @@ +# MicroPython SSD1306 OLED driver, I2C and SPI interfaces +from micropython import const +import time +import framebuf + + +# register definitions +SET_CONTRAST = const(0x81) +SET_ENTIRE_ON = const(0xa4) +SET_NORM_INV = const(0xa6) +SET_DISP = const(0xae) +SET_MEM_ADDR = const(0x20) +SET_COL_ADDR = const(0x21) +SET_PAGE_ADDR = const(0x22) +SET_DISP_START_LINE = const(0x40) +SET_SEG_REMAP = const(0xa0) +SET_MUX_RATIO = const(0xa8) +SET_COM_OUT_DIR = const(0xc0) +SET_DISP_OFFSET = const(0xd3) +SET_COM_PIN_CFG = const(0xda) +SET_DISP_CLK_DIV = const(0xd5) +SET_PRECHARGE = const(0xd9) +SET_VCOM_DESEL = const(0xdb) +SET_CHARGE_PUMP = const(0x8d) + + +class SSD1306: + def __init__(self, width, height, external_vcc): + self.width = width + self.height = height + self.external_vcc = external_vcc + self.pages = self.height // 8 + self.buffer = bytearray(self.pages * self.width) + fb = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MONO_VLSB) + self.framebuf = fb + # Provide methods for accessing FrameBuffer graphics primitives. This is a + # workround because inheritance from a native class is currently unsupported. + # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html + self.fill = fb.fill + self.pixel = fb.pixel + self.hline = fb.hline + self.vline = fb.vline + self.line = fb.line + self.rect = fb.rect + self.fill_rect = fb.fill_rect + self.text = fb.text + self.scroll = fb.scroll + self.blit = fb.blit + self.poweron() + self.init_display() + + def init_display(self): + for cmd in ( + SET_DISP | 0x00, # off + # address setting + SET_MEM_ADDR, 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE | 0x00, + SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 + SET_MUX_RATIO, self.height - 1, + SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 + SET_DISP_OFFSET, 0x00, + SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12, + # timing and driving scheme + SET_DISP_CLK_DIV, 0x80, + SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1, + SET_VCOM_DESEL, 0x30, # 0.83*Vcc + # display + SET_CONTRAST, 0xff, # maximum + SET_ENTIRE_ON, # output follows RAM contents + SET_NORM_INV, # not inverted + # charge pump + SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, + SET_DISP | 0x01): # on + self.write_cmd(cmd) + self.fill(0) + self.show() + + def poweroff(self): + self.write_cmd(SET_DISP | 0x00) + + def contrast(self, contrast): + self.write_cmd(SET_CONTRAST) + self.write_cmd(contrast) + + def invert(self, invert): + self.write_cmd(SET_NORM_INV | (invert & 1)) + + def show(self): + x0 = 0 + x1 = self.width - 1 + if self.width == 64: + # displays with width of 64 pixels are shifted by 32 + x0 += 32 + x1 += 32 + self.write_cmd(SET_COL_ADDR) + self.write_cmd(x0) + self.write_cmd(x1) + self.write_cmd(SET_PAGE_ADDR) + self.write_cmd(0) + self.write_cmd(self.pages - 1) + self.write_data(self.buffer) + + +class SSD1306_I2C(SSD1306): + def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False): + self.i2c = i2c + self.addr = addr + self.temp = bytearray(2) + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.temp[0] = 0x80 # Co=1, D/C#=0 + self.temp[1] = cmd + self.i2c.writeto(self.addr, self.temp) + + def write_data(self, buf): + self.temp[0] = self.addr << 1 + self.temp[1] = 0x40 # Co=0, D/C#=1 + self.i2c.start() + self.i2c.write(self.temp) + self.i2c.write(buf) + self.i2c.stop() + + def poweron(self): + pass + + +class SSD1306_SPI(SSD1306): + def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): + self.rate = 10 * 1024 * 1024 + dc.init(dc.OUT, value=0) + res.init(res.OUT, value=0) + cs.init(cs.OUT, value=1) + self.spi = spi + self.dc = dc + self.res = res + self.cs = cs + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(0) + self.cs(0) + self.spi.write(bytearray([cmd])) + self.cs(1) + + def write_data(self, buf): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(1) + self.cs(0) + self.spi.write(buf) + self.cs(1) + + def poweron(self): + self.res(1) + time.sleep_ms(1) + self.res(0) + time.sleep_ms(10) + self.res(1) diff --git a/GW-custom/uPyLoRaWAN/uPySensors/ssd1306_i2c.py b/GW-custom/uPyLoRaWAN/uPySensors/ssd1306_i2c.py new file mode 100644 index 0000000000000000000000000000000000000000..26bae2c7da93fbf52bb68bf2455defcbe7f92310 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/ssd1306_i2c.py @@ -0,0 +1,82 @@ +# https://learn.adafruit.com/micropython-hardware-ssd1306-oled-display/software +import time +from machine import I2C, Pin +from .ssd1306 import SSD1306_I2C + +class Display: + + def __init__(self, + width = 128, height = 64, + scl_pin_id = 15, sda_pin_id = 4, + freq = 400000): + + self.width = width + self.height = height + self.poweron() + self.i2c = I2C(scl = Pin(scl_pin_id, Pin.OUT), + sda = Pin(sda_pin_id), + freq = freq) + self.display = SSD1306_I2C(width, height, self.i2c) + self.show = self.display.show + + def poweron(self, pin=16): + pin_reset = Pin(pin, mode=Pin.OUT) + pin_reset.value(0) + time.sleep_ms(50) + pin_reset.value(1) + + def poweroff(self, pin=16): + pin_reset = Pin(pin, mode=Pin.OUT) + pin_reset.value(0) + + def clear(self): + self.display.fill(0) + self.display.show() + + + def show_text(self, text, x = 0, y = 0, clear_first = True, show_now = True, hold_seconds = 0): + if clear_first: self.display.fill(0) + self.display.text(text, x, y) + if show_now: + self.display.show() + if hold_seconds > 0: time.sleep(hold_seconds) + + + def wrap(self, text, start_line = 0, + height_per_line = 8, width_per_char = 8, + start_pixel_each_line = 0): + + chars_per_line = self.width//width_per_char + max_lines = self.height//height_per_line - start_line + lines = [(text[chars_per_line*line: chars_per_line*(line+1)], start_pixel_each_line, height_per_line*(line+start_line)) + for line in range(max_lines)] + + return lines + + + def show_text_wrap(self, text, + start_line = 0, height_per_line = 8, width_per_char = 8, start_pixel_each_line = 0, + clear_first = True, show_now = True, hold_seconds = 0): + + if clear_first: self.clear() + + for line, x, y in self.wrap(text, start_line, height_per_line, width_per_char, start_pixel_each_line): + self.show_text(line, x, y, clear_first = False, show_now = False) + + if show_now: + self.display.show() + if hold_seconds > 0: time.sleep(hold_seconds) + + + def show_datetime(self, year, month, day, hour, minute, second): + datetime = [year, month, day, hour, minute, second] + datetime_str = ["{0:0>2}".format(d) for d in datetime] + + self.show_text(text = '-'.join(datetime_str[:3]), + x = 0, y = 0, clear_first = True, show_now = False) + self.show_text(text = ':'.join(datetime_str[3:6]), + x = 0, y = 10, clear_first = False, show_now = True) + + + def show_time(self, year, month, day, hour, minute, second): + self.show_datetime(year, month, day, hour, minute, second) diff --git a/GW-custom/uPyLoRaWAN/uPySensors/stepper.py b/GW-custom/uPyLoRaWAN/uPySensors/stepper.py new file mode 100644 index 0000000000000000000000000000000000000000..463272c0a86f08a9134b50d8437c8b91e30378e3 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/stepper.py @@ -0,0 +1,94 @@ +""" +Copyright 2020 LeMaRiva|Tech (Mauro Riva) info@lemariva.com +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import utime +from machine import Pin + +class STEPPER: + + def __init__(self, device_config): + self._In1 = Pin(device_config["In1"], Pin.OUT) + self._In2 = Pin(device_config["In2"], Pin.OUT) + self._In3 = Pin(device_config["In3"], Pin.OUT) + self._In4 = Pin(device_config["In4"], Pin.OUT) + self._number_of_steps = device_config["number_of_steps"] + 1 + self._max_speed = 60 * 1000 * 1000 / self._number_of_steps / device_config["max_speed"] + self._step_number = 0 + self._last_step_time = 0 + self.set_speed(device_config["max_speed"]/2) + + def set_speed(self, speed): + self._step_delay = 60 * 1000 * 1000 / self._number_of_steps / speed + if self._step_delay < self._max_speed: + self._step_delay = self._max_speed + + def step_motor(self, step): + if step == 0: #1010 + self._In1.value(1) + self._In2.value(0) + self._In3.value(1) + self._In4.value(0) + elif step == 1: #0110 + self._In1.value(0) + self._In2.value(1) + self._In3.value(1) + self._In4.value(0) + elif step == 2: #0101 + self._In1.value(0) + self._In2.value(1) + self._In3.value(0) + self._In4.value(1) + elif step == 3: #1001 + self._In1.value(1) + self._In2.value(0) + self._In3.value(0) + self._In4.value(1) + + def release(self): + self._In1.value(0) + self._In2.value(0) + self._In3.value(0) + self._In4.value(0) + + def step(self, steps_to_move, speed=None, hold=True): + if speed is not None: + self.set_speed(speed) + + steps_left = abs(steps_to_move) + + if steps_to_move > 0: + direction = 1 + else: + direction = 0 + + while steps_left > 0: + now = utime.ticks_us() + if now - self._last_step_time >= self._step_delay: + self._last_step_time = now + if direction == 1: + self._step_number += 1 + + if direction == 0: + if self._step_number == 0: + self._step_number == steps_left + + self._step_number -= 1 + + self.step_motor(self._step_number % 4) + steps_left -= 1 + + if self._step_number == steps_left: + self._step_number = 0 + + if steps_left == 0 and not hold: + self.release() diff --git a/GW-custom/uPyLoRaWAN/uPySensors/sysfont.py b/GW-custom/uPyLoRaWAN/uPySensors/sysfont.py new file mode 100644 index 0000000000000000000000000000000000000000..b1fa2d69b10862ea87e30c208e54d394aff7474a --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/sysfont.py @@ -0,0 +1,265 @@ + +#Font used for ST7735 display. + +#Each character uses 5 bytes. +#index using ASCII value * 5. +#Each byte contains a column of pixels. +#The character may be 8 pixels high and 5 wide. + +sysfont = {"Width": 5, "Height": 8, "Start": 0, "End": 254, "Data": bytearray([ + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, + 0x18, 0x3C, 0x7E, 0x3C, 0x18, + 0x1C, 0x57, 0x7D, 0x57, 0x1C, + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, + 0x00, 0x18, 0x3C, 0x18, 0x00, + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, + 0x00, 0x18, 0x24, 0x18, 0x00, + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, + 0x30, 0x48, 0x3A, 0x06, 0x0E, + 0x26, 0x29, 0x79, 0x29, 0x26, + 0x40, 0x7F, 0x05, 0x05, 0x07, + 0x40, 0x7F, 0x05, 0x25, 0x3F, + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, + 0x08, 0x1C, 0x1C, 0x3E, 0x7F, + 0x14, 0x22, 0x7F, 0x22, 0x14, + 0x5F, 0x5F, 0x00, 0x5F, 0x5F, + 0x06, 0x09, 0x7F, 0x01, 0x7F, + 0x00, 0x66, 0x89, 0x95, 0x6A, + 0x60, 0x60, 0x60, 0x60, 0x60, + 0x94, 0xA2, 0xFF, 0xA2, 0x94, + 0x08, 0x04, 0x7E, 0x04, 0x08, + 0x10, 0x20, 0x7E, 0x20, 0x10, + 0x08, 0x08, 0x2A, 0x1C, 0x08, + 0x08, 0x1C, 0x2A, 0x08, 0x08, + 0x1E, 0x10, 0x10, 0x10, 0x10, + 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, + 0x30, 0x38, 0x3E, 0x38, 0x30, + 0x06, 0x0E, 0x3E, 0x0E, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5F, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x07, 0x00, + 0x14, 0x7F, 0x14, 0x7F, 0x14, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, + 0x23, 0x13, 0x08, 0x64, 0x62, + 0x36, 0x49, 0x56, 0x20, 0x50, + 0x00, 0x08, 0x07, 0x03, 0x00, + 0x00, 0x1C, 0x22, 0x41, 0x00, + 0x00, 0x41, 0x22, 0x1C, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, + 0x08, 0x08, 0x3E, 0x08, 0x08, + 0x00, 0x80, 0x70, 0x30, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x60, 0x60, 0x00, + 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3E, 0x51, 0x49, 0x45, 0x3E, + 0x00, 0x42, 0x7F, 0x40, 0x00, + 0x72, 0x49, 0x49, 0x49, 0x46, + 0x21, 0x41, 0x49, 0x4D, 0x33, + 0x18, 0x14, 0x12, 0x7F, 0x10, + 0x27, 0x45, 0x45, 0x45, 0x39, + 0x3C, 0x4A, 0x49, 0x49, 0x31, + 0x41, 0x21, 0x11, 0x09, 0x07, + 0x36, 0x49, 0x49, 0x49, 0x36, + 0x46, 0x49, 0x49, 0x29, 0x1E, + 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x40, 0x34, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x00, 0x41, 0x22, 0x14, 0x08, + 0x02, 0x01, 0x59, 0x09, 0x06, + 0x3E, 0x41, 0x5D, 0x59, 0x4E, + 0x7C, 0x12, 0x11, 0x12, 0x7C, + 0x7F, 0x49, 0x49, 0x49, 0x36, + 0x3E, 0x41, 0x41, 0x41, 0x22, + 0x7F, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x49, 0x49, 0x49, 0x41, + 0x7F, 0x09, 0x09, 0x09, 0x01, + 0x3E, 0x41, 0x41, 0x51, 0x73, + 0x7F, 0x08, 0x08, 0x08, 0x7F, + 0x00, 0x41, 0x7F, 0x41, 0x00, + 0x20, 0x40, 0x41, 0x3F, 0x01, + 0x7F, 0x08, 0x14, 0x22, 0x41, + 0x7F, 0x40, 0x40, 0x40, 0x40, + 0x7F, 0x02, 0x1C, 0x02, 0x7F, + 0x7F, 0x04, 0x08, 0x10, 0x7F, + 0x3E, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x09, 0x09, 0x09, 0x06, + 0x3E, 0x41, 0x51, 0x21, 0x5E, + 0x7F, 0x09, 0x19, 0x29, 0x46, + 0x26, 0x49, 0x49, 0x49, 0x32, + 0x03, 0x01, 0x7F, 0x01, 0x03, + 0x3F, 0x40, 0x40, 0x40, 0x3F, + 0x1F, 0x20, 0x40, 0x20, 0x1F, + 0x3F, 0x40, 0x38, 0x40, 0x3F, + 0x63, 0x14, 0x08, 0x14, 0x63, + 0x03, 0x04, 0x78, 0x04, 0x03, + 0x61, 0x59, 0x49, 0x4D, 0x43, + 0x00, 0x7F, 0x41, 0x41, 0x41, + 0x02, 0x04, 0x08, 0x10, 0x20, + 0x00, 0x41, 0x41, 0x41, 0x7F, + 0x04, 0x02, 0x01, 0x02, 0x04, + 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x03, 0x07, 0x08, 0x00, + 0x20, 0x54, 0x54, 0x78, 0x40, + 0x7F, 0x28, 0x44, 0x44, 0x38, + 0x38, 0x44, 0x44, 0x44, 0x28, + 0x38, 0x44, 0x44, 0x28, 0x7F, + 0x38, 0x54, 0x54, 0x54, 0x18, + 0x00, 0x08, 0x7E, 0x09, 0x02, + 0x18, 0xA4, 0xA4, 0x9C, 0x78, + 0x7F, 0x08, 0x04, 0x04, 0x78, + 0x00, 0x44, 0x7D, 0x40, 0x00, + 0x20, 0x40, 0x40, 0x3D, 0x00, + 0x7F, 0x10, 0x28, 0x44, 0x00, + 0x00, 0x41, 0x7F, 0x40, 0x00, + 0x7C, 0x04, 0x78, 0x04, 0x78, + 0x7C, 0x08, 0x04, 0x04, 0x78, + 0x38, 0x44, 0x44, 0x44, 0x38, + 0xFC, 0x18, 0x24, 0x24, 0x18, + 0x18, 0x24, 0x24, 0x18, 0xFC, + 0x7C, 0x08, 0x04, 0x04, 0x08, + 0x48, 0x54, 0x54, 0x54, 0x24, + 0x04, 0x04, 0x3F, 0x44, 0x24, + 0x3C, 0x40, 0x40, 0x20, 0x7C, + 0x1C, 0x20, 0x40, 0x20, 0x1C, + 0x3C, 0x40, 0x30, 0x40, 0x3C, + 0x44, 0x28, 0x10, 0x28, 0x44, + 0x4C, 0x90, 0x90, 0x90, 0x7C, + 0x44, 0x64, 0x54, 0x4C, 0x44, + 0x00, 0x08, 0x36, 0x41, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, + 0x00, 0x41, 0x36, 0x08, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x02, + 0x3C, 0x26, 0x23, 0x26, 0x3C, + 0x1E, 0xA1, 0xA1, 0x61, 0x12, + 0x3A, 0x40, 0x40, 0x20, 0x7A, + 0x38, 0x54, 0x54, 0x55, 0x59, + 0x21, 0x55, 0x55, 0x79, 0x41, + 0x21, 0x54, 0x54, 0x78, 0x41, + 0x21, 0x55, 0x54, 0x78, 0x40, + 0x20, 0x54, 0x55, 0x79, 0x40, + 0x0C, 0x1E, 0x52, 0x72, 0x12, + 0x39, 0x55, 0x55, 0x55, 0x59, + 0x39, 0x54, 0x54, 0x54, 0x59, + 0x39, 0x55, 0x54, 0x54, 0x58, + 0x00, 0x00, 0x45, 0x7C, 0x41, + 0x00, 0x02, 0x45, 0x7D, 0x42, + 0x00, 0x01, 0x45, 0x7C, 0x40, + 0xF0, 0x29, 0x24, 0x29, 0xF0, + 0xF0, 0x28, 0x25, 0x28, 0xF0, + 0x7C, 0x54, 0x55, 0x45, 0x00, + 0x20, 0x54, 0x54, 0x7C, 0x54, + 0x7C, 0x0A, 0x09, 0x7F, 0x49, + 0x32, 0x49, 0x49, 0x49, 0x32, + 0x32, 0x48, 0x48, 0x48, 0x32, + 0x32, 0x4A, 0x48, 0x48, 0x30, + 0x3A, 0x41, 0x41, 0x21, 0x7A, + 0x3A, 0x42, 0x40, 0x20, 0x78, + 0x00, 0x9D, 0xA0, 0xA0, 0x7D, + 0x39, 0x44, 0x44, 0x44, 0x39, + 0x3D, 0x40, 0x40, 0x40, 0x3D, + 0x3C, 0x24, 0xFF, 0x24, 0x24, + 0x48, 0x7E, 0x49, 0x43, 0x66, + 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, + 0xFF, 0x09, 0x29, 0xF6, 0x20, + 0xC0, 0x88, 0x7E, 0x09, 0x03, + 0x20, 0x54, 0x54, 0x79, 0x41, + 0x00, 0x00, 0x44, 0x7D, 0x41, + 0x30, 0x48, 0x48, 0x4A, 0x32, + 0x38, 0x40, 0x40, 0x22, 0x7A, + 0x00, 0x7A, 0x0A, 0x0A, 0x72, + 0x7D, 0x0D, 0x19, 0x31, 0x7D, + 0x26, 0x29, 0x29, 0x2F, 0x28, + 0x26, 0x29, 0x29, 0x29, 0x26, + 0x30, 0x48, 0x4D, 0x40, 0x20, + 0x38, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x38, + 0x2F, 0x10, 0xC8, 0xAC, 0xBA, + 0x2F, 0x10, 0x28, 0x34, 0xFA, + 0x00, 0x00, 0x7B, 0x00, 0x00, + 0x08, 0x14, 0x2A, 0x14, 0x22, + 0x22, 0x14, 0x2A, 0x14, 0x08, + 0xAA, 0x00, 0x55, 0x00, 0xAA, + 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x10, 0x10, 0x10, 0xFF, 0x00, + 0x14, 0x14, 0x14, 0xFF, 0x00, + 0x10, 0x10, 0xFF, 0x00, 0xFF, + 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x14, 0x14, 0x14, 0xFC, 0x00, + 0x14, 0x14, 0xF7, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x14, 0x14, 0xF4, 0x04, 0xFC, + 0x14, 0x14, 0x17, 0x10, 0x1F, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0x1F, 0x00, + 0x10, 0x10, 0x10, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0xF0, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0xFF, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x14, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x1F, 0x10, 0x17, + 0x00, 0x00, 0xFC, 0x04, 0xF4, + 0x14, 0x14, 0x17, 0x10, 0x17, + 0x14, 0x14, 0xF4, 0x04, 0xF4, + 0x00, 0x00, 0xFF, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0xF7, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x17, 0x14, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0xF4, 0x14, + 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x00, 0x00, 0x1F, 0x10, 0x1F, + 0x00, 0x00, 0x00, 0x1F, 0x14, + 0x00, 0x00, 0x00, 0xFC, 0x14, + 0x00, 0x00, 0xF0, 0x10, 0xF0, + 0x10, 0x10, 0xFF, 0x10, 0xFF, + 0x14, 0x14, 0x14, 0xFF, 0x14, + 0x10, 0x10, 0x10, 0x1F, 0x00, + 0x00, 0x00, 0x00, 0xF0, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x38, 0x44, 0x44, 0x38, 0x44, + 0x7C, 0x2A, 0x2A, 0x3E, 0x14, + 0x7E, 0x02, 0x02, 0x06, 0x06, + 0x02, 0x7E, 0x02, 0x7E, 0x02, + 0x63, 0x55, 0x49, 0x41, 0x63, + 0x38, 0x44, 0x44, 0x3C, 0x04, + 0x40, 0x7E, 0x20, 0x1E, 0x20, + 0x06, 0x02, 0x7E, 0x02, 0x02, + 0x99, 0xA5, 0xE7, 0xA5, 0x99, + 0x1C, 0x2A, 0x49, 0x2A, 0x1C, + 0x4C, 0x72, 0x01, 0x72, 0x4C, + 0x30, 0x4A, 0x4D, 0x4D, 0x30, + 0x30, 0x48, 0x78, 0x48, 0x30, + 0xBC, 0x62, 0x5A, 0x46, 0x3D, + 0x3E, 0x49, 0x49, 0x49, 0x00, + 0x7E, 0x01, 0x01, 0x01, 0x7E, + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, + 0x44, 0x44, 0x5F, 0x44, 0x44, + 0x40, 0x51, 0x4A, 0x44, 0x40, + 0x40, 0x44, 0x4A, 0x51, 0x40, + 0x00, 0x00, 0xFF, 0x01, 0x03, + 0xE0, 0x80, 0xFF, 0x00, 0x00, + 0x08, 0x08, 0x6B, 0x6B, 0x08, + 0x36, 0x12, 0x36, 0x24, 0x36, + 0x06, 0x0F, 0x09, 0x0F, 0x06, + 0x00, 0x00, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x10, 0x10, 0x00, + 0x30, 0x40, 0xFF, 0x01, 0x01, + 0x00, 0x1F, 0x01, 0x01, 0x1E, + 0x00, 0x19, 0x1D, 0x17, 0x12, + 0x00, 0x3C, 0x3C, 0x3C, 0x3C +])} + diff --git a/GW-custom/uPyLoRaWAN/uPySensors/ublox_gps.py b/GW-custom/uPyLoRaWAN/uPySensors/ublox_gps.py new file mode 100644 index 0000000000000000000000000000000000000000..61108799cffa374f2feb99a214a4091c03c95be9 --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/ublox_gps.py @@ -0,0 +1,808 @@ +# -*- coding: utf-8 -*- +# MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X + +# +# The MIT License (MIT) + +# Copyright (c) 2014 Michael Calvin McCoy (calvin.mccoy@gmail.com) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +# TODO: +# Time Since First Fix +# Distance/Time to Target +# Logging +# More Helper Functions +# Dynamically limit sentences types to parse + +# +# Modified by [Mauro Riva <lemariva@mail.com> <lemariva.com>] for Wipy 2.0 +# * reduce size because of heap size of the Wipy 2.0 +# * added updateall, latitude_decimal, longitude_decimal methods +# * added support for GLONASS, and GLONASS + GPS + +import time +import gc + +class MicropyGPS(object): + """GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics. + Parses sentences one character at a time using update(). """ + + # Max Number of Characters a valid sentence can be (based on GGA sentence) + SENTENCE_LIMIT = 76 + __HEMISPHERES = ('N', 'S', 'E', 'W') + __NO_FIX = 1 + __FIX_2D = 2 + __FIX_3D = 3 + __DIRECTIONS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] + __MONTHS = ('January', 'February', 'March', 'April', 'May', + 'June', 'July', 'August', 'September', 'October', + 'November', 'December') + + def __init__(self, local_offset=0): + """Setup GPS Object Status Flags, Internal Data Registers, etc""" + + ##################### + # Object Status Flags + self.sentence_active = False + self.active_segment = 0 + self.process_crc = False + self.gps_segments = [] + self.crc_xor = 0 + self.char_count = 0 + self.fix_time = 0 + + ##################### + # Sentence Statistics + self.crc_fails = 0 + self.clean_sentences = 0 + self.parsed_sentences = 0 + + ##################### + # Logging Related + self.log_handle = None + self.log_en = False + + ##################### + # Data From Sentences + # Time + self.timestamp = (0, 0, 0) + self.date = (0, 0, 0) + self.local_offset = local_offset + + # Position/Motion + self.latitude = (0, 0.0, 'N') + self.longitude = (0, 0.0, 'W') + self.speed = (0.0, 0.0, 0.0) + self.course = 0.0 + self.altitude = 0.0 + self.geoid_height = 0.0 + + # GPS Info + self.satellites_in_view = 0 + self.satellites_in_use = 0 + self.satellites_used = [] + self.last_sv_sentence = 0 + self.total_sv_sentences = 0 + self.satellite_data = dict() + self.hdop = 0.0 + self.pdop = 0.0 + self.vdop = 0.0 + self.valid = False + self.fix_stat = 0 + self.fix_type = 1 + + # UART readall + self.oldstring = bytes() + + ######################################## + # Logging Related Functions + ######################################## + def start_logging(self, target_file, mode="append"): + """ + Create GPS data log object + """ + if mode == 'new': + mode_code = 'w' + else: + mode_code = 'a' + try: + self.log_handle = open(target_file, mode_code) + except AttributeError: + print("Invalid FileName") + return False + + self.log_en = True + return True + + def stop_logging(self): + """ + Closes the log file handler and disables further logging + """ + try: + self.log_handle.close() + except AttributeError: + print("Invalid Handle") + return False + + self.log_en = False + return True + + def write_log(self, log_string): + """Attempts to write the last valid NMEA sentence character to the active file handler + """ + try: + self.log_handle.write(log_string) + except TypeError: + return False + return True + + ######################################## + # Sentence Parsers + ######################################## + def gprmc(self): + """Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence. Updates UTC timestamp, latitude, + longitude, Course, Speed, Date, and fix status""" + + # UTC Timestamp + try: + utc_string = self.gps_segments[1] + + if utc_string: # Possible timestamp found + hours = int(utc_string[0:2]) + self.local_offset + minutes = int(utc_string[2:4]) + seconds = float(utc_string[4:]) + self.timestamp = (hours, minutes, seconds) + else: # No Time stamp yet + self.timestamp = (0, 0, 0) + + except ValueError: # Bad Timestamp value present + return False + + # Date stamp + try: + date_string = self.gps_segments[9] + + # Date string printer function assumes to be year >=2000, + # date_string() must be supplied with the correct century argument to display correctly + if date_string: # Possible date stamp found + day = int(date_string[0:2]) + month = int(date_string[2:4]) + year = int(date_string[4:6]) + self.date = (day, month, year) + else: # No Date stamp yet + self.date = (0, 0, 0) + + except ValueError: # Bad Date stamp value present + return False + + # Check Receiver Data Valid Flag + if self.gps_segments[2] == 'A': # Data from Receiver is Valid/Has Fix + + # Longitude / Latitude + try: + # Latitude + l_string = self.gps_segments[3] + lat_degs = int(l_string[0:2]) + lat_mins = float(l_string[2:]) + lat_hemi = self.gps_segments[4] + + # Longitude + l_string = self.gps_segments[5] + lon_degs = int(l_string[0:3]) + lon_mins = float(l_string[3:]) + lon_hemi = self.gps_segments[6] + except ValueError: + return False + + if lat_hemi not in self.__HEMISPHERES: + return False + + if lon_hemi not in self.__HEMISPHERES: + return False + + # Speed + try: + spd_knt = float(self.gps_segments[7]) + except ValueError: + return False + + # Course + try: + course = float(self.gps_segments[8]) + except ValueError: + return False + + # TODO - Add Magnetic Variation + + # Update Object Data + self.latitude = (lat_degs, lat_mins, lat_hemi) + self.longitude = (lon_degs, lon_mins, lon_hemi) + # Include mph and hm/h + self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852) + self.course = course + self.valid = True + + # Update Last Fix Time + self.new_fix_time() + + else: # Clear Position Data if Sentence is 'Invalid' + self.latitude = (0, 0.0, 'N') + self.longitude = (0, 0.0, 'W') + self.speed = (0.0, 0.0, 0.0) + self.course = 0.0 + self.date = (0, 0, 0) + self.valid = False + + return True + + def gpgll(self): + """Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude, + longitude, and fix status""" + + # UTC Timestamp + try: + utc_string = self.gps_segments[5] + + if utc_string: # Possible timestamp found + hours = int(utc_string[0:2]) + self.local_offset + minutes = int(utc_string[2:4]) + seconds = float(utc_string[4:]) + self.timestamp = (hours, minutes, seconds) + else: # No Time stamp yet + self.timestamp = (0, 0, 0) + + except ValueError: # Bad Timestamp value present + return False + + # Check Receiver Data Valid Flag + if self.gps_segments[6] == 'A': # Data from Receiver is Valid/Has Fix + + # Longitude / Latitude + try: + # Latitude + l_string = self.gps_segments[1] + lat_degs = int(l_string[0:2]) + lat_mins = float(l_string[2:]) + lat_hemi = self.gps_segments[2] + + # Longitude + l_string = self.gps_segments[3] + lon_degs = int(l_string[0:3]) + lon_mins = float(l_string[3:]) + lon_hemi = self.gps_segments[4] + except ValueError: + return False + + if lat_hemi not in self.__HEMISPHERES: + return False + + if lon_hemi not in self.__HEMISPHERES: + return False + + # Update Object Data + self.latitude = (lat_degs, lat_mins, lat_hemi) + self.longitude = (lon_degs, lon_mins, lon_hemi) + self.valid = True + + # Update Last Fix Time + self.new_fix_time() + + else: # Clear Position Data if Sentence is 'Invalid' + self.latitude = (0, 0.0, 'N') + self.longitude = (0, 0.0, 'W') + self.valid = False + + return True + + def gpvtg(self): + """Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course""" + try: + course = float(self.gps_segments[1]) + spd_knt = float(self.gps_segments[5]) + except ValueError: + return False + + # Include mph and km/h + self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852) + self.course = course + return True + + def gpgga(self): + """Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude, + fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status""" + + try: + # UTC Timestamp + utc_string = self.gps_segments[1] + + # Skip timestamp if receiver doesn't have on yet + if utc_string: + hours = int(utc_string[0:2]) + self.local_offset + minutes = int(utc_string[2:4]) + seconds = float(utc_string[4:]) + else: + hours = 0 + minutes = 0 + seconds = 0.0 + + # Number of Satellites in Use + satellites_in_use = int(self.gps_segments[7]) + + # Horizontal Dilution of Precision + hdop = float(self.gps_segments[8]) + + # Get Fix Status + fix_stat = int(self.gps_segments[6]) + + except ValueError: + return False + + # Process Location and Speed Data if Fix is GOOD + if fix_stat: + + # Longitude / Latitude + try: + # Latitude + l_string = self.gps_segments[2] + lat_degs = int(l_string[0:2]) + lat_mins = float(l_string[2:]) + lat_hemi = self.gps_segments[3] + + # Longitude + l_string = self.gps_segments[4] + lon_degs = int(l_string[0:3]) + lon_mins = float(l_string[3:]) + lon_hemi = self.gps_segments[5] + except ValueError: + return False + + if lat_hemi not in self.__HEMISPHERES: + return False + + if lon_hemi not in self.__HEMISPHERES: + return False + + # Altitude / Height Above Geoid + try: + altitude = float(self.gps_segments[9]) + geoid_height = float(self.gps_segments[11]) + except ValueError: + return False + + # Update Object Data + self.latitude = (lat_degs, lat_mins, lat_hemi) + self.longitude = (lon_degs, lon_mins, lon_hemi) + self.altitude = altitude + self.geoid_height = geoid_height + + # Update Object Data + self.timestamp = (hours, minutes, seconds) + self.satellites_in_use = satellites_in_use + self.hdop = hdop + self.fix_stat = fix_stat + + # If Fix is GOOD, update fix timestamp + if fix_stat: + self.new_fix_time() + + return True + + def gpgsa(self): + """Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in + fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical + Dilution of Precision, and fix status""" + + # Fix Type (None,2D or 3D) + try: + fix_type = int(self.gps_segments[2]) + except ValueError: + return False + + # Read All (up to 12) Available PRN Satellite Numbers + sats_used = [] + for sats in range(12): + sat_number_str = self.gps_segments[3 + sats] + if sat_number_str: + try: + sat_number = int(sat_number_str) + sats_used.append(sat_number) + except ValueError: + return False + else: + break + + # PDOP,HDOP,VDOP + try: + pdop = float(self.gps_segments[15]) + hdop = float(self.gps_segments[16]) + vdop = float(self.gps_segments[17]) + except ValueError: + return False + + # Update Object Data + self.fix_type = fix_type + + # If Fix is GOOD, update fix timestamp + if fix_type > self.__NO_FIX: + self.new_fix_time() + + self.satellites_used = sats_used + self.hdop = hdop + self.vdop = vdop + self.pdop = pdop + + return True + + def gpgsv(self): + """Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence + parsed, and data on each satellite present in the sentence""" + try: + num_sv_sentences = int(self.gps_segments[1]) + current_sv_sentence = int(self.gps_segments[2]) + sats_in_view = int(self.gps_segments[3]) + except ValueError: + return False + + # Create a blank dict to store all the satellite data from this sentence in: + # satellite PRN is key, tuple containing telemetry is value + satellite_dict = dict() + + # Calculate Number of Satelites to pull data for and thus how many segment positions to read + if num_sv_sentences == current_sv_sentence: + sat_segment_limit = ((sats_in_view % 4) * 4) + 4 # Last sentence may have 1-4 satellites + else: + sat_segment_limit = 20 # Non-last sentences have 4 satellites and thus read up to position 20 + + # Try to recover data for up to 4 satellites in sentence + for sats in range(4, sat_segment_limit, 4): + + # If a PRN is present, grab satellite data + if self.gps_segments[sats]: + try: + sat_id = int(self.gps_segments[sats]) + except ValueError: + return False + + try: # elevation can be null (no value) when not tracking + elevation = int(self.gps_segments[sats+1]) + except ValueError: + elevation = None + + try: # azimuth can be null (no value) when not tracking + azimuth = int(self.gps_segments[sats+2]) + except ValueError: + azimuth = None + + try: # SNR can be null (no value) when not tracking + snr = int(self.gps_segments[sats+3]) + except ValueError: + snr = None + + # If no PRN is found, then the sentence has no more satellites to read + else: + break + + # Add Satellite Data to Sentence Dict + satellite_dict[sat_id] = (elevation, azimuth, snr) + + # Update Object Data + self.total_sv_sentences = num_sv_sentences + self.last_sv_sentence = current_sv_sentence + self.satellites_in_view = sats_in_view + + # For a new set of sentences, we either clear out the existing sat data or + # update it as additional SV sentences are parsed + if current_sv_sentence == 1: + self.satellite_data = satellite_dict + else: + self.satellite_data.update(satellite_dict) + + return True + + ########################################## + # Data Stream Handler Functions + ########################################## + + def new_sentence(self): + """Adjust Object Flags in Preparation for a New Sentence""" + self.gps_segments = [''] + self.active_segment = 0 + self.crc_xor = 0 + self.sentence_active = True + self.process_crc = True + self.char_count = 0 + + def updateall(self, string, fastmode=False): + try: + idx = 0 + string_tmp = self.oldstring + string + + for c in string_tmp: + idx = idx + 1 + stat = self.update(chr(c)) + if(stat != None): + self.oldstring = string_tmp[idx:] + if(fastmode): + self.stringclean() + return stat + return stat + + gc.collect() + except OSError: + print('Error') + + + def stringclean(self): + self.oldstring = bytes() + + def update(self, new_char): + """Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*') + Function builds a list of received string that are validate by CRC prior to parsing by the appropriate + sentence function. Returns sentence type on successful parse, None otherwise""" + + valid_sentence = False + + # Validate new_char is a printable char + ascii_char = ord(new_char) + + if 10 <= ascii_char <= 126: + self.char_count += 1 + + # Write Character to log file if enabled + if self.log_en: + self.write_log(new_char) + + # Check if a new string is starting ($) + if new_char == '$': + self.new_sentence() + return None + + elif self.sentence_active: + + # Check if sentence is ending (*) + if new_char == '*': + self.process_crc = False + self.active_segment += 1 + self.gps_segments.append('') + return None + + # Check if a section is ended (,), Create a new substring to feed + # characters to + elif new_char == ',': + self.active_segment += 1 + self.gps_segments.append('') + + # Store All Other printable character and check CRC when ready + else: + self.gps_segments[self.active_segment] += new_char + + # When CRC input is disabled, sentence is nearly complete + if not self.process_crc: + + if len(self.gps_segments[self.active_segment]) == 2: + try: + final_crc = int(self.gps_segments[self.active_segment], 16) + if self.crc_xor == final_crc: + valid_sentence = True + else: + self.crc_fails += 1 + except ValueError: + pass # CRC Value was deformed and could not have been correct + + # Update CRC + if self.process_crc: + self.crc_xor ^= ascii_char + + # If a Valid Sentence Was received and it's a supported sentence, then parse it!! + if valid_sentence: + self.clean_sentences += 1 # Increment clean sentences received + self.sentence_active = False # Clear Active Processing Flag + + if self.gps_segments[0][2:] in self.supported_sentences: + # parse the Sentence Based on the message type, return True if parse is clean + if self.supported_sentences[self.gps_segments[0][2:]](self): + # Let host know that the GPS object was updated by returning parsed sentence type + self.parsed_sentences += 1 + return self.gps_segments[0] + + # Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete + if self.char_count > self.SENTENCE_LIMIT: + self.sentence_active = False + + # Tell Host no new sentence was parsed + return None + + def new_fix_time(self): + """Updates a high resolution counter with current time when fix is updated. Currently only triggered from + GGA, GSA and RMC sentences""" + try: + self.fix_time = pyb.millis() + except NameError: + self.fix_time = time.time() + + ######################################### + # User Helper Functions + # These functions make working with the GPS object data easier + ######################################### + + def satellite_data_updated(self): + """ + Checks if the all the GSV sentences in a group have been read, making satellite data complete + :return: boolean + """ + if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence: + return True + else: + return False + + def satellites_visible(self): + """ + Returns a list of of the satellite PRNs currently visible to the receiver + :return: list + """ + return list(self.satellite_data.keys()) + + def time_since_fix(self): + """Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if + no fix has been found""" + + # Test if a Fix has been found + if self.fix_time == 0: + return -1 + + # Try calculating fix time assuming using millis on a pyboard; default to seconds if not + try: + current = pyb.elapsed_millis(self.fix_time) + except NameError: + current = time.time() - self.fix_time + + return current + + def compass_direction(self): + """ + Determine a cardinal or inter-cardinal direction based on current course. + :return: string + """ + # Calculate the offset for a rotated compass + if self.course >= 348.75: + offset_course = 360 - self.course + else: + offset_course = self.course + 11.25 + + # Each compass point is separated by 22.5 degrees, divide to find lookup value + dir_index = offset_course // 22.5 #dir_index = floor(offset_course / 22.5) + + final_dir = self.__DIRECTIONS[dir_index] + + return final_dir + + def latitude_string(self): + """ + Create a readable string of the current latitude data + :return: string + """ + lat_string = str(self.latitude[0]) + '° ' + str(self.latitude[1]) + "' " + str(self.latitude[2]) + return lat_string + + def latitude_decimal(self): + """ + Create a decimal value of the current latitude data + :return: decimal + """ + lat_decimal = float(self.latitude[0]) + float(self.latitude[1]) / 60 + return lat_decimal + + def longitude_string(self): + """ + Create a readable string of the current longitude data + :return: string + """ + long_string = str(self.longitude[0]) + '° ' + str(self.longitude[1]) + "' " + str(self.longitude[2]) + return long_string + + + def longitude_decimal(self): + """ + Create a decimal value of the current longitude data + :return: decimal + """ + long_decimal = float(self.longitude[0]) + float(self.longitude[1]) / 60 + return long_decimal + + def speed_string(self, unit='kph'): + """ + Creates a readable string of the current speed data in one of three units + :param unit: string of 'kph','mph, or 'knot' + :return: + """ + if unit == 'mph': + speed_string = str(self.speed[1]) + ' mph' + + elif unit == 'knot': + if self.speed[0] == 1: + unit_str = ' knot' + else: + unit_str = ' knots' + speed_string = str(self.speed[0]) + unit_str + + else: + speed_string = str(self.speed[2]) + ' km/h' + + return speed_string + + def date_string(self, formatting='s_mdy', century='20'): + """ + Creates a readable string of the current date. + Can select between long format: Januray 1st, 2014 + or two short formats: + 11/01/2014 (MM/DD/YYYY) + 01/11/2014 (DD/MM/YYYY) + :param formatting: string 's_mdy', 's_dmy', or 'long' + :param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX) + :return: date_string string with long or short format date + """ + + # Long Format Januray 1st, 2014 + if formatting == 'long': + # Retrieve Month string from private set + month = self.__MONTHS[self.date[1] - 1] + + # Determine Date Suffix + if self.date[0] in (1, 21, 31): + suffix = 'st' + elif self.date[0] in (2, 22): + suffix = 'nd' + elif self.date[0] == 3: + suffix = 'rd' + else: + suffix = 'th' + + day = str(self.date[0]) + suffix # Create Day String + + year = century + str(self.date[2]) # Create Year String + + date_string = month + ' ' + day + ', ' + year # Put it all together + + else: + # Add leading zeros to day string if necessary + if self.date[0] < 10: + day = '0' + str(self.date[0]) + else: + day = str(self.date[0]) + + # Add leading zeros to month string if necessary + if self.date[1] < 10: + month = '0' + str(self.date[1]) + else: + month = str(self.date[1]) + + # Add leading zeros to year string if necessary + if self.date[2] < 10: + year = '0' + str(self.date[2]) + else: + year = str(self.date[2]) + + # Build final string based on desired formatting + if formatting == 's_dmy': + date_string = day + '/' + month + '/' + year + + else: # Default date format + date_string = month + '/' + day + '/' + year + + return date_string + + # All the currently supported NMEA sentences + supported_sentences = {'RMC': gprmc, 'GGA': gpgga, 'VTG': gpvtg, 'GSA': gpgsa, 'GSV': gpgsv, 'GLL': gpgll} # GPS + GLONASS diff --git a/GW-custom/uPyLoRaWAN/uPySensors/vector3d.py b/GW-custom/uPyLoRaWAN/uPySensors/vector3d.py new file mode 100644 index 0000000000000000000000000000000000000000..2e757167f7af415e8865cf1403e9cf470e7a110f --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/vector3d.py @@ -0,0 +1,148 @@ +# vector3d.py 3D vector class for use in inertial measurement unit drivers +# Authors Peter Hinch, Sebastian Plamauer + +# V0.7 17th May 2017 pyb replaced with utime +# V0.6 18th June 2015 + +''' +The MIT License (MIT) +Copyright (c) 2014 Sebastian Plamauer, oeplse@gmail.com, Peter Hinch +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +''' + +from utime import sleep_ms +from math import sqrt, degrees, acos, atan2 + + +def default_wait(): + ''' + delay of 50 ms + ''' + sleep_ms(50) + + +class Vector3d(object): + ''' + Represents a vector in a 3D space using Cartesian coordinates. + Internally uses sensor relative coordinates. + Returns vehicle-relative x, y and z values. + ''' + def __init__(self, transposition, scaling, update_function): + self._vector = [0, 0, 0] + self._ivector = [0, 0, 0] + self.cal = (0, 0, 0) + self.argcheck(transposition, "Transposition") + self.argcheck(scaling, "Scaling") + if set(transposition) != {0, 1, 2}: + raise ValueError('Transpose indices must be unique and in range 0-2') + self._scale = scaling + self._transpose = transposition + self.update = update_function + + def argcheck(self, arg, name): + ''' + checks if arguments are of correct length + ''' + if len(arg) != 3 or not (type(arg) is list or type(arg) is tuple): + raise ValueError(name + ' must be a 3 element list or tuple') + + def calibrate(self, stopfunc, waitfunc=default_wait): + ''' + calibration routine, sets cal + ''' + self.update() + maxvec = self._vector[:] # Initialise max and min lists with current values + minvec = self._vector[:] + while not stopfunc(): + waitfunc() + self.update() + maxvec = list(map(max, maxvec, self._vector)) + minvec = list(map(min, minvec, self._vector)) + self.cal = tuple(map(lambda a, b: (a + b)/2, maxvec, minvec)) + + @property + def _calvector(self): + ''' + Vector adjusted for calibration offsets + ''' + return list(map(lambda val, offset: val - offset, self._vector, self.cal)) + + @property + def x(self): # Corrected, vehicle relative floating point values + self.update() + return self._calvector[self._transpose[0]] * self._scale[0] + + @property + def y(self): + self.update() + return self._calvector[self._transpose[1]] * self._scale[1] + + @property + def z(self): + self.update() + return self._calvector[self._transpose[2]] * self._scale[2] + + @property + def xyz(self): + self.update() + return (self._calvector[self._transpose[0]] * self._scale[0], + self._calvector[self._transpose[1]] * self._scale[1], + self._calvector[self._transpose[2]] * self._scale[2]) + + @property + def magnitude(self): + x, y, z = self.xyz # All measurements must correspond to the same instant + return sqrt(x**2 + y**2 + z**2) + + @property + def inclination(self): + x, y, z = self.xyz + return degrees(acos(z / sqrt(x**2 + y**2 + z**2))) + + @property + def elevation(self): + return 90 - self.inclination + + @property + def azimuth(self): + x, y, z = self.xyz + return degrees(atan2(y, x)) + + # Raw uncorrected integer values from sensor + @property + def ix(self): + return self._ivector[0] + + @property + def iy(self): + return self._ivector[1] + + @property + def iz(self): + return self._ivector[2] + + @property + def ixyz(self): + return self._ivector + + @property + def transpose(self): + return tuple(self._transpose) + + @property + def scale(self): + return tuple(self._scale) diff --git a/GW-custom/uPyLoRaWAN/uPySensors/vl53l0x.py b/GW-custom/uPyLoRaWAN/uPySensors/vl53l0x.py new file mode 100644 index 0000000000000000000000000000000000000000..65aa8d26128835bcd938d6540c7c1a0b4a414dbe --- /dev/null +++ b/GW-custom/uPyLoRaWAN/uPySensors/vl53l0x.py @@ -0,0 +1,344 @@ +from micropython import const +import ustruct +import utime +from machine import I2C + +_IO_TIMEOUT = 1000 +_SYSRANGE_START = const(0x00) +_EXTSUP_HV = const(0x89) +_MSRC_CONFIG = const(0x60) +_FINAL_RATE_RTN_LIMIT = const(0x44) +_SYSTEM_SEQUENCE = const(0x01) +_SPAD_REF_START = const(0x4f) +_SPAD_ENABLES = const(0xb0) +_REF_EN_START_SELECT = const(0xb6) +_SPAD_NUM_REQUESTED = const(0x4e) +_INTERRUPT_GPIO = const(0x0a) +_INTERRUPT_CLEAR = const(0x0b) +_GPIO_MUX_ACTIVE_HIGH = const(0x84) +_RESULT_INTERRUPT_STATUS = const(0x13) +_RESULT_RANGE_STATUS = const(0x14) +_OSC_CALIBRATE = const(0xf8) +_MEASURE_PERIOD = const(0x04) + +class TimeoutError(RuntimeError): + pass + +class VL53L0X: + def __init__(self, i2c, address=0x29): + if isinstance(i2c, str): # Non-pyb targets may use other than X or Y + self.i2c = I2C(i2c) + elif isinstance(i2c, int): # WiPY targets + self.i2c = I2C(i2c) + elif hasattr(i2c, 'readfrom'): # Soft or hard I2C instance. See issue #3097 + self.i2c = i2c + else: + raise ValueError("Invalid I2C instance") + + self.address = address + self.init() + self._started = False + + def _registers(self, register, values=None, struct='B'): + if values is None: + size = ustruct.calcsize(struct) + data = self.i2c.readfrom_mem(self.address, register, size) + values = ustruct.unpack(struct, data) + return values + data = ustruct.pack(struct, *values) + self.i2c.writeto_mem(self.address, register, data) + + def _register(self, register, value=None, struct='B'): + if value is None: + return self._registers(register, struct=struct)[0] + self._registers(register, (value,), struct=struct) + + def _flag(self, register=0x00, bit=0, value=None): + data = self._register(register) + mask = 1 << bit + if value is None: + return bool(data & mask) + elif value: + data |= mask + else: + data &= ~mask + self._register(register, data) + + def _config(self, *config): + for register, value in config: + self._register(register, value) + + def init(self, power2v8=True): + self._flag(_EXTSUP_HV, 0, power2v8) + + # I2C standard mode + self._config( + (0x88, 0x00), + + (0x80, 0x01), + (0xff, 0x01), + (0x00, 0x00), + ) + self._stop_variable = self._register(0x91) + self._config( + (0x00, 0x01), + (0xff, 0x00), + (0x80, 0x00), + ) + + # disable signal_rate_msrc and signal_rate_pre_range limit checks + self._flag(_MSRC_CONFIG, 1, True) + self._flag(_MSRC_CONFIG, 4, True) + + # rate_limit = 0.25 + self._register(_FINAL_RATE_RTN_LIMIT, int(0.25 * (1 << 7)), + struct='>H') + + self._register(_SYSTEM_SEQUENCE, 0xff) + + spad_count, is_aperture = self._spad_info() + spad_map = bytearray(self._registers(_SPAD_ENABLES, struct='6B')) + + # set reference spads + self._config( + (0xff, 0x01), + (_SPAD_REF_START, 0x00), + (_SPAD_NUM_REQUESTED, 0x2c), + (0xff, 0x00), + (_REF_EN_START_SELECT, 0xb4), + ) + + spads_enabled = 0 + for i in range(48): + if i < 12 and is_aperture or spads_enabled >= spad_count: + spad_map[i // 8] &= ~(1 << (i >> 2)) + elif spad_map[i // 8] & (1 << (i >> 2)): + spads_enabled += 1 + + self._registers(_SPAD_ENABLES, spad_map, struct='6B') + + self._config( + (0xff, 0x01), + (0x00, 0x00), + + (0xff, 0x00), + (0x09, 0x00), + (0x10, 0x00), + (0x11, 0x00), + + (0x24, 0x01), + (0x25, 0xFF), + (0x75, 0x00), + + (0xFF, 0x01), + (0x4E, 0x2C), + (0x48, 0x00), + (0x30, 0x20), + + (0xFF, 0x00), + (0x30, 0x09), + (0x54, 0x00), + (0x31, 0x04), + (0x32, 0x03), + (0x40, 0x83), + (0x46, 0x25), + (0x60, 0x00), + (0x27, 0x00), + (0x50, 0x06), + (0x51, 0x00), + (0x52, 0x96), + (0x56, 0x08), + (0x57, 0x30), + (0x61, 0x00), + (0x62, 0x00), + (0x64, 0x00), + (0x65, 0x00), + (0x66, 0xA0), + + (0xFF, 0x01), + (0x22, 0x32), + (0x47, 0x14), + (0x49, 0xFF), + (0x4A, 0x00), + + (0xFF, 0x00), + (0x7A, 0x0A), + (0x7B, 0x00), + (0x78, 0x21), + + (0xFF, 0x01), + (0x23, 0x34), + (0x42, 0x00), + (0x44, 0xFF), + (0x45, 0x26), + (0x46, 0x05), + (0x40, 0x40), + (0x0E, 0x06), + (0x20, 0x1A), + (0x43, 0x40), + + (0xFF, 0x00), + (0x34, 0x03), + (0x35, 0x44), + + (0xFF, 0x01), + (0x31, 0x04), + (0x4B, 0x09), + (0x4C, 0x05), + (0x4D, 0x04), + + (0xFF, 0x00), + (0x44, 0x00), + (0x45, 0x20), + (0x47, 0x08), + (0x48, 0x28), + (0x67, 0x00), + (0x70, 0x04), + (0x71, 0x01), + (0x72, 0xFE), + (0x76, 0x00), + (0x77, 0x00), + + (0xFF, 0x01), + (0x0D, 0x01), + + (0xFF, 0x00), + (0x80, 0x01), + (0x01, 0xF8), + + (0xFF, 0x01), + (0x8E, 0x01), + (0x00, 0x01), + (0xFF, 0x00), + (0x80, 0x00), + ) + + self._register(_INTERRUPT_GPIO, 0x04) + self._flag(_GPIO_MUX_ACTIVE_HIGH, 4, False) + self._register(_INTERRUPT_CLEAR, 0x01) + + # XXX Need to implement this. + #budget = self._timing_budget() + #self._register(_SYSTEM_SEQUENCE, 0xe8) + #self._timing_budget(budget) + + self._register(_SYSTEM_SEQUENCE, 0x01) + self._calibrate(0x40) + self._register(_SYSTEM_SEQUENCE, 0x02) + self._calibrate(0x00) + + self._register(_SYSTEM_SEQUENCE, 0xe8) + + def _spad_info(self): + self._config( + (0x80, 0x01), + (0xff, 0x01), + (0x00, 0x00), + + (0xff, 0x06), + ) + self._flag(0x83, 3, True) + self._config( + (0xff, 0x07), + (0x81, 0x01), + + (0x80, 0x01), + + (0x94, 0x6b), + (0x83, 0x00), + ) + for timeout in range(_IO_TIMEOUT): + if self._register(0x83): + break + utime.sleep_ms(1) + else: + raise TimeoutError() + self._config( + (0x83, 0x01), + ) + value = self._register(0x92) + self._config( + (0x81, 0x00), + (0xff, 0x06), + ) + self._flag(0x83, 3, False) + self._config( + (0xff, 0x01), + (0x00, 0x01), + + (0xff, 0x00), + (0x80, 0x00), + ) + count = value & 0x7f + is_aperture = bool(value & 0b10000000) + return count, is_aperture + + def _calibrate(self, vhv_init_byte): + self._register(_SYSRANGE_START, 0x01 | vhv_init_byte) + for timeout in range(_IO_TIMEOUT): + if self._register(_RESULT_INTERRUPT_STATUS) & 0x07: + break + utime.sleep_ms(1) + else: + raise TimeoutError() + self._register(_INTERRUPT_CLEAR, 0x01) + self._register(_SYSRANGE_START, 0x00) + + def start(self, period=0): + self._config( + (0x80, 0x01), + (0xFF, 0x01), + (0x00, 0x00), + (0x91, self._stop_variable), + (0x00, 0x01), + (0xFF, 0x00), + (0x80, 0x00), + ) + if period: + oscilator = self._register(_OSC_CALIBRATE, struct='>H') + if oscilator: + period *= oscilator + self._register(_MEASURE_PERIOD, period, struct='>H') + self._register(_SYSRANGE_START, 0x04) + else: + self._register(_SYSRANGE_START, 0x02) + self._started = True + + def stop(self): + self._register(_SYSRANGE_START, 0x01) + self._config( + (0xFF, 0x01), + (0x00, 0x00), + (0x91, self._stop_variable), + (0x00, 0x01), + (0xFF, 0x00), + ) + self._started = False + + def read(self): + if not self._started: + self._config( + (0x80, 0x01), + (0xFF, 0x01), + (0x00, 0x00), + (0x91, self._stop_variable), + (0x00, 0x01), + (0xFF, 0x00), + (0x80, 0x00), + (_SYSRANGE_START, 0x01), + ) + for timeout in range(_IO_TIMEOUT): + if not self._register(_SYSRANGE_START) & 0x01: + break + utime.sleep_ms(1) + else: + raise TimeoutError() + for timeout in range(_IO_TIMEOUT): + if self._register(_RESULT_INTERRUPT_STATUS) & 0x07: + break + utime.sleep_ms(1) + else: + raise TimeoutError() + value = self._register(_RESULT_RANGE_STATUS + 10, struct='>H') + self._register(_INTERRUPT_CLEAR, 0x01) + return value