├── UnitV └── MaixPy │ ├── UARTCamera │ ├── README.md │ └── uart_unitV.py │ └── IPCamera │ ├── README.md │ └── IPCam_unitv.py ├── M5StickC └── UIFlow │ ├── FireAnimation │ ├── natmod │ │ ├── bin │ │ │ ├── fullfire.mpy │ │ │ └── fire_module.mpy │ │ └── src │ │ │ ├── fire_with_c.py │ │ │ └── fire_module.c │ ├── fire_anim_arr.py │ └── README.md │ ├── Snake │ ├── README.md │ └── Snake.py │ ├── TwitchClient │ ├── wifi.py │ ├── selectionManager.py │ ├── README.md │ └── twitchClient.py │ ├── Ticker │ ├── README.md │ └── Ticker.py │ ├── AccelerometerKeyboard │ ├── README.md │ └── accelKeyboard.py │ ├── UARTCamera │ ├── README.md │ └── uart_stickC.py │ ├── BusStopInfo │ ├── README.md │ └── bsi.py │ ├── Maps │ ├── geolocate.py │ ├── README.md │ └── maps.py │ └── IPCamera │ ├── README.md │ ├── frontend │ ├── dist │ │ └── www │ │ │ └── index.html │ └── src │ │ └── index.html │ └── IPCam_stickc.py ├── README.md └── LICENSE /UnitV/MaixPy/UARTCamera/README.md: -------------------------------------------------------------------------------- 1 | Project description is [here](https://github.com/remixer-dec/M5Stack_Experiments/blob/master/M5StickC/UIFlow/UARTCamera) -------------------------------------------------------------------------------- /M5StickC/UIFlow/FireAnimation/natmod/bin/fullfire.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remixer-dec/M5Stack_Experiments/HEAD/M5StickC/UIFlow/FireAnimation/natmod/bin/fullfire.mpy -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M5Stack_Experiments 2 | small projects made for M5StickC and UnitV IoT development boards from M5Stack 3 | 4 | 5 | Current file structure: board / firmware / project -------------------------------------------------------------------------------- /M5StickC/UIFlow/FireAnimation/natmod/bin/fire_module.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remixer-dec/M5Stack_Experiments/HEAD/M5StickC/UIFlow/FireAnimation/natmod/bin/fire_module.mpy -------------------------------------------------------------------------------- /UnitV/MaixPy/IPCamera/README.md: -------------------------------------------------------------------------------- 1 | This is a part of a IPCamera project. 2 | Full project description is [here](https://github.com/remixer-dec/M5Stack_Experiments/blob/master/M5StickC/UIFlow/IPCamera) -------------------------------------------------------------------------------- /M5StickC/UIFlow/Snake/README.md: -------------------------------------------------------------------------------- 1 | # Classic Snake game for M5StickC 2 | ![Demo](https://i.imgur.com/pdz0iQS.gif) 3 | Eat as much food as you can, don't hit the borders and your tail. The snake can be controlled by a human or by an AI. 4 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/TwitchClient/wifi.py: -------------------------------------------------------------------------------- 1 | import network 2 | import time 3 | 4 | wlan=None 5 | 6 | def connect(ssid = "YOUR_WIFI_SSID", passwd = "YOUR_WIFI_PASS"): 7 | global wlan 8 | wlan=network.WLAN(network.STA_IF) 9 | wlan.active(True) 10 | wlan.disconnect() 11 | wlan.connect(ssid, passwd) 12 | 13 | while(wlan.ifconfig()[0] == '0.0.0.0'): 14 | time.sleep(1) 15 | return True 16 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/Ticker/README.md: -------------------------------------------------------------------------------- 1 | A ticker for M5StickC with UIFlow firmware 2 | ![Demo](https://i.imgur.com/6XNFoWJ.gif) 3 | 4 | Color, speed, max. text length, position, type and delay are configurable, screen rotation parameter defines default text length limit. 5 | This code does not override default UIFlow auto-linebreak behavior. See the .py file for examples. 6 | Default text length limit might look like it's smaller than the screen size, but it actually tries to avoid linebreaks of long Unicode characters, you can increase it manually, passing limit=number argument. -------------------------------------------------------------------------------- /M5StickC/UIFlow/AccelerometerKeyboard/README.md: -------------------------------------------------------------------------------- 1 | ## Accelerometer-controlled QWERTY keyboard for M5StickC 2 | 3 | ![Demo](https://i.imgur.com/vmyJVCq.gif) 4 | 5 | ### Usage: 6 | ```python 7 | from accelKeyboard import Keyboard 8 | inputText = Keyboard().loop() 9 | ``` 10 | 11 | ### Keyboard class 12 | cursor size, accelerometer sensitivity, cursor color, screen background color, keyboard background color and text color are configurable in class constructor 13 | 14 | Keyboard layout was originally designed for [Twitch Client](https://github.com/remixer-dec/M5Stack_Experiments/tree/master/M5StickC/UIFlow/TwitchClient) channel search. -------------------------------------------------------------------------------- /M5StickC/UIFlow/FireAnimation/natmod/src/fire_with_c.py: -------------------------------------------------------------------------------- 1 | import fire_module as fire 2 | from m5stack import lcd 3 | from random import randint 4 | import time 5 | 6 | w = 80 7 | h = 160 8 | 9 | fire.setup() 10 | lcd.clear() 11 | 12 | @micropython.viper 13 | def render(w:int, h:int): 14 | for y in range(0, h-1): 15 | for x in range(0, w): 16 | clr = int(fire.fire(randint(0,254), x, y)) 17 | if clr == 0: 18 | pass 19 | else: 20 | lcd.drawPixel(x, y, clr) 21 | 22 | @micropython.viper 23 | def drawLoop(): 24 | while True: 25 | #start = int(time.ticks_ms()) 26 | render(int(w), int(h)) 27 | #end = int(time.ticks_ms()) 28 | #print(end - start) #frametime 29 | drawLoop() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Remixer Dec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/UARTCamera/README.md: -------------------------------------------------------------------------------- 1 | # UART-based camera. 2 | The goal is to take a photo using UnitV, and transfer it to StickC and show it on the screen. 3 | ![Demo](https://i.imgur.com/SvZs45s.gif) 4 | 5 | Challenges: 6 | 1) Current version of UIFlow doesn't allow to load photos from RAMFS (Kernel panic) 7 | Solution: either save them to ROM or draw them pixel-by-pixel or use custom JPEG decoder. This project uses the second approach. 8 | 9 | 2) UIFlow doesn't have Image library that can load images from byte arrays 10 | Solution: use arrays with colors to store, read and write image data 11 | 12 | 3) StickC doesn't have enough RAM to store received bytearray. 13 | Solution: read received byte array and store its data in a typed int array, which uses less memory 14 | 15 | 4) Light colors use more memory and UART input can have different length, which can corrupt bytearray to array transformation 16 | Solution: append to array only amount of data that can be divided by 4, which is the number of bytes for int in bytearray. 17 | 18 | 5) Transferring data via UART is the slowest part of the process. 19 | Solution: (not implemented yet in this project) Use UARTHS (high-speed UART interface) in UnitV. 20 | 21 | UnitV code is [here](https://github.com/remixer-dec/M5Stack_Experiments/blob/master/UnitV/MaixPy/UARTCamera) -------------------------------------------------------------------------------- /M5StickC/UIFlow/BusStopInfo/README.md: -------------------------------------------------------------------------------- 1 | ## Realtime bus arrival bus stop board 2 | 3 | This project shows bus arrival times based on data provided by Russian service Yandex Maps. The rest of the readme is in Russian. 4 | ![Demo](https://i.imgur.com/j1REFtF.jpg) 5 | В этом проекте устройство m5StickC используется как информационное табло, на котором показывается информация о прибытии автобусов на выбранной остановке. Кнопками переключается порядок сортировки транспорта и режим отображения. 6 | 7 | Дисклеймер: данные для отображения берутся на основе недокументированного API, используемого в публичном сервисе [Яндекс.Карт](https://yandex.ru/maps/). В ходе разработки проекта, были изучены публично доступные исходные коды сервиса, и на их основе был написан клиент, имитирующий взаимодействие с сервисом. Со временем, изменения в архитектуре сервиса могут повлиять на работоспособность клиента. 8 | 9 | ID остановки указывается в константе ```BUS_STOP``` и его можно получить в адресной строке, кликнув на остановку в Яндекс.Картах. 10 | 11 | В приложении используется [зависимость](https://github.com/remixer-dec/M5Stack_Experiments/blob/master/M5StickC/UIFlow/TwitchClient/wifi.py) для подключения wi-fi, вместо которой, при желании, можно воспользоваться встроенным в UIFlow функционалом. Также, при старте синхронизируется системное время с помощью NTP. По умолчанию используется часовой пояс Московского времени. -------------------------------------------------------------------------------- /M5StickC/UIFlow/Maps/geolocate.py: -------------------------------------------------------------------------------- 1 | def findDevice(): 2 | import urequests as rq 3 | def getWifiNetworks(): 4 | import network 5 | import ubinascii 6 | import ujson 7 | wlan = network.WLAN(network.STA_IF) 8 | scanned = wlan.scan() 9 | output = [] 10 | for n in scanned: 11 | mac = ubinascii.hexlify(n[1]).decode('ascii') 12 | mac = ':'.join(mac[i:i+2] for i in range(0, len(mac), 2)) 13 | output.append({"macAddress": mac, "signalStrength": n[3], "channel": n[2]}) 14 | del scanned 15 | return ujson.dumps({'wifiAccessPoints': output}) 16 | wifis = getWifiNetworks() 17 | coordsjson = rq.post('https://location.services.mozilla.com/v1/geolocate?key=geoclue', 18 | headers={'Content-Type': 'application/json'}, data = wifis).json() 19 | coords = (coordsjson["location"]["lng"], coordsjson["location"]["lat"]) 20 | return coords 21 | 22 | def findPlace(txt): 23 | import urequests as rq 24 | results = rq.get( 25 | 'https://nominatim.openstreetmap.org/search?q={}&format=json&limit=1' 26 | .format(txt.replace('_', '+')), 27 | headers={ 28 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3945.88 Safari/537.36' 29 | }) 30 | results = results.json() 31 | if len(results) > 0: 32 | r = (float(results[0]["lon"]), float(results[0]["lat"]), 33 | results[0]["type"]) 34 | del results 35 | return r 36 | else: 37 | return False 38 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/IPCamera/README.md: -------------------------------------------------------------------------------- 1 | ## IP Camera PoC 2 | 3 | The idea is to capture images using UnitV camera, transfer them to StickC via UART and make the live-stream accessible via built-in web server. 4 | This project was only developed as proof-of-concept and should not be used in production. The main bottleneck is StickC's RAM. A better solution would be to create a custom web server, which sends image parts to client, as soon as they're received in UART. 5 | ![Demo](https://i.imgur.com/VAwg6l2.png) 6 | ### Frontend 7 | The frontend app can be used to watch the livestream, configure basic camera settings and set up some post-processing jobs. It can be laucnhed from device's server direcrly, or can be placed to external server instead. Make sure to add `````` inside the head tag, to place it somewhere else. 8 | 9 | #### Setup 10 | This app uses external [wifi](https://github.com/remixer-dec/M5Stack_Experiments/blob/master/M5StickC/UIFlow/TwitchClient/wifi.py) module - a simple silent module to connect to a wifi network. It can be replaced with similar built-in UIFlow functionality. 11 | To provide some basic security, the server has a whitelist function that is used as a decorator to filter requests by IP and allow only ones from the list. Add your IPs to the list or remove all ```whitelist``` decorators if you don't want to use it. 12 | Don't forget to put index.html to /flash/www/ 13 | Finally, 3 easy steps to launch the app: 14 | 1) run [UnitV code](https://github.com/remixer-dec/M5Stack_Experiments/blob/master/UnitV/MaixPy/IPCamera) 15 | 2) run StickC code 16 | 3) navigate to StickC's IP 17 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/TwitchClient/selectionManager.py: -------------------------------------------------------------------------------- 1 | from m5stack import lcd, btnA, btnB 2 | 3 | class SelectionManager: 4 | def __init__(self, total, boxW=80, boxH=20, boxColor = 0xffffff, bgColor=0, offsetY=0, callback=print, menu=0): 5 | self.total = total + menu 6 | self.real = total 7 | self.boxW = boxW 8 | self.boxH = boxH 9 | self.boxColor = boxColor 10 | self.bgColor = bgColor 11 | self.offsetY = offsetY 12 | self.selection = 1 13 | self.callback = callback 14 | 15 | def loop(self): 16 | self._render() 17 | while btnA.isPressed(): 18 | pass 19 | while True: 20 | if btnB.isPressed(): 21 | self.selection = (self.selection + 1) if self.selection < self.total else 1 22 | while True: 23 | if not btnB.isPressed(): 24 | break 25 | self._render() 26 | if btnA.isPressed(): 27 | return self.callback(self.selection - 1) 28 | 29 | def _render(self): 30 | def selOffset(selection): 31 | selection = selection if selection > 0 else self.real 32 | return (self.offsetY + (selection - 1) * self.boxH) 33 | if self.selection > self.real: 34 | if self.selection % 2 == 0: 35 | lcd.drawRect(0, selOffset(self.selection - 1), self.boxW, self.boxH, self.bgColor) 36 | lcd.drawRect(0, 144, 40, 16, self.boxColor) 37 | else: 38 | lcd.drawRect(0, 144, 40, 16, self.bgColor) 39 | lcd.drawRect(40, 144, 40, 16, self.boxColor) 40 | else: 41 | if self.selection == 1: 42 | lcd.drawRect(40, 144, 40, 16, self.bgColor) 43 | lcd.drawRect(0, selOffset(self.selection - 1), self.boxW, self.boxH, self.bgColor) 44 | lcd.drawRect(0, selOffset(self.selection), self.boxW, self.boxH, self.boxColor) 45 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/Maps/README.md: -------------------------------------------------------------------------------- 1 | # Maps for m5stickC 2 | Simple maps app that lets you explore the world map, find specific locations and locate the device using nearest wifi data. 3 | ![Demo](https://i.imgur.com/8oPkTzI.jpg) 4 | 5 | This app is using [OpenStreetMaps](https://www.openstreetmap.org/copyright "OpenStreetMaps") API to find location by name, [MapBox](https://mapbox.com "MapBox") and [Yandex](https://yandex.com/dev/maps/staticapi/doc/1.x/dg/concepts/input_params-docpage/ "Yandex") for map tile images and [Mozilla Location Services](https://location.services.mozilla.com "Mozilla Location Services") for wifi based positioning. 6 | You need to set up MapBox API token before using the app, you can get one [here](https://www.mapbox.com/maps/satellite/#the-data), by clicking "start building". Or you can change map provider to Yandex, by editing the configuration ```USE_YANDEX = True``` 7 | 8 | ### Requirements 9 | This project requires the following external modules: 10 | [mpy-img-decoder](https://github.com/remixer-dec/mpy-img-decoder) - custom PNG decoder that supports showing files without writing them to flash 11 | [accelKeyboard](https://github.com/remixer-dec/M5Stack_Experiments/tree/master/M5StickC/UIFlow/AccelerometerKeyboard) - a QWERTY keyboard module that uses accelerometer to input location 12 | [wifi](https://github.com/remixer-dec/M5Stack_Experiments/blob/master/M5StickC/UIFlow/TwitchClient/wifi.py) - a simple module to connect to a wifi network, can be replaced with similar built-in UIFlow functionality. 13 | 14 | ### RAM issues: 15 | If you face any memory-related problems, try decreasing map image height. Another solution would be to save PNG directly to flash and use native PNG renderer. Any optimization solutions are welcome. 16 | 17 | ### geolocate.py 18 | This file can be used as a library to get current device location coordinates using wifi data with ```findDevice()``` function, or to get coordinates of a place by its name using ```findPlace(name)``` function. 19 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/TwitchClient/README.md: -------------------------------------------------------------------------------- 1 | # Experimental twitch.tv client for m5stickC 2 | ![Demo](https://i.imgur.com/rkXi9tf.gif) 3 | ### What can you do with it? 4 | * browse top streams and games 5 | * preview their image, channel name and viewer count 6 | * join selected or entered chat room and read chat messages 7 | ### What can't you do with it? 8 | * play streams and videos 9 | * view emotes in the chat 10 | 11 | 12 | ## Configuration 13 | 14 | *you have to add a clientID to use this project, you can get it [here](https://dev.twitch.tv/console/apps/create) or [find](https://github.com/search) one ;)* 15 | 16 | Configuration is stored in ```tcfg``` dictionary. 17 | **clientID** - (str) twitch client ID 18 | **tickers** - (bool) uses tickers to scroll each line horizontally, othervise scrolls all messages vertically. 19 | **textBG** - (int or False), text color used as a background color of channel/game name text, works weird with UIFlow auto line-breaks 20 | 21 | ### This project requires the following external modules: 22 | [mpy-img-decoder](https://github.com/remixer-dec/mpy-img-decoder) - custom jpeg decoder that supports showing files without saving them 23 | [ticker](https://github.com/remixer-dec/M5Stack_Experiments/tree/master/M5StickC/UIFlow/Ticker) - a ticker library for multi-line scrolling chat 24 | [accelKeyboard](https://github.com/remixer-dec/M5Stack_Experiments/tree/master/M5StickC/UIFlow/AccelerometerKeyboard) - a QWERTY keyboard module that uses accelerometer to input channel name 25 | [uwebsockets](https://github.com/adrianalin/uwebsockets/tree/1977d95c06052ad9b77e22d07994921374f49d36/uwebsockets) - a websocket client to connect to chat servers, should be placed in uwebsockets folder 26 | 27 | ### and includes the following modules: 28 | ```wifi.py``` - a simple module to connect to a wifi network, can be replaced with similar built-in UIFlow functionality 29 | ```selectionManager.py``` - a module to make menu item selection interfaces, similar to UIFlow built-in menu. 30 | 31 | Twitch is a Trademark by Twitch Interactive, Inc -------------------------------------------------------------------------------- /M5StickC/UIFlow/FireAnimation/fire_anim_arr.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from m5stack import lcd 3 | import array 4 | import time 5 | 6 | w = 80 7 | h = 160 8 | 9 | @micropython.native 10 | def hsl_to_rgb(HSLlist): 11 | H, S, L = HSLlist 12 | S = S/100 13 | L = L/100 14 | C = (1 - abs(2*L - 1)) * S 15 | X = C * (1 - abs(((H / 60) % 2)-1)) 16 | m = L - C/2; 17 | [r1, g1, b1] = [C, X, 0] if H >= 0 and H < 60 else \ 18 | [X, C, 0] if H >= 60 and H < 120 else \ 19 | [0, C, X] if H >= 120 and H < 180 else \ 20 | [0, X, C] if H >= 180 and H < 240 else \ 21 | [X, 0, C] if H >= 240 and H < 300 else [C, 0, X] 22 | return [int((r1 + m) * 255), int((g1 + m) * 255), int((b1 + m) * 255)] 23 | 24 | @micropython.native 25 | def rgb_to_int(RGBlist): 26 | r, g, b = RGBlist 27 | return (r << 0x10) + (g << 0x8) + b 28 | 29 | @micropython.native 30 | def init(): 31 | global fire, palette 32 | fire = [array.array('H', [0] * w) for x in range(0, h)] 33 | palette = [rgb_to_int(hsl_to_rgb([x // 3, 100, min(100, (x * 100 // 255) *2)])) for x in range(0, 255)] 34 | 35 | @micropython.viper 36 | def fireManager(fire, x:int, y:int, w:int, h:int): 37 | fire[y][x] = int(((\ 38 | int(fire[(y + 1) % h][(x - 1 + w) % w]) \ 39 | + int(fire[(y + 2) % h][(x) % w]) \ 40 | + int(fire[(y + 1) % h][(x + 1) % w]) \ 41 | + int(fire[(y + 3) % h][(x) % w])) * 64) // 257) 42 | 43 | @micropython.viper 44 | def calcFire(fire, w:int, h:int): 45 | for y in range(0, h-1): 46 | for x in range(0, w): 47 | fireManager(fire, x, y, w, h) 48 | 49 | @micropython.viper 50 | def render(fire, w:int, h:int): 51 | for x in range(0, w): 52 | fire[h-1][x] = randint(0,255) 53 | for y in range(0, h-1): 54 | for x in range(0, w): 55 | px = int(palette[int(fire[y][x])]) 56 | if px == 0: 57 | pass 58 | else: 59 | lcd.drawPixel(x, y, px) 60 | 61 | @micropython.viper 62 | def renderer(fire): 63 | while 1: 64 | #start = int(time.ticks_ms()) 65 | calcFire(fire, int(w), int(h)) 66 | render(fire, int(w), int(h)) 67 | #end = int(time.ticks_ms()) 68 | #print(end - start) #frametime 69 | 70 | init() 71 | lcd.clear() 72 | renderer(fire) 73 | -------------------------------------------------------------------------------- /UnitV/MaixPy/UARTCamera/uart_unitV.py: -------------------------------------------------------------------------------- 1 | import sensor 2 | import array 3 | 4 | class UART: 5 | def __init__(self, checkInterval): 6 | from machine import Timer 7 | self.timer = Timer(Timer.TIMER0, Timer.CHANNEL0, start=False, callback=self.inputCheck, period=checkInterval, mode=Timer.MODE_PERIODIC) 8 | checking = False 9 | uart = False 10 | timer = False 11 | def connect(self): 12 | from fpioa_manager import fm 13 | import machine 14 | fm.register(35, fm.fpioa.UART2_RX, force=True) 15 | fm.register(34, fm.fpioa.UART2_TX, force=True) 16 | self.uart = machine.UART(machine.UART.UART2, 115200, 8, 0, 2, timeout=1000, read_buf_len=4096) 17 | self.uart.init() 18 | self.timer.start() 19 | return self.uart 20 | 21 | def inputCheck(self, timerobj): 22 | if (self.uart.any() > 0 and self.checking == False): 23 | self.checking = True 24 | self.checking = self.onMessage(actionManager) 25 | 26 | def onMessage(self, callback): 27 | while self.uart.any() > 0: 28 | msg = self.uart.readline() 29 | print("new message") 30 | print(msg) 31 | if callback != None: 32 | callback(msg.decode("utf-8"), self) 33 | return False 34 | 35 | def pic2PixelByteArray(img): 36 | print("encoding") 37 | w = 80 if img.width() > 80 else img.width() 38 | h = img.height() 39 | offset = 24 40 | arr = array.array('i', [w, h, 0]) 41 | for i in range(0, h): 42 | for j in range(offset, w + offset): 43 | pixcolor = rgbToInt(img.get_pixel(j, i)) 44 | arr.append(pixcolor) 45 | return bytearray(arr) 46 | 47 | def rgbToInt(RGBlist): 48 | return (RGBlist[0] << 0x10) + (RGBlist[1] << 0x8) + RGBlist[2] 49 | 50 | class Camera: 51 | isOn = False 52 | mode = sensor.RGB565 53 | frame = sensor.QQVGA2 54 | def shoot(shutdown = False): 55 | import sensor 56 | if not Camera.isOn: 57 | sensor.reset() 58 | sensor.set_vflip(True) 59 | sensor.set_hmirror(True) 60 | sensor.set_pixformat(Camera.mode) 61 | sensor.set_framesize(Camera.frame) 62 | sensor.skip_frames(10) 63 | pic = sensor.snapshot() 64 | sensor.shutdown(shutdown) 65 | Camera.isOn = not shutdown 66 | return pic 67 | 68 | def actionManager(msg, uart): 69 | def takePhoto(): 70 | print("taking photo") 71 | pic = Camera.shoot() 72 | uart.uart.write(b"picIncoming\n") 73 | barr = pic2PixelByteArray(pic) 74 | uart.uart.write(bytearray(repr(len(barr)) + "\n")) 75 | print("sending " + repr(len(barr)) + " bytes") 76 | uart.uart.write(barr) 77 | print("photo sent") 78 | del barr 79 | switch = { 80 | 'takePhoto\n': takePhoto() 81 | } 82 | if msg in switch: 83 | return switch[msg] 84 | 85 | uart = UART(50) 86 | uart.connect() 87 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/FireAnimation/README.md: -------------------------------------------------------------------------------- 1 | I saw fire animation project for m5StickC written in C by MajorSnags and decided to implement it in Micropython. 2 | C Project Source: https://m5stack.hackster.io/MajorSnags/m5stickc-flame-demo-ee6672 3 | Original Source: https://lodev.org/cgtutor/fire.html 4 | Forks: [optimized version](https://projects.om-office.de/frans/m5stickc_multitool/-/blob/main/fire.py) by [frans-fuerst](https://github.com/frans-fuerst) 5 | 6 | ![Demo](https://i.imgur.com/ruA7uP6.gif) 7 | 8 | Challenges: 9 | 1) List memory is limited, I was only able to render 80*40 pixel lists 10 | Solution: use array module and arrays with short numbers ('H') 11 | 12 | 2) Random implementation is different, no hsl module 13 | Solution: implement them yourself or find a working open-source implementation 14 | 15 | 3) It runs really slow 16 | Solution: use @micropython.native where it's possible and @micropython.viper only for heavy calculations, avoid floating points and precise division. 17 | 18 | Extra Optimizations: 19 | * pixel-skip - render a pixel only if its color is not empty / not current background color (0x000000) 20 | * 2func - separate fire pixel calculations and rendering into two functions 21 | * natmod - use C language to create a native module for computation part 22 | 23 | ### Speed comparison (frame time in ms) 24 | 25 | | variation | 1st frame time | 100th frame time | hard reboot required | 26 | |----------------------------------------------------|----------------|------------------|----------------------| 27 | | natmod \+ @viper rendering \(mpy\-cross compiled\) | 93 | 940 | yes | 28 | | natmod no\-pixel\-skip \+ @viper rendering | 966 | 966 | yes | 29 | | natmod \+ @viper rendering | 112 | 972 | yes | 30 | | @viper \+ 2func | 242 | 1073 | yes | 31 | | natmod \+ @native rendering | 251 | 1180 | yes | 32 | | @viper | 261 | 1383 | yes | 33 | | @native \+ 2func | 582 | 1484 | yes | 34 | | @native | 567 | 1759 | yes | 35 | | natmod \+ pure python rendering | 607 | 1768 | no | 36 | | pure python | 2036 | 3545 | no | 37 | | pure python \+ 2func | 2222 | 3566 | no | 38 | 39 | To use precompiled fullfire binary, try importing it inside a function if you face any errors. 40 | 41 | Further optimizations: decrease rendering area + decrease palette and random selection, add frameskip, this will make fire less realistic, but faster. 42 | 43 | P.S. Demo gif was speeded up 10x times 44 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/UARTCamera/uart_stickC.py: -------------------------------------------------------------------------------- 1 | import machine 2 | import array 3 | import gc 4 | from m5stack import btnA, lcd 5 | 6 | class UART: 7 | checking = False 8 | uart = machine.UART(1, tx=32, rx=33) 9 | timer = machine.Timer(8457) 10 | callback = None 11 | def connect(self, checkInterval=250): 12 | self.uart.init(115200, bits=8, parity=None, stop=2) 13 | self.timer.init(period=checkInterval, mode=machine.Timer.PERIODIC, callback=self.inputCheck) 14 | return self.uart 15 | 16 | def inputCheck(self, timerobj): 17 | if (self.uart.any() > 0 and self.checking == False): 18 | self.checking = True 19 | self.checking = self.onMessage() 20 | 21 | def onMessage(self): 22 | while self.uart.any() > 0: 23 | msg = self.uart.readline() 24 | print(msg) 25 | if self.callback: 26 | self.callback(msg,self) 27 | return False 28 | 29 | @micropython.native 30 | def actionManager(msg, uart): 31 | if(msg == b"picIncoming\n"): 32 | statusText("encoding\nphoto") 33 | uart.checking = True 34 | while uart.uart.any() == 0: 35 | pass 36 | size = uart.uart.readline() 37 | size = int(size.decode("utf-8")) 38 | while uart.uart.any() == 0: 39 | pass 40 | print("file transmission started") 41 | statusText("transferring\nphoto") 42 | received = 0 43 | imgarr = array.array('i') 44 | bytesLeft = bytearray([]) 45 | while received < size: 46 | while uart.uart.any() == 0: 47 | pass 48 | bytesreceived = uart.uart.read(256) 49 | if bytesreceived: 50 | bytesLeft += bytesreceived 51 | received += len(bytesreceived) 52 | limiter = (len(bytesLeft) // 4) * 4 53 | imgarr.extend(bytesLeft[:limiter]) 54 | bytesLeft = bytesLeft[limiter:] 55 | if received >= size: 56 | imgarr.extend(bytesLeft) 57 | del bytesreceived 58 | #print('received ' + repr(received) + '/' + repr(size)) 59 | gc.collect() 60 | print("file transmission ended, received:" + repr(len(imgarr))) 61 | uart.checking = False 62 | render(imgarr) 63 | del imgarr 64 | gc.collect() 65 | 66 | @micropython.viper 67 | def render(arr): 68 | w = int(arr[0]) 69 | h = int(arr[1]) 70 | lcd.clear() 71 | for y in range(0, h): 72 | for x in range(0, w): 73 | lcd.drawPixel(x, y, arr[3 + x + y * w]) 74 | 75 | @micropython.viper 76 | def statusText(text): 77 | lcd.fillRect(0, 0, 80, 32, 0) 78 | lcd.print(text, 0, 0) 79 | 80 | def pressLoop(): 81 | while True: 82 | if btnA.isPressed(): 83 | lcd.clear() 84 | statusText("taking\nphoto") 85 | uart.uart.write(b'takePhoto') 86 | while not btnA.isReleased(): 87 | pass 88 | 89 | uart = UART() 90 | uart.connect(50) 91 | uart.callback = actionManager 92 | lcd.font(lcd.FONT_DefaultSmall) 93 | lcd.clear() 94 | statusText("camera\nis ready") 95 | pressLoop() -------------------------------------------------------------------------------- /M5StickC/UIFlow/Ticker/Ticker.py: -------------------------------------------------------------------------------- 1 | from m5stack import lcd 2 | from machine import Timer 3 | 4 | class Ticker: 5 | _count = 0 6 | def __init__(self, text, color, rotation=3, sliding = True, limit=None, speed=250, x=0, y=0, delay=0, multiline=False): 7 | self._iterator = 0 8 | self._itdir = 1 9 | self._delayState = 0 10 | self.delay = delay 11 | self.sliding = sliding 12 | self.text = text 13 | self.color = color 14 | self.y = y 15 | self.x = x 16 | self.rotation = rotation 17 | self.limit = limit if limit else (7 if rotation % 2 == 0 else 14) 18 | self.timer = Timer(716839 + Ticker._count) 19 | if multiline: 20 | self._iterators = [0] * len(text) 21 | self.timer.init(period=speed, mode=Timer.PERIODIC, callback=self._showMultiline) 22 | else: 23 | self.timer.init(period=speed, mode=Timer.PERIODIC, callback=self._showText) 24 | Ticker._count += 1 25 | 26 | @micropython.native 27 | def _showMultiline(self, timercallbackvar=None): 28 | y = self.y 29 | lines = self.text 30 | i = 0 31 | for line in lines: 32 | #lcd.fillRect(self.x, self.y, 80, 16, 0) 33 | self.text = line 34 | self._iterator = self._iterators[i] 35 | self._showText() 36 | self.y += 16 37 | self._iterators[i] = self._iterator 38 | i += 1 39 | self.y = y 40 | self.text = lines 41 | 42 | @micropython.native 43 | def _showText(self, timercallbackvar=None): 44 | text = self.text 45 | if len(text) > self.limit: 46 | chunk = text[self._iterator:self._iterator + self.limit] 47 | if self._delayState < self.delay: 48 | self._delayState += 1 49 | else: 50 | self._iterator += self._itdir 51 | if self.sliding: 52 | if self._itdir == 1 and self._iterator >= (len(text) - self.limit): 53 | self._itdir *= -1 54 | self._delayState = 0 55 | if self._itdir == -1 and self._iterator == 0: 56 | self._itdir *= -1 57 | self._delayState = 0 58 | else: 59 | txtlen = len(text) 60 | if self._iterator >= txtlen - self.limit and self._iterator <= txtlen: 61 | lcd.fillRect(self.x + len(chunk) * 4, self.y, 180, self.y + 15, 0) 62 | if self._iterator > txtlen and self._iterator < self.limit + txtlen: 63 | offset = self.limit + txtlen - self._iterator 64 | chunk = ' ' * offset + text[:self._iterator - txtlen] 65 | if self._iterator >= self.limit + txtlen: 66 | self._iterator = 0 67 | lcd.print(chunk, self.x, self.y, self.color) 68 | else: 69 | lcd.print(text, self.x, self.y, self.color) 70 | 71 | def stop(self): 72 | self.timer.deinit() 73 | 74 | def Demo(): 75 | lcd.setRotation(3) 76 | lcd.clear() 77 | lcd.font(lcd.FONT_UNICODE) 78 | t = Ticker("This is a very long text", 0xffffff, sliding=False, delay=2) 79 | tt = Ticker("The line can be longer and can go faster and can even have unicodэ symbols", 0x85fa92, y=20, speed=100, delay=10) -------------------------------------------------------------------------------- /M5StickC/UIFlow/FireAnimation/natmod/src/fire_module.c: -------------------------------------------------------------------------------- 1 | #include "../py/dynruntime.h" 2 | 3 | #define SW 80 4 | #define SH 160 5 | 6 | const int palette[255] = {0, 0, 0, 655360, 655360, 655360, 1310720, 1310720, 1966336, 1966336, 1966336, 2621952, 2621952, 3277568, 3277568, 3277824, 3998976, 3998976, 4654848, 4654848, 4654848, 5310720, 5310720, 5966336, 5966848, 5966848, 6622464, 6622976, 6622976, 7344128, 7344640, 8000512, 8000512, 8001024, 8656896, 8656896, 9313280, 9313280, 9313280, 10035456, 10035456, 10691328, 10692096, 10692096, 11347968, 11348736, 12004608, 12004608, 12005376, 12661504, 12661504, 13383936, 13383936, 13383936, 14041088, 14041088, 14041088, 14698240, 14698240, 15354368, 15355392, 15355392, 16011520, 16012544, 16734464, 16734464, 16735488, 16737034, 16737034, 16739860, 16739860, 16739860, 16742430, 16742430, 16743976, 16745000, 16745000, 16746291, 16747315, 16747315, 16748861, 16749629, 16751175, 16751175, 16751943, 16753233, 16753233, 16755291, 16755291, 16755291, 16757349, 16757349, 16758640, 16759152, 16759152, 16760442, 16761210, 16762244, 16762244, 16762756, 16764046, 16764046, 16765593, 16765593, 16765593, 16767139, 16767139, 16767139, 16768685, 16768685, 16769719, 16769975, 16769975, 16771009, 16771265, 16772299, 16772299, 16772555, 16773334, 16773334, 16774368, 16774368, 16774368, 16775402, 16775402, 16776180, 16776180, 16776180, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215, 16777215}; 7 | 8 | int fire[SH][SW]; 9 | 10 | 11 | static mp_obj_t setup() { 12 | for (int y = 0; y < SH; y++) { 13 | for (int x = 0; x < SW; x++) { 14 | fire[y][x] = 0; 15 | } 16 | } 17 | return mp_obj_new_int(1); 18 | } 19 | 20 | static MP_DEFINE_CONST_FUN_OBJ_0(suobj, setup); 21 | 22 | static mp_obj_t pixel(mp_obj_t f_in, mp_obj_t x_in, mp_obj_t y_in) { 23 | mp_int_t f = mp_obj_get_int(f_in); 24 | mp_int_t x = mp_obj_get_int(x_in); 25 | mp_int_t y = mp_obj_get_int(y_in); 26 | 27 | if (y == 0) { 28 | fire[SH - 1][x] = f; 29 | } 30 | fire[y][x] = ((fire[(y + 1) % SH][(x - 1) % SW] 31 | + fire[(y + 2) % SH][(x) % SW] 32 | + fire[(y + 1) % SH][(x + 1) % SW] 33 | + fire[(y + 3) % SH][(x) % SW]) 34 | * 64) / 257; 35 | 36 | return mp_obj_new_int(palette[fire[y][x]]); 37 | } 38 | static MP_DEFINE_CONST_FUN_OBJ_3(fireobj, pixel); 39 | 40 | 41 | mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { 42 | MP_DYNRUNTIME_INIT_ENTRY 43 | mp_store_global(MP_QSTR_fire, MP_OBJ_FROM_PTR(&fireobj)); 44 | mp_store_global(MP_QSTR_setup, MP_OBJ_FROM_PTR(&suobj)); 45 | MP_DYNRUNTIME_INIT_EXIT 46 | } -------------------------------------------------------------------------------- /M5StickC/UIFlow/IPCamera/frontend/dist/www/index.html: -------------------------------------------------------------------------------- 1 | M5StickC+UnitV IPCam

Contrast: Brightness: Saturation: Gainceiling: Flashlight:
2 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/Snake/Snake.py: -------------------------------------------------------------------------------- 1 | from m5stack import lcd, btnA, btnB 2 | from machine import Timer 3 | from random import randint, seed 4 | from utime import ticks_cpu 5 | 6 | class Food: 7 | pos = [0, 0] 8 | def generate(): 9 | Food.pos = [randint(2, 157), randint(2, 77)] 10 | Food.pos = [Food.pos[0] if Food.pos[0] %2 == 0 else Food.pos[0] - 1, Food.pos[1] if Food.pos[1] % 2 == 0 else Food.pos[1] - 1] 11 | Food.checkPosition() 12 | 13 | def checkPosition(): 14 | while Snake.tail.count(Food.pos) > 0: 15 | Food.generate() 16 | 17 | class Snake: 18 | move_r = [2, 0] 19 | move_l = [-2,0] 20 | move_u = [0,-2] 21 | move_d = [0, 2] 22 | moves = [move_u, move_r, move_d, move_l] 23 | dr = 1 #direction 24 | tail = [[44, 44], [42, 44], [40, 44]] 25 | timer = Timer(58463) 26 | speed = 500 27 | notail = [2,2] 28 | isDead = False 29 | def isAlive(head): 30 | return head[0] > 0 and head[0] < 158 and head[1] > 0 and head[1] < 78 and Snake.tail.count(head) == 0 31 | 32 | def run(): 33 | seed(ticks_cpu()) 34 | lcd.setRotation(3) 35 | Snake.timer.init(period=Snake.speed, mode=Timer.PERIODIC, callback=Snake.move) 36 | 37 | def dirChange(): 38 | if btnB.isPressed(): 39 | Snake.dr += 1 40 | if btnA.isPressed(): 41 | Snake.dr -= 1 42 | if Snake.dr > 3: 43 | Snake.dr = 0 44 | if Snake.dr < 0: 45 | Snake.dr = 3 46 | 47 | def reset(): 48 | Snake.isDead = False 49 | Snake.tail = [[44, 44], [42, 44], [40, 44]] 50 | Snake.notail = [2,2] 51 | Snake.dr = 1 52 | Snake.speed = 500 53 | 54 | def speedUp(): 55 | Snake.speed = Snake.speed - 20 if Snake.speed > 140 else 140 56 | Snake.timer.init(period=Snake.speed, mode=Timer.PERIODIC, callback=Snake.move) 57 | 58 | def move(tmr=None): 59 | head = Snake.tail[0].copy() 60 | Snake.dirChange() 61 | #Snake.basicAIMove(head) #uncomment this line if you want AI to play Snake 62 | head[0] += Snake.moves[Snake.dr][0] 63 | head[1] += Snake.moves[Snake.dr][1] 64 | if [head].count(Food.pos) == 0: 65 | Snake.notail = Snake.tail.pop() 66 | else: 67 | Food.generate() 68 | Snake.speedUp() 69 | Snake.isDead = not Snake.isAlive(head) 70 | Snake.tail.insert(0, head) 71 | 72 | def basicAIMove(head): 73 | if head[0] == Food.pos[0]: 74 | Snake.dr = 2 if Food.pos[1] > head[1] and Snake.dr != 0 else 0 if Snake.dr !=2 else Snake.dr 75 | if head[1] == Food.pos[1]: 76 | Snake.dr = 1 if Food.pos[0] > head[0] and Snake.dr != 3 else 3 if Snake.dr !=1 else Snake.dr 77 | if (head[0] > 155 and Snake.dr == 1) or (head[0] < 3 and Snake.dr == 3): 78 | print(head[0]) 79 | Snake.dr = 0 if head[1] > Food.pos[1] else 2 80 | if (head[1] > 75 and Snake.dr == 2) or (head[1] < 4 and Snake.dr == 0): 81 | Snake.dr = 3 if head[0] > Food.pos[0] else 1 82 | 83 | class Game: 84 | def drawBorders(): 85 | lcd.drawRect(0, 0, 160, 80) 86 | 87 | def prepareField(): 88 | Snake.run() 89 | lcd.clear() 90 | Game.drawBorders() 91 | Food.generate() 92 | 93 | def loop(): 94 | #render snake 95 | for tailpart in Snake.tail: 96 | lcd.fillRect(tailpart[0], tailpart[1], 2, 2) 97 | #clear old tail coords 98 | lcd.fillRect(Snake.notail[0], Snake.notail[1], 2, 2, 0) 99 | #render food 100 | lcd.fillRect(Food.pos[0], Food.pos[1], 2, 2, 0x00ff00) 101 | return not Snake.isDead 102 | 103 | def over(): 104 | Snake.timer.deinit() 105 | lcd.clear() 106 | lcd.text(lcd.CENTER, lcd.CENTER, "GAME OVER") 107 | lcd.text(lcd.CENTER, 10, "score: " + repr(len(Snake.tail))) 108 | while btnA.isPressed(): 109 | pass 110 | while True: 111 | if btnA.isPressed(): 112 | Snake.reset() 113 | Game.prepareField() 114 | return 0 115 | 116 | Game.prepareField() 117 | while True: 118 | Game.loop() or Game.over() 119 | 120 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/AccelerometerKeyboard/accelKeyboard.py: -------------------------------------------------------------------------------- 1 | from m5stack import lcd, btnA, btnB 2 | 3 | 4 | class Keyboard: 5 | def __init__(self, cursorSize=4, sensitivity=0.6, cursorColor=0x11f011, topBG=0, keyboardBG=0x444444, textColor = 0xffffff): 6 | self.input = '' 7 | self.cursorSize = cursorSize 8 | self.sensitivity = sensitivity 9 | self.cursorColor = cursorColor 10 | self.topBG = topBG 11 | self.keyboardBG = keyboardBG 12 | self.textColor = textColor 13 | lcd.clear() 14 | lcd.font(lcd.FONT_Small) 15 | 16 | def loop(self): 17 | from libs.imu import IMU #from hardware import mpu6050 18 | mpu = IMU() #mpu = mpu6050.MPU6050() 19 | 20 | maxX = 80.0 21 | maxY = 160.0 22 | accel = [0.0, 0.0] 23 | accelSense = self.sensitivity 24 | curSize = self.cursorSize 25 | curPos = [40.0, 40.0] 26 | self._drawInputForm() 27 | self._drawLayoutBG() 28 | while True: 29 | self._showLayout() 30 | self._drawSelectedLetter(curPos) 31 | if curPos[1] <= 21: 32 | self._drawInputForm() 33 | self._drawInputText() 34 | accelX = mpu.acceleration[0] * accelSense * -1 35 | accelY = mpu.acceleration[1] * accelSense 36 | lcd.fillRect(round(curPos[0]), round(curPos[1]), curSize, curSize, 37 | self.topBG if curPos[1] < 104 else self.keyboardBG) 38 | if curPos[0] + accelX < 80 - curSize and curPos[0] + accelX > 0: 39 | curPos[0] += accelX 40 | if curPos[1] + accelY < 160 - curSize and curPos[1] + accelY > 0: 41 | curPos[1] += accelY 42 | lcd.fillRect(round(curPos[0]), round(curPos[1]), curSize, curSize, 43 | self.cursorColor) 44 | if btnA.isPressed(): 45 | key = self._getLetter(curPos) 46 | if key == '>': 47 | self._hide() 48 | return self.input 49 | else: 50 | self.input += key 51 | self._drawInputText() 52 | while btnA.isPressed(): 53 | pass 54 | if btnB.isPressed(): 55 | if len(self.input) > 0: 56 | self.input = self.input[:-1] 57 | self._drawInputText() 58 | while btnB.isPressed(): 59 | pass 60 | 61 | def _drawInputText(self): 62 | lcd.fillRect(10, 10, 90, 10, self.topBG) 63 | txt = self.input if len(self.input) < 8 else ('~' + self.input[-7:]) 64 | lcd.print(txt, 10, 9, self.textColor) 65 | 66 | def _drawInputForm(self): 67 | lcd.drawLine(10, 20, 70, 20, self.textColor) 68 | 69 | def _drawSelectedLetter(self, curPos): 70 | lcd.fillRect(0, 92, 11, 11, self.topBG) 71 | lcd.print(self._getLetter(curPos), 0, 92, self.cursorColor) 72 | 73 | def _drawLayoutBG(self): 74 | for i in range(56): 75 | lcd.fillRect(0, 160 - i, 80, 160, self.keyboardBG) 76 | return self 77 | 78 | def _showLayout(self): 79 | lcd.fillRect(0, 104, 80, 4, self.keyboardBG) 80 | lcd.print('123456789', 0, 108, self.textColor) 81 | lcd.print('0', 71, 108, self.textColor) 82 | lcd.print('QWERTYUIO', 0, 120, self.textColor) 83 | lcd.print('P', 71, 120, self.textColor) 84 | lcd.print('ASDFGHJKL', 0, 132, self.textColor) 85 | lcd.print('-', 71, 132, self.textColor) 86 | lcd.print('ZXCVBNM._', 0, 144, self.textColor) 87 | lcd.drawLine(78, 148, 78, 152, self.textColor) 88 | lcd.drawLine(78, 152, 74, 152, self.textColor) 89 | return self 90 | 91 | def _hide(self): 92 | for i in range(68): 93 | lcd.fillRect(0, 90, 80, 3 + i, self.topBG) 94 | 95 | def _getLetter(self, pos): 96 | x, y = pos 97 | l = '' 98 | if y < 108: 99 | return '' 100 | if y < 120: 101 | return '1234567890'[round(x / 80 * 10)] 102 | if y < 132: 103 | return 'QWERTYUIOP'[round(x / 80 * 10)] 104 | if y < 144: 105 | return 'ASDFGHJKL-'[round(x / 80 * 10)] 106 | if y < 160: 107 | return 'ZXCVBNM._>'[round(x / 80 * 10)] 108 | 109 | #k = Keyboard().loop() -------------------------------------------------------------------------------- /M5StickC/UIFlow/IPCamera/IPCam_stickc.py: -------------------------------------------------------------------------------- 1 | import machine 2 | import time 3 | import array 4 | import gc 5 | 6 | class UART: 7 | def __init__(self, checkInterval=250, callback=None): 8 | self.checking = False 9 | self.sending = False 10 | self.uart = machine.UART(1, tx=32, rx=33) 11 | self.timer = machine.Timer(8457) 12 | self.callback = callback 13 | self.checkInterval = checkInterval 14 | 15 | def connect(self): 16 | self.uart.init(115200, bits=8, parity=None, stop=1) 17 | self.timer.init(period=self.checkInterval, mode=machine.Timer.PERIODIC, callback=self.inputCheck) 18 | return self.uart 19 | 20 | def send(self, data): 21 | if self.sending: 22 | return 23 | ts = time.time() 24 | while self.checking: 25 | if time.time() - ts < 3: 26 | pass 27 | else: 28 | self.checking = False 29 | self.sending = True 30 | self.uart.write(data) 31 | self.sending = False 32 | 33 | def inputCheck(self, timerobj): 34 | if (self.uart.any() > 0 and self.checking is False): 35 | self.checking = True 36 | self.onMessage() 37 | 38 | def onMessage(self): 39 | while self.uart.any() > 0: 40 | msg = self.uart.readline() 41 | #print(msg) 42 | if self.callback: 43 | self.callback(msg) 44 | else: 45 | self.checking = False 46 | return 47 | 48 | def actionManager(msg): 49 | actions = {b'picIncoming\n': getPhoto} 50 | if msg in actions: 51 | actions[msg]() 52 | else: 53 | uart.checking = False 54 | 55 | def getPhoto(): 56 | global uart, camPic 57 | uart.checking = True 58 | try: 59 | while uart.uart.any() < 2: 60 | pass 61 | 62 | size = uart.uart.readline() 63 | size = int(size.decode("utf-8")) 64 | 65 | receivedSize = 0 66 | receivedBytes = bytearray(size) 67 | ts = time.time() 68 | 69 | while receivedSize < size: 70 | while uart.uart.any() == 0 and time.time() - ts < 3: 71 | pass 72 | if not uart.uart.any(): 73 | del receivedBytes 74 | uart.checking = False 75 | return 76 | ts = time.time() 77 | chunkSize = uart.uart.any() 78 | ba = bytearray(chunkSize) 79 | uart.uart.readinto(ba, chunkSize) 80 | receivedBytes[receivedSize:receivedSize+chunkSize] = ba 81 | receivedSize += chunkSize 82 | del ba 83 | 84 | camPic = receivedBytes 85 | except: 86 | gc.collect() 87 | print(b'NOT ENOUGH RAM') 88 | time.sleep_ms(40) 89 | while uart.uart.any(): 90 | uart.uart.read(uart.uart.any()) 91 | time.sleep_ms(40) 92 | uart.checking = False 93 | return 94 | 95 | 96 | class RemoteAction: 97 | def configure(*args): 98 | global camCfg 99 | camCfg = tuple(args) 100 | uart.send(b'configure\n') 101 | uart.send(b''+"_".join(map(str, camCfg)) + '\n') 102 | 103 | def takePhoto(): 104 | uart.send(b'takePhoto\n') 105 | 106 | class Backend: 107 | def whitelist(src): 108 | def isAllowed(req, res): 109 | if req.GetIPAddr() in allowedIPs: 110 | src(req, res) 111 | else: 112 | return res.WriteResponseBadRequest() 113 | return isAllowed 114 | 115 | def parseParams(params, keys): 116 | output = array.array('h') 117 | for key in keys: 118 | if key in params: 119 | output.append(int(params[key])) 120 | else: 121 | output.append(0) 122 | return output 123 | 124 | @whitelist 125 | def setCfgPage(req, res): 126 | params = req.GetRequestQueryParams() 127 | res.WriteResponse(204, (), '', '', '') 128 | RemoteAction.configure(*Backend.parseParams(params, 129 | ('res', 'color', 'filter1', 'filter2', 'nn', 130 | 'contrast', 'bright', 'sat', 'gain', 'flash'))) 131 | @whitelist 132 | def getCfgPage(req, res): 133 | res.WriteResponse(200, (), 'text/javascript', 'utf-8', 'setConfig'+repr(camCfg)+'') 134 | 135 | @whitelist 136 | def camImage(req, res): 137 | global camPic 138 | try: 139 | RemoteAction.takePhoto() 140 | res.WriteResponse(200, {'Cache-Control': 'no-cache, max-age=0'}, 141 | 'image/jpeg', 'binary', camPic) 142 | except: 143 | gc.collect() 144 | 145 | 146 | 147 | 148 | from MicroWebSrv.microWebSrv import MicroWebSrv 149 | 150 | import wifi 151 | wifi.connect() 152 | allowedIPs = ('192.168.1.45', '192.168.1.33') 153 | 154 | camPic = b'' 155 | camCfg = (4, 0, 0, 0, 0, 10, 0, 0, 3, 0) 156 | 157 | mws = MicroWebSrv(routeHandlers=[ 158 | ('/img.jpg','GET', Backend.camImage), 159 | ('/setcfg', 'GET', Backend.setCfgPage), 160 | ('/getcfg', 'GET', Backend.getCfgPage) 161 | ], webPath="/flash/www") 162 | mws.Start(threaded=True) 163 | 164 | uart = UART(32, callback=actionManager) 165 | uart.connect() 166 | time.sleep(2) 167 | uart.send(b'takePhoto\n') -------------------------------------------------------------------------------- /UnitV/MaixPy/IPCamera/IPCam_unitv.py: -------------------------------------------------------------------------------- 1 | import sensor 2 | import image 3 | 4 | try: 5 | from modules import ws2812 6 | except ImportError: 7 | class ws2812(): 8 | def __init__(self, p1, p2): 9 | pass 10 | def set_led(self, a, b): 11 | pass 12 | def display(self): 13 | pass 14 | 15 | LED = ws2812(8,1) 16 | 17 | RES = [sensor.VGA, sensor.QVGA, sensor.QQVGA, sensor.QQVGA2, sensor.CIF, sensor.SIF, sensor.B128X128, sensor.B64X64] 18 | CLR = [sensor.RGB565, sensor.GRAYSCALE, sensor.YUV422] 19 | 20 | 21 | class UART: 22 | def __init__(self, checkInterval,callback): 23 | from machine import Timer 24 | self.callback = callback 25 | self.timer = Timer(Timer.TIMER0, Timer.CHANNEL0, start=False, callback=self.inputCheck, period=checkInterval, mode=Timer.MODE_PERIODIC) 26 | self.checking = False 27 | self.sending = False 28 | self.uart = False 29 | 30 | def connect(self): 31 | from fpioa_manager import fm 32 | import machine 33 | fm.register(35, fm.fpioa.UARTHS_RX, force=True) 34 | fm.register(34, fm.fpioa.UARTHS_TX, force=True) 35 | self.uart = machine.UART(machine.UART.UARTHS, 115200, 8, 0, 1, timeout=1000, read_buf_len=4096) 36 | self.uart.init() 37 | self.timer.start() 38 | return self.uart 39 | 40 | def send(self, data): 41 | if not self.sending: 42 | self.sending = True 43 | self.uart.write(data) 44 | self.sending = False 45 | 46 | def inputCheck(self, timerobj): 47 | if (self.uart.any() > 0 and self.checking == False): 48 | self.checking = True 49 | self.checking = self.onMessage(self.callback) 50 | 51 | def onMessage(self, callback): 52 | while self.uart.any() > 0: 53 | msg = self.uart.readline() 54 | if callback != None: 55 | callback(msg) 56 | return False 57 | 58 | class Camera: 59 | isOn = False 60 | mode = sensor.RGB565 61 | frame = sensor.CIF 62 | brightness = 0 63 | contrast = 1 64 | saturation = 0 65 | postproc = 0 66 | filter1 = 0 67 | filter2 = 0 68 | flash = 0 69 | 70 | def shoot(shutdown = False): 71 | import sensor 72 | if not Camera.isOn: 73 | sensor.reset() 74 | sensor.set_vflip(True) 75 | sensor.set_hmirror(True) 76 | sensor.set_pixformat(Camera.mode) 77 | sensor.set_framesize(Camera.frame) 78 | sensor.skip_frames(10) 79 | 80 | pic = sensor.snapshot() 81 | pic.gamma_corr(1, Camera.contrast, Camera.brightness) 82 | if Camera.filter1 > 0: 83 | postProcess.applyFilter(Camera.filter1, pic) 84 | if Camera.filter2 > 0: 85 | postProcess.applyFilter(Camera.filter2, pic) 86 | if Camera.postproc == 1: 87 | postProcess.faceDetect(pic) 88 | 89 | sensor.shutdown(shutdown) 90 | Camera.isOn = not shutdown 91 | return pic 92 | 93 | class Action: 94 | def takePhoto(): 95 | try: 96 | pic = Camera.shoot() 97 | uart.send(b'picIncoming\n') 98 | picb = pic.compress().to_bytes() 99 | uart.send(str(len(picb)) + "\n") 100 | uart.send(picb) 101 | del pic 102 | del picb 103 | except MemoryError: 104 | print('NOT ENOUGH RAM, IT WAS ENOUGH IN PREVIOUS VERSIONS OF THIS FIRMWARE') 105 | 106 | def setConfig(): 107 | while not uart.uart.any() > 0: 108 | pass 109 | uart.checking = True 110 | data = uart.uart.readline() 111 | data = data.decode('ascii') 112 | data = list(map(int, data.split('_'))) 113 | Camera.frame = RES[data[0]] 114 | Camera.mode = CLR[data[1]] 115 | Camera.filter1 = data[2] 116 | Camera.filter2 = data[3] 117 | Camera.postproc = data[4] 118 | Camera.contrast = data[5] / 10 119 | Camera.brightness = data[6] / 10 120 | Camera.saturation = data[7] 121 | sensor.set_gainceiling(2**(data[8] if data[8] > 0 and data[8] < 8 else 3)) 122 | Camera.flash = data[9] if data[9] <= 255 and data[9] >= 0 else 0 123 | LED.set_led(0, (Camera.flash, Camera.flash, Camera.flash)) 124 | LED.display() 125 | sensor.set_pixformat(Camera.mode) 126 | sensor.set_framesize(Camera.frame) 127 | sensor.set_saturation(Camera.saturation) 128 | uart.checking = False 129 | 130 | def actionManager(msg): 131 | actions = {b'takePhoto\n': Action.takePhoto, b'configure\n': Action.setConfig} 132 | if msg in actions: 133 | return actions[msg]() 134 | 135 | class postProcess: 136 | faceCascade = image.HaarCascade('frontalface') 137 | 138 | def applyFilter(fid, img): 139 | f = [None, lambda i: i.gaussian(1), lambda i: i.histeq(), lambda i: i.mean(1), 140 | lambda i: i.mean(1), lambda i: i.erode(2), 141 | lambda i: i.dilate(2), lambda i: i.chrominvar() if i.format() == 2 else i, 142 | lambda i: i.illuminvar() if i.format() == 2 else i] 143 | return f[fid](img) 144 | 145 | def detectFace(img): 146 | f = img.find_features(postProcess.faceCascade, 0.9) 147 | if f: 148 | img.draw_rectangle(*f) 149 | 150 | 151 | uart = UART(32, callback=Action.actionManager) 152 | uart.connect() 153 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/IPCamera/frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | M5StickC+UnitV IPCam 7 | 8 | 9 | 82 | 83 | 84 |
85 |
86 | 87 | 88 |
89 |
90 | 101 |
107 | 119 | 131 | 136 |
137 | Contrast: 138 | Brightness: 139 | Saturation: 140 | Gainceiling: 141 | Flashlight: 142 |
143 |
144 |
145 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/TwitchClient/twitchClient.py: -------------------------------------------------------------------------------- 1 | from m5stack import lcd, btnA, btnB 2 | import gc 3 | 4 | from wifi import connect 5 | from JPEGdecoder import jpeg 6 | from ticker import Ticker 7 | from selectionManager import SelectionManager 8 | 9 | 10 | tcfg = {'clientID': 'REPLACE_THIS_WITH_YOUR_CLIENT_ID', 'tickers': True, 'textBG': False} 11 | 12 | gameFilter = '' 13 | msgQueue = [] 14 | ticker = 0 15 | 16 | def drawHeader(): 17 | lcd.fillRect(0, 0, 80, 16, 0x6441a5) 18 | lcd.text(lcd.CENTER, 2, 'TwitchIoT') 19 | 20 | def mainLoop(): 21 | nextItem = mainMenu() 22 | while True: 23 | if isinstance(nextItem, list): 24 | nextItem = nextItem[0](nextItem[1]) 25 | else: 26 | nextItem = nextItem() 27 | gc.collect() 28 | 29 | 30 | def mainMenu(idk=0): 31 | global gameFilter 32 | gameFilter = '' 33 | lcd.font(lcd.FONT_Small) 34 | lcd.clear() 35 | drawHeader() 36 | lcd.text(lcd.CENTER, 20, 'Streams') 37 | lcd.text(lcd.CENTER, 40, 'Games') 38 | lcd.text(lcd.CENTER, 60, 'Users') 39 | lcd.text(lcd.CENTER, 80, 'Exit') 40 | def mainCallback(sel): 41 | return (streamsMenu, gamesMenu, searchMenu, leave)[sel] 42 | return SelectionManager(4, offsetY=18, callback=mainCallback).loop() 43 | 44 | def leave(): 45 | import sys 46 | sys.exit() 47 | 48 | def loadAndDrawOnePreview(img, x, y): 49 | import urequests as rq 50 | try: 51 | img = img.replace('./', '') 52 | jpg = jpeg(rq.get(img).content, callback=lcd.drawPixel, quality=5).render(x, y) 53 | del jpg 54 | except (MemoryError, NotImplementedError): 55 | lcd.fillRect(x, y, 80, 44, 0x444444) 56 | gc.collect() 57 | 58 | def trippleText(x, y, txt, clr): 59 | if tcfg['textBG'] is not False: 60 | lcd.textClear(x, y, txt, int(tcfg['textBG'])) 61 | else: 62 | lcd.text(x, y+1, txt, 0) 63 | lcd.text(x, y-1, txt, 0) 64 | lcd.text(x, y, txt, clr) 65 | 66 | def streamsMenu(streamOffset=0): 67 | ofy = 0 68 | data = twitchAPI('streams', {'limit': 3, 'offset': streamOffset, 'game': gameFilter}) 69 | channels = [] 70 | lcd.clear() 71 | lcd.drawLine(0, 0, 80, 0, 0x6441a5) 72 | for item in data['streams']: 73 | lcd.fillRect(0, ofy, 80, 45, 0x333333) 74 | lcd.text(lcd.CENTER, ofy+16,'LOADING') 75 | loadAndDrawOnePreview(item['preview']['small'], 0, 1+ofy) 76 | trippleText(2, ofy+2, item['channel']['name'], 0xffffff) 77 | channels.append(item['channel']['name']) 78 | trippleText(lcd.RIGHT, ofy+32, str(item['viewers']), 0xFF0000) 79 | ofy += 46 80 | del data 81 | lcd.text(4, 144, 'more', 0xFFFFFF) 82 | lcd.text(44, 144, 'back', 0xFFFFFF) 83 | def sc(n=0): 84 | return selectChannel(channels[n]) 85 | 86 | def channelCallback(sel): 87 | return [(sc, sc, sc, streamsMenu, mainMenu)[sel], (streamOffset+sel if sel == 3 else sel)] 88 | 89 | return SelectionManager(3, offsetY=1, boxH=46, callback=channelCallback, menu=2).loop() 90 | 91 | def selectChannel(name): 92 | global ticker 93 | import uwebsockets.client as uwc 94 | lcd.clear() 95 | lcd.text(lcd.CENTER, 40, 'joining') 96 | lcd.text(lcd.CENTER, 60, 'chat') 97 | lcd.font(lcd.FONT_UNICODE) 98 | if tcfg['tickers']: 99 | ticker = Ticker(['']*9, 0xffffff, rotation=0, sliding=False, speed=16, delay=10, x=2, multiline=True) 100 | 101 | exitChat = False 102 | with uwc.connect('wss://irc-ws.chat.twitch.tv') as ws: 103 | ws.send('NICK justinfan123') 104 | #ws.send('CAP REQ :twitch.tv/tags') 105 | ws.send('JOIN #' + name) 106 | msg = ws.recv() 107 | i = 0 108 | while msg.find('/NAMES') == -1: 109 | msg = ws.recv() 110 | msg = ws.recv() 111 | while not exitChat: 112 | if msg != '': 113 | #gc.collect() 114 | parseChatMsg(msg) 115 | msg = ws.recv() 116 | while btnB.isPressed(): 117 | exitChat = True 118 | if tcfg['tickers']: 119 | ticker.stop() 120 | del ticker 121 | return mainMenu 122 | 123 | def parseChatMsg(msg): 124 | s = msg.split(':') 125 | username = s[1].split('!')[0] 126 | msg = s[2] 127 | del s 128 | renderChatMsg(username, msg) 129 | 130 | 131 | def renderChatMsg(usr, msg): 132 | global msgQueue 133 | if len(msgQueue) > 8: 134 | msgQueue.pop(0) 135 | lcd.clear() 136 | msgQueue.append(usr+':' + msg) 137 | if tcfg['tickers']: 138 | for i in range(len(msgQueue)): 139 | ticker.text[i] = msgQueue[i] 140 | else: 141 | lcd.clear() 142 | lcd.text(0, 2, '\n'.join(msgQueue[-4:])) 143 | 144 | def gamesMenu(itemOffset=0): 145 | ofy = 0 146 | data = twitchAPI('games/top', {'limit': 3, 'offset': itemOffset}) 147 | games = [] 148 | lcd.clear() 149 | lcd.drawLine(0, 0, 80, 0, 0x6441a5) 150 | for item in data['top']: 151 | lcd.fillRect(0, ofy, 80, 45, 0x333333) 152 | lcd.text(lcd.CENTER, ofy+16,'LOADING') 153 | loadAndDrawOnePreview(item['game']['box']['template'] 154 | .replace('{width}', '80').replace('{height}', '45'), 0 , 1+ofy) 155 | trippleText(2, ofy+2, item['game']['name'], 0xffffff) 156 | games.append(item['game']['name']) 157 | trippleText(lcd.RIGHT, ofy+32, str(item['viewers']), 0xFF0000) 158 | ofy += 46 159 | lcd.text(4, 144, 'more', 0xFFFFFF) 160 | lcd.text(44, 144, 'back', 0xFFFFFF) 161 | 162 | def gameCallback(sel): 163 | global gameFilter 164 | if sel < 3: 165 | if sel >= len(games): 166 | return mainMenu 167 | gameFilter = games[sel].replace(' ','+') 168 | return [(streamsMenu, streamsMenu, streamsMenu, gamesMenu, mainMenu)[sel], (itemOffset+sel if sel == 3 else 0)] 169 | return SelectionManager(3, offsetY=1, boxH=46, callback=gameCallback, menu=2).loop() 170 | 171 | def searchMenu(): 172 | from accelKeyboard import Keyboard 173 | channelName = Keyboard().loop() 174 | return selectChannel(channelName.lower()) 175 | 176 | def twitchAPI(method, params): 177 | import urequests as rq 178 | def _q(d): 179 | query = "" 180 | for key in d.keys(): 181 | query += str(key) + "=" + str(d[key]) + "&" 182 | return query 183 | url = "https://api.twitch.tv/kraken/" + method + "/?" + _q(params) 184 | return rq.get(url, headers={'accept':'application/vnd.twitchtv.v5+json', 'client-id': tcfg['clientID']}).json() 185 | 186 | 187 | connect() 188 | mainLoop() 189 | 190 | -------------------------------------------------------------------------------- /M5StickC/UIFlow/Maps/maps.py: -------------------------------------------------------------------------------- 1 | from m5stack import lcd, btnA, btnB 2 | from PNGdecoder import png 3 | import wifi 4 | import gc 5 | from array import array 6 | 7 | wifi.connect() 8 | del wifi 9 | 10 | def getMap(lon, lat, zoom, pitch): 11 | import urequests as rq 12 | 13 | USE_YANDEX = True 14 | MAPBOX_TOKEN = '' 15 | HEIGHT = 148 16 | 17 | url = 'https://static-maps.yandex.ru/1.x/?ll={lon},{lat}&z={zoom}&l=map&size=80,{height}' if USE_YANDEX \ 18 | else 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/{lon},{lat},{zoom},{rad},{pitch}/80x{height}?access_token={mbt}' 19 | return rq.get(url.format(lon=lon, lat=lat, zoom=zoom, rad=0, pitch=pitch, height=HEIGHT, mbt=MAPBOX_TOKEN)).content 20 | 21 | 22 | def drawControls(): 23 | lcd.fillRect(0, 148, 80, 11, 0x2b4860) 24 | pos = array('b', (4, 14, 24, 34, 44, 48, 54, 58, 66, 68, 70)) 25 | txt = '+-<>/\\\\/...' 26 | for i, char in enumerate(txt): 27 | lcd.text(pos[i], 148, char, 0xffffff) 28 | 29 | 30 | class SelectionManagerV2: 31 | def __init__(self, smtype=0, total=2, callback=print): 32 | self.total = total 33 | self.sel = 0 34 | self.type = smtype 35 | self.callback = callback 36 | 37 | def loop(self): 38 | self.callback(self) 39 | while True: 40 | if btnA.isPressed(): 41 | while btnA.isPressed(): 42 | pass 43 | return self.sel 44 | if btnB.isPressed(): 45 | self.sel = self.sel + 1 if self.sel < self.total - 1 else 0 46 | while btnB.isPressed(): 47 | pass 48 | self.callback(self) 49 | 50 | 51 | def selectionRenderer(smInstance): 52 | oldSel = smInstance.sel - 1 if smInstance.sel - 1 >= 0 else smInstance.total - 1 53 | if smInstance.type == 0: 54 | lcd.fillRect(2, 22 + 12 * smInstance.sel, 76, 12, 0x88bfb9) 55 | lcd.fillRect(2, 22 + 12 * oldSel, 76, 12, 0x2b4860) 56 | mainMenuText() 57 | else: 58 | lcd.drawRect(smInstance.sel * 10, 148, 59 | 4 + (10 if smInstance.sel < 4 else 12), 12, 0x88bfb9) 60 | lcd.drawRect(oldSel * 10, 148, 4 + (10 if oldSel < 4 else 12), 12, 61 | 0x2b4860) 62 | 63 | 64 | def mainMenuText(): 65 | lcd.text(lcd.CENTER, 22, 'World Map') 66 | lcd.text(lcd.CENTER, 34, 'Search') 67 | lcd.text(lcd.CENTER, 46, 'Find me') 68 | lcd.text(lcd.CENTER, 58, 'Exit') 69 | 70 | 71 | def mainMenu(): 72 | def worldMap(): 73 | return mapView 74 | 75 | def leave(): 76 | import sys 77 | sys.exit() 78 | 79 | lcd.clear(0x2b4860) 80 | lcd.fillRect(0, 0, 80, 12, 0xc6585a) 81 | lcd.font(lcd.FONT_Arial12) 82 | lcd.text(lcd.CENTER, 0, 'MAPS', 0xFFFFFF) 83 | lcd.font(lcd.FONT_Small) 84 | callbacks = (worldMap, search, findMe) 85 | s = SelectionManagerV2(0, 4, selectionRenderer).loop() 86 | return callbacks[s] 87 | 88 | 89 | LRzoomFactors = array('f', (30, 0, 10, 0, .5, 0, .4, 0, .1, 0, .04, 0, .01, 0, .002, 0, .0005, 0, .0002, 0, .00005)) 90 | UDzoomFactors = array('f', (0, 0, 5, 0, 1, 0, .5, 0, .2, 0, .05, 0, .01, 0, .005, 0, .0005, 0, .0001, 0, .00005)) 91 | 92 | 93 | class MapData: 94 | def __init__(self, lon, lat, zoom, pitch): 95 | self.lon = lon 96 | self.lat = lat 97 | self.pitch = pitch 98 | self.zoom = zoom 99 | 100 | def scaleUp(self): 101 | self.zoom = self.zoom + 2 if self.zoom < 20 else 20 102 | return self 103 | 104 | def scaleDown(self): 105 | self.zoom = self.zoom - 2 if self.zoom > 0 else 0 106 | return self 107 | 108 | def moveLeft(self): 109 | self.lon -= LRzoomFactors[self.zoom] 110 | return self.fixLimits() 111 | 112 | def moveRight(self): 113 | self.lon += LRzoomFactors[self.zoom] 114 | return self.fixLimits() 115 | 116 | def moveUp(self): 117 | self.lat += UDzoomFactors[self.zoom] 118 | return self.fixLimits() 119 | 120 | def moveDown(self): 121 | self.lat -= UDzoomFactors[self.zoom] 122 | return self.fixLimits() 123 | 124 | def fixLimits(self): 125 | if self.lon < -180: 126 | self.lon = 180 + (180 - self.lon) 127 | if self.lon > 180: 128 | self.lon = -180 + (self.lon - 180) 129 | if self.lat < -85.0511: 130 | self.lat = 85.0511 + (85.0511 - self.lon) 131 | if self.lat > 85.0511: 132 | self.lat = -85.0515 + (self.lat - 85.0511) 133 | return self 134 | 135 | #Longitude must be between -180-180 136 | #Latitude must be between -85.0511-85.0511. 137 | 138 | def mapView(lon=0, lat=0, zoom=0): 139 | m = MapData(lon, lat, zoom, 50) 140 | try: 141 | img = png(getMap(lon, lat, zoom, 50), 142 | callback=lcd.drawPixel).render(0, 0) 143 | del img 144 | except MemoryError: 145 | lcd.text(0, 10, 'Not enough RAM', 0) 146 | gc.collect() 147 | sm = SelectionManagerV2(1, 7, selectionRenderer) 148 | while True: 149 | drawControls() 150 | s = sm.loop() 151 | callbacks = (m.scaleUp, m.scaleDown, m.moveLeft, m.moveRight, m.moveUp, 152 | m.moveDown, mainMenu) 153 | if s != 6: 154 | callbacks[s]() 155 | del callbacks 156 | try: 157 | img = png(getMap(m.lon, m.lat, m.zoom, m.pitch), 158 | callback=lcd.drawPixel).render(0, 0) 159 | del img 160 | except MemoryError: 161 | lcd.text(0, 10, 'Not enough RAM', 0) 162 | gc.collect() 163 | else: 164 | return mainMenu 165 | 166 | 167 | def findMe(): 168 | from geolocate import findDevice 169 | coords = findDevice() 170 | del findDevice 171 | gc.collect() 172 | return mapView(coords[0], coords[1], 12) 173 | 174 | 175 | def search(): 176 | from accelKeyboard import Keyboard 177 | from geolocate import findPlace 178 | inputText = Keyboard().loop() 179 | coords = findPlace(inputText) 180 | del Keyboard 181 | del inputText 182 | del findPlace 183 | if coords: 184 | gc.collect() 185 | return mapView(coords[0], coords[1], 14 if coords[2] != 'city' else 8) 186 | else: 187 | lcd.clear() 188 | lcd.text(0, 20, 'Place not Found') 189 | lcd.text(lcd.CENTER, 100, 'ok') 190 | while not btnA.isPressed() or btnB.isPressed(): 191 | while btnA.isPressed() or btnB.isPressed(): 192 | pass 193 | return search 194 | 195 | 196 | def mainLoop(): 197 | item = mainMenu 198 | while True: 199 | item = item() 200 | 201 | mainLoop() -------------------------------------------------------------------------------- /M5StickC/UIFlow/BusStopInfo/bsi.py: -------------------------------------------------------------------------------- 1 | import urequests as rq 2 | import gc 3 | 4 | import wifi 5 | wifi.connect() 6 | 7 | API_URL = 'https://yandex.ru/maps/api/' 8 | yUID = '' 9 | csrftoken = '' 10 | BUS_STOP = 'stop__9642916' 11 | 12 | def getBusStopInfo(stop_id ): 13 | global csrftoken, yUID 14 | 15 | def GET(url, headers = {}): 16 | headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3945.88 Safari/537.36' 17 | return rq.get(url, headers=headers) 18 | 19 | def getCSRFToken(): 20 | return GET(API_URL, {'Cookie': 'yandexuid=' + yUID}).json()["csrfToken"] 21 | 22 | @micropython.native 23 | def sign(s) -> int: 24 | from array import array 25 | t = len(s) 26 | n = array('i', [5381]) 27 | for r in range(t): 28 | n[0] = 33 * n[0] ^ ord(s[r]) 29 | return 4294967296 + n[0] if n[0] < 0 else n[0] 30 | 31 | def formQueryString(d): 32 | query = "" 33 | for key in d.keys(): 34 | query += str(key) + "=" + str(d[key]) + "&" 35 | return query[:-1] 36 | 37 | def formReqObj(token, stopid, sid): 38 | from collections import OrderedDict 39 | return OrderedDict([('ajax', 1), ('csrfToken', token.replace(':', '%3A')), ('id', stopid), ('lang', 'ru'), 40 | ('locale', 'ru_RU'), ('mode', 'prognosis'),('sessionId', sid)]) 41 | 42 | def formRequest(query): 43 | url = API_URL + 'masstransit/getStopInfo?' + query 44 | ref = 'https://yandex.ru/maps/213/moscow/?l=masstransit&mode=masstransit' 45 | return GET(url, headers = {'x-retpath-y': ref, 'referrer': ref, 'cookie': 'yandexuid={}'.format(yUID)}) 46 | 47 | def getClearedData(resp): 48 | global csrftoken 49 | 50 | resp = resp.json() 51 | if not 'data' in resp: 52 | if 'csrftoken' in resp: 53 | csrftoken = resp['csrftoken'] 54 | return False 55 | 56 | del resp['data']['region'] 57 | del resp['data']['breadcrumbs'] 58 | gc.collect() 59 | for n in resp['data']['transports']: 60 | del n['lineId'] 61 | n['name'] = n['seoname'] 62 | del n['seoname'] 63 | del n['Types'] 64 | del n['uri'] 65 | n['times'] = n['threads'][0]['BriefSchedule']['Events'] 66 | del n['threads'] 67 | gc.collect() 68 | return resp 69 | 70 | def getYUIDCookie(url): 71 | import socket 72 | _, _, host, path = url.split('/', 3) 73 | addr = socket.getaddrinfo(host, 443)[0][-1] 74 | s = socket.socket() 75 | s.connect(addr) 76 | s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')) 77 | response = '' 78 | while True: 79 | data = s.recv(100) 80 | if data: 81 | response += str(data, 'utf8') 82 | else: 83 | break 84 | s.close() 85 | rcontent = response.split('\r\n', 1)[1].split('\r\n\r\n') 86 | for h in rcontent[0].split('\r\n'): 87 | hk, hv = h.split(': ', 1) 88 | if hk == 'Set-Cookie': 89 | if hv[:9] == 'yandexuid': 90 | return hv[10:hv.find(';')] 91 | 92 | if not yUID: 93 | yUID = getYUIDCookie(API_URL) 94 | if not csrftoken: 95 | csrftoken = getCSRFToken() 96 | reqObj = formReqObj(csrftoken, stop_id, csrftoken.split(':')[1]+'000_543210') 97 | qstr = formQueryString(reqObj) 98 | reqObj['s'] = str(sign(qstr)) 99 | qstr = formQueryString(reqObj) 100 | return getClearedData(formRequest(qstr)) 101 | 102 | def BSIApp(): 103 | from m5stack import lcd, btnA, btnB, rtc 104 | from machine import Timer 105 | import _thread 106 | 107 | lcd.font(lcd.FONT_Small) 108 | info = False 109 | xCenter = (0, 9, 5, 0, -4, -4) 110 | order = False 111 | mode = 1 112 | 113 | def updateBusDataThreaded(tmr=False): 114 | _thread.start_new_thread(updateBusData, tuple()) 115 | 116 | def updateBusData(): 117 | nonlocal info 118 | info = False 119 | print('updating') 120 | info = getBusStopInfo(BUS_STOP) 121 | drawBusData() 122 | gc.collect() 123 | 124 | def drawBusData(): 125 | if not info: 126 | return 127 | 128 | lcd.clear(0xffffff) 129 | offsetY = 2 130 | offsetX = 35 131 | info['data']['transports'].sort(reverse=order, key= \ 132 | lambda x: int(getBusTimeData(x['times'][0])['value']) if len(x['times']) > 0 else float('inf')) 133 | 134 | for i, t in enumerate(info['data']['transports']): 135 | lcd.fillRoundRect(2, offsetY, 30, 12, 2, getVehicleColor(t['type'])) 136 | lcd.text(6 + xCenter[len(t['name'])], offsetY, t['name'], 0xffffff) 137 | if len(t['times']) > 0: 138 | if mode == 0: 139 | target = getBusTimeData(t['times'][0]) 140 | lcd.text(38, offsetY, target['text'], 0) 141 | else: 142 | currentTime = info['data']['currentTime'] // 1000 + info['data']['tzOffset'] 143 | for k,v in enumerate(t['times']): 144 | target = getBusTimeData(v) 145 | arrivalTime = int(target['value']) + int(target['tzOffset']) 146 | waitTime = str(getMinuteWaitTime(currentTime, arrivalTime)) 147 | lcd.fillRoundRect(1 + offsetX, offsetY, 20, 12, 2, getWaitTimeColor(int(waitTime))) 148 | if len(waitTime) > 2: 149 | waitTime = str(int(waitTime) // 60 + 1) + 'H' 150 | lcd.text(offsetX + xCenter[len(waitTime)], offsetY, waitTime, 0xffffff) 151 | offsetX += 22 152 | if k == 1: 153 | break 154 | offsetX = 35 155 | offsetY += 13 156 | if i == 10: 157 | break 158 | displayTime() 159 | 160 | def getBusTimeData(src): 161 | return src['Estimated' if 'Estimated' in src else 'Scheduled'] 162 | 163 | def getMinuteWaitTime(currentTime, busTime): 164 | return round((busTime - currentTime) / 60) 165 | 166 | def getWaitTimeColor(wt: int) -> int: 167 | return 0x3cb300 * int(wt <= 10) + 0xfce513 * int(wt > 10 and wt <= 16) + 0xfe7613 * (wt > 16 and wt <= 25) + 0xff2812 * int(wt > 25) 168 | 169 | def getVehicleColor(vtype): 170 | return int(vtype == 'bus' or vtype == 'trolleybus') * 0x3377e4 + int(vtype == 'minibus') * 0xb43dcc \ 171 | + int(vtype == 'tramway') * 0xf43000 + int(vtype == 'suburban') * 0x777000 172 | 173 | def displayTimeThreaded(tmr = False): 174 | _thread.start_new_thread(displayTime, tuple()) 175 | 176 | @micropython.native 177 | def displayTime(): 178 | time = rtc.now() 179 | txt = '{:02d}:{:02d}:{:02d}'.format(*time[-3:]) 180 | lcd.textClear(9, 148, txt, 0xFFFFFF) 181 | lcd.text(9, 148, txt, 0) 182 | 183 | syncTime() 184 | updateBusData() 185 | timeTimer = Timer(8005) 186 | updateTimer = Timer(8006) 187 | timeTimer.init(period=1000, mode=Timer.PERIODIC, callback=displayTimeThreaded) 188 | updateTimer.init(period=30000, mode=Timer.PERIODIC, callback=updateBusDataThreaded) 189 | 190 | while True: 191 | if btnA.isPressed(): 192 | order = not order 193 | drawBusData() 194 | while btnA.isPressed(): 195 | pass 196 | if btnB.isPressed(): 197 | mode = not mode 198 | drawBusData() 199 | while btnB.isPressed(): 200 | pass 201 | 202 | def syncTime(): 203 | from m5stack import rtc 204 | from ntptime import client 205 | n = client(host='pool.ntp.org', timezone=3) 206 | n.updateTime() 207 | rtc.setTime(*n.time[:-1]) 208 | del n 209 | 210 | if __name__ == '__main__': 211 | BSIApp() --------------------------------------------------------------------------------