├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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
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 |
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()
--------------------------------------------------------------------------------