├── 0.pbm ├── 1.pbm ├── 2.pbm ├── 3.pbm ├── 4.pbm ├── 5.pbm ├── 6.pbm ├── 7.pbm ├── 8.pbm ├── 9.pbm ├── LICENSE ├── README.md ├── colon.pbm ├── lum ├── 0.pbm ├── 1.pbm └── dim_label.pbm ├── main.py ├── ntp ├── 0.pbm ├── 1.pbm ├── 2.pbm ├── 3.pbm ├── error.pbm ├── main.pbm ├── ntp.pbm ├── ok.pbm └── wait.pbm ├── pymg.py ├── pymg_example.py ├── rotary.py ├── rotary_irp_esp.py ├── wifi ├── error.pbm ├── main.pbm ├── off │ ├── 0.pbm │ ├── 1.pbm │ ├── 2.pbm │ ├── 3.pbm │ ├── 4.pbm │ ├── 5.pbm │ └── 6.pbm ├── ok.pbm ├── on │ ├── 0.pbm │ ├── 1.pbm │ ├── 2.pbm │ ├── 3.pbm │ ├── 4.pbm │ ├── 5.pbm │ └── 6.pbm └── wait.pbm ├── yee ├── k.pbm └── main.pbm └── yeelight.py /0.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/0.pbm -------------------------------------------------------------------------------- /1.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/1.pbm -------------------------------------------------------------------------------- /2.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/2.pbm -------------------------------------------------------------------------------- /3.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/3.pbm -------------------------------------------------------------------------------- /4.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/4.pbm -------------------------------------------------------------------------------- /5.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/5.pbm -------------------------------------------------------------------------------- /6.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/6.pbm -------------------------------------------------------------------------------- /7.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/7.pbm -------------------------------------------------------------------------------- /8.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/8.pbm -------------------------------------------------------------------------------- /9.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/9.pbm -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Reboot93 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VFD-desktop-clock-micropython 2 | Ftutaba 8-MD-06INKM esp32 3 | 4 | ## 演示视频: 5 | https://www.youtube.com/watch?v=FzkWlVCXeCU 6 | 7 | https://www.bilibili.com/video/BV1MM411C7mV 8 | 9 | ## 也许你需要 10 | VFD屏幕驱动程序 https://github.com/Reboot93/MicroPython-8MD-06INKM-display-driver 11 | 12 | 程序中使用的 Button类 文档 https://github.com/Reboot93/simple-Interrupt-button-for-micropython 13 | -------------------------------------------------------------------------------- /colon.pbm: -------------------------------------------------------------------------------- 1 | P4 2 | 5 3 | 7 4 | 6666 -------------------------------------------------------------------------------- /lum/0.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/lum/0.pbm -------------------------------------------------------------------------------- /lum/1.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/lum/1.pbm -------------------------------------------------------------------------------- /lum/dim_label.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/lum/dim_label.pbm -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import framebuf, futaba_8md06inkm 2 | from machine import Pin, freq, SPI 3 | from pymg import * 4 | from pymg_example import * 5 | import time 6 | 7 | freq(240000000) 8 | 9 | hspi = SPI(1, 5000000) 10 | en = Pin(4) # data/command 11 | rst = Pin(5) # reset 12 | cs = Pin(26) # chip select, some modules do not have a pin for this 13 | display = futaba_8md06inkm.VFD(hspi, rst, cs, en) 14 | display.set_display_dimming(127) 15 | 16 | 17 | class MainWindow(Pymg): 18 | 19 | def __init__(self, display, display_info, bt): 20 | super().__init__(display, display_info, 16, fps=False) 21 | self.button = Button(25) 22 | 23 | self.testWindow = RotaryPager(self, (0, 0, 40, 7), scrollSpeed=0.1) 24 | self.button.connect(self.testWindow.buttonCallback) 25 | self.button.setEnable(True) 26 | 27 | 28 | mainWindow = MainWindow(display, (40, 7), 25) 29 | 30 | while True: 31 | try: 32 | mainWindow.start() 33 | except: 34 | print('pymg error') 35 | time.sleep(1) -------------------------------------------------------------------------------- /ntp/0.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/0.pbm -------------------------------------------------------------------------------- /ntp/1.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/1.pbm -------------------------------------------------------------------------------- /ntp/2.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/2.pbm -------------------------------------------------------------------------------- /ntp/3.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/3.pbm -------------------------------------------------------------------------------- /ntp/error.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/error.pbm -------------------------------------------------------------------------------- /ntp/main.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/main.pbm -------------------------------------------------------------------------------- /ntp/ntp.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/ntp.pbm -------------------------------------------------------------------------------- /ntp/ok.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/ok.pbm -------------------------------------------------------------------------------- /ntp/wait.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/ntp/wait.pbm -------------------------------------------------------------------------------- /pymg.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, Timer 2 | import time, framebuf, network 3 | from math import ceil 4 | from rotary_irp_esp import RotaryIRQ 5 | 6 | 7 | # 帧缓冲局部反转 8 | def framebuf_inversion(original_buf, x, y, w, h): 9 | if h < 8: 10 | h = 8 11 | buf = bytearray((h // 8) * w) 12 | fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.MONO_VLSB) 13 | fbuf.blit(original_buf, 0, 0, -1) 14 | for i in range(0, len(buf)): 15 | buf[i] = ~buf[i] 16 | original_buf.blit(fbuf, int(x), int(y), -1) 17 | 18 | 19 | class Pbm: 20 | w = None 21 | h = None 22 | pbm = None 23 | filename = None 24 | 25 | def __init__(self, file): 26 | self.filename = file 27 | self.loadPBM(file) 28 | 29 | def loadPBM(self, file): 30 | with open(file, 'rb') as f: 31 | f.readline() 32 | self.w = int(f.readline()) 33 | self.h = int(f.readline()) 34 | self.pbm = bytearray(f.read()) 35 | f.close() 36 | 37 | def pbmPrint(self) -> framebuf: 38 | return framebuf.FrameBuffer(self.pbm, self.w, self.h, framebuf.MONO_HLSB) 39 | 40 | def zoom(self, w, h): 41 | Image = self.pbmPrint() 42 | buffer = bytearray((w * h) // 8) 43 | newImage = framebuf.FrameBuffer(buffer, w, h, framebuf.MONO_HLSB) 44 | alpha_w = w / self.w 45 | alpha_h = h / self.h 46 | for x in range(0, w): 47 | for y in range(0, h): 48 | resX = round(x / alpha_w) 49 | resY = round(y / alpha_h) 50 | newImage.pixel(x, y, Image.pixel(resX, resY)) 51 | return newImage, w, h 52 | 53 | 54 | class PbmManager: 55 | 56 | def __init__(self): 57 | self.pbms = {} 58 | 59 | def get_Pbm(self, filename): 60 | if filename in self.pbms: 61 | return self.pbms[filename] 62 | else: 63 | print('PBM: %s not load |get' % filename) 64 | return None 65 | 66 | def add_pbm(self, pbm: Pbm): 67 | self.pbms[pbm.filename] = pbm 68 | 69 | def del_pbm(self, filename): 70 | try: 71 | del self.pbms[filename] 72 | except: 73 | print('PBM: %s not load |del' % filename) 74 | 75 | 76 | class Pymg: 77 | 78 | def __init__(self, display, display_info, refresh_interval=30, fps=False): 79 | self.display = display 80 | self.display_info = display_info 81 | self.refresh_interval = refresh_interval 82 | self._fps = fps 83 | self.fps = 0 84 | self.wlan = network.WLAN(network.STA_IF) 85 | self.wlan.active(False) 86 | self.windows = [] 87 | self.showWindows = [] 88 | self.last_show_time = time.ticks_ms() 89 | 90 | ''' 91 | window : 窗口对象 92 | insert : 插入位置 93 | loc : 顶部或底部''' 94 | 95 | def widgetAdd(self, window, insert=None, loc='Top'): 96 | if insert == None: 97 | if loc == "Top": 98 | self.windows.append(window) 99 | else: 100 | self.windows.insert(0, window) 101 | else: 102 | self.windows.insert(insert, window) 103 | 104 | # 移除窗口 105 | def widgetDel(self, window): 106 | self.windows.remove(window) 107 | 108 | # 获取传入窗口下标 109 | def getWidgetIndex(self, window): 110 | return self.windows.index(window) 111 | 112 | # 获取需要显示的窗口 113 | def getShowWindows(self): 114 | self.showWindows.clear() 115 | for window in self.windows: 116 | if not window.hidden: 117 | self.showWindows.append(window) 118 | 119 | def getDisplay(self): 120 | return self.display 121 | 122 | def getWlan(self): 123 | return self.wlan 124 | 125 | # 更新窗口 126 | def window_update(self): 127 | # if not self.now_show: 128 | # self.getShowWindows() 129 | # 遍历 当前显示的窗口, 刷新 130 | # for window in self.showWindows: 131 | # window.gui_update() 132 | for window in self.windows: 133 | if not window.hidden: 134 | window.gui_update() 135 | 136 | def show(self): 137 | self.display.fill(0) 138 | for window in self.showWindows: 139 | res = window.gui_show() 140 | self.display.blit(res[0], res[1], res[2], res[3]) 141 | if self._fps: 142 | self.display.fill_rect(0, 0, 24, 8, 0) 143 | self.display.text(str(self.fps), 0, 0) 144 | self.display.show() 145 | 146 | def start(self): 147 | while True: 148 | self.window_update() 149 | now_time = time.ticks_ms() 150 | interval = time.ticks_diff(now_time, self.last_show_time) 151 | if interval >= self.refresh_interval: 152 | self.getShowWindows() 153 | self.last_show_time = now_time 154 | if self._fps: 155 | self.fps = int(1000 / interval) 156 | self.show() 157 | 158 | 159 | class Button: 160 | 161 | def __init__(self, pin, single_click_time=130, long_press_time=210, pull=Pin.PULL_UP, trigger=Pin.IRQ_FALLING): 162 | self.pin = pin 163 | self.callback = None 164 | self.isEnable = False 165 | self._single_click_time = single_click_time 166 | self._long_press_time = long_press_time 167 | self._timer_count = 0 168 | self.button = Pin(pin, Pin.IN, pull) 169 | self.trigger = trigger 170 | self._long_press_timer = Timer(0) 171 | 172 | def setEnable(self, flag: bool): 173 | if flag: 174 | self.button.irq(handler=self._irq_callback, trigger=self.trigger) 175 | self.isEnable = True 176 | else: 177 | self.button.irq(handler=None) 178 | self.isEnable = False 179 | 180 | def connect(self, fun): 181 | self.callback = fun 182 | 183 | def _irq_callback(self, pin): 184 | self.setEnable(False) 185 | self.click_flag = False 186 | self.long_press_flag = False 187 | self._long_press_timer.init(period=2, callback=self._timer_irp_callback) 188 | 189 | def _timer_irp_callback(self, msg): 190 | if self.button.value() != 0 or self._timer_count > self._long_press_time + 10: 191 | print(self._timer_count) 192 | if 5 < self._timer_count < self._single_click_time: 193 | self._long_press_timer.deinit() 194 | print('Button %s clicked' % str(self.pin)) 195 | self.callback(self.pin, 0) 196 | elif self._timer_count > self._long_press_time: 197 | self._long_press_timer.deinit() 198 | print('Button %s long pressed' % str(self.pin)) 199 | self.callback(self.pin, 1) 200 | else: 201 | self._long_press_timer.deinit() 202 | self._timer_count = 0 203 | self.setEnable(True) 204 | else: 205 | self._timer_count += 1 206 | 207 | 208 | class Signal: 209 | 210 | def __init__(self, msg=None): 211 | self._signal_msg = msg 212 | self._callback = None 213 | 214 | def connect(self, fun): 215 | self._callback = fun 216 | 217 | def emit(self, msg): 218 | self._callback(msg) 219 | 220 | def emit(self): 221 | self._callback() 222 | 223 | 224 | class Widget: 225 | 226 | def __init__(self, parant, window_info, insert=None, loc="Top", brackGround=-1): 227 | self.x, self.y, self.w, self.h = window_info 228 | self.parant = parant 229 | self.parant.widgetAdd(self, insert, loc) 230 | self.hidden = False 231 | self.isCheckable = False 232 | self.brackGround = brackGround 233 | self.buffer = framebuf.FrameBuffer(bytearray((self.h * self.w) // 8), self.w, self.h, framebuf.MONO_HMSB) 234 | 235 | # 接受 更新信号 并处理 236 | def gui_update(self): 237 | pass 238 | 239 | def focus(self, msg): 240 | pass 241 | 242 | def getWlan(self): 243 | return self.parant.wlan 244 | 245 | def getDisplay(self): 246 | return self.parant.getDisplay() 247 | 248 | def buttonCallback(self, pin, msg): 249 | if msg == 0: 250 | print('%s : button callback not set | Pin call : %s' % self, pin) 251 | elif msg == 1: 252 | print('%s : button long press not set | Pin call : %s' % self, pin) 253 | 254 | # 接受 画面渲染信号 并处理 255 | def gui_show(self) -> (framebuf, int, int, int): 256 | return (self.buffer, self.x, self.y, self.brackGround) 257 | 258 | 259 | class Window(Widget): 260 | 261 | def __init__(self, parant, window_info, insert=None, loc="Top", brackGround=-1): 262 | super().__init__(parant, window_info, insert, loc, brackGround) 263 | self.widgets = [] 264 | 265 | def widgetAdd(self, window, insert=None, loc='Top'): 266 | if insert == None: 267 | if loc == "Top": 268 | self.widgets.append(window) 269 | else: 270 | self.widgets.insert(0, window) 271 | else: 272 | self.widgets.insert(insert, window) 273 | 274 | # 移除窗口 275 | def widgetDel(self, window): 276 | self.widgets.remove(window) 277 | 278 | # 获取传入窗口下标 279 | def getWidgetIndex(self, window): 280 | return self.widgets.index(window) 281 | 282 | # 接受 更新信号 并处理 283 | def gui_update(self): 284 | # 更新自身及子窗口状态 285 | self.update() 286 | widgets = self.widgets 287 | if len(widgets) > 0: 288 | for widget in widgets: 289 | if not widget.hidden: 290 | widget.gui_update() 291 | 292 | def buttonCallback(self, pin, msg): 293 | if msg == 0: 294 | print('%s : button callback not set | Pin call : %s' % self, pin) 295 | elif msg == 1: 296 | print('%s : button long press not set | Pin call : %s' % self, pin) 297 | 298 | # 更新自身窗口状态 299 | def update(self): 300 | pass 301 | # 需要重构 302 | 303 | # 接受 画面渲染信号 并处理 304 | def gui_show(self): 305 | self.buffer.fill(0) 306 | self.show() 307 | widgets = self.widgets 308 | if len(widgets) > 0: 309 | for widget in widgets: 310 | if not widget.hidden: 311 | res = widget.gui_show() 312 | self.buffer.blit(res[0], res[1], res[2], res[3]) 313 | return (self.buffer, self.x, self.y, self.brackGround) 314 | 315 | def show(self): 316 | pass 317 | 318 | 319 | class Rotary: 320 | MODE_UNBOUNDED = RotaryIRQ.RANGE_UNBOUNDED 321 | MODE_BOUNDED = RotaryIRQ.RANGE_BOUNDED 322 | MODE_WRAP = RotaryIRQ.RANGE_WRAP 323 | 324 | def __init__(self, clk, dt, mode=MODE_UNBOUNDED, min=0): 325 | self.dt = dt 326 | self.clk = clk 327 | self.min = min 328 | self.mode = mode 329 | self.rotary = RotaryIRQ(pin_num_clk=self.clk, 330 | pin_num_dt=self.dt, 331 | min_val=self.min, 332 | reverse=False, 333 | half_step=True, 334 | range_mode=self.mode) 335 | self.setEnable(False) 336 | self.callback = None 337 | 338 | def init(self): 339 | self.rotary = RotaryIRQ(pin_num_clk=self.clk, 340 | pin_num_dt=self.dt, 341 | min_val=self.min, 342 | reverse=False, 343 | half_step=True, 344 | range_mode=self.mode) 345 | 346 | def setEnable(self, flag: bool): 347 | if flag: 348 | self.rotary.enable() 349 | else: 350 | self.rotary.close() 351 | 352 | def setIncr(self, count: int): 353 | self.rotary.setIncr(count) 354 | 355 | def setListener(self, fun): 356 | self.rotary.add_listener(fun) 357 | 358 | def delListener(self, fun): 359 | self.rotary.remove_listener(fun) 360 | 361 | def setValue(self, value: int): 362 | self.rotary.setValue(value) 363 | 364 | def setValueMax(self, value: int): 365 | self.rotary.setValueMax(value) 366 | 367 | def setValueMin(self, value: int): 368 | self.rotary.setValueMin(value) 369 | 370 | def setRangeMode(self, mode): 371 | if mode == 'MODE_UNBOUNDED': 372 | self.rotary.setRangeMode(self.MODE_UNBOUNDED) 373 | elif mode == 'MODE_BOUNDED': 374 | self.rotary.setRangeMode(self.MODE_BOUNDED) 375 | elif mode == 'MODE_WRAP': 376 | self.rotary.setRangeMode(self.MODE_WRAP) 377 | else: 378 | print('mode error') 379 | raise Exception 380 | 381 | def value(self): 382 | return self.rotary.value() 383 | 384 | 385 | class ViewPager(Window): 386 | 387 | def __init__(self, parant, window_info, scrollSpeed=0.1, back=2, back_count=100, insert=None, loc="Top", 388 | brackGround=-1, refresh_interval=5): 389 | super().__init__(parant, window_info, insert, loc, brackGround) 390 | self.widgetsChecked = 0 391 | self.switch_siganl = Signal() 392 | self.scrollCount = 0 393 | self.gap = 2 394 | self.scrollSpeed = scrollSpeed 395 | self.back = back 396 | self.back_count = back_count 397 | self.scroll_flag = None # left right back_left back_right 398 | self.checkable = True 399 | self.isCheckable = True 400 | self.buttonPassthrough = False 401 | self._back_scroll_count = 0 402 | self._back_scroll_flag = None 403 | self._last_time = time.ticks_ms() 404 | self.refresh_interval = refresh_interval 405 | 406 | self.switch_siganl.connect(self.switch_widget) 407 | 408 | def switch_widget(self): 409 | self.buttonPassthrough = False 410 | 411 | def back_home(self): 412 | left = len(self.widgets[self.widgetsChecked:]) 413 | right = self.widgetsChecked 414 | if left > right: 415 | self._back_scroll_count = right 416 | self._back_scroll_flag = 'right' 417 | else: 418 | self._back_scroll_count = left 419 | self._back_scroll_flag = 'left' 420 | 421 | def setWidget(self, flag: int): 422 | count = flag - self.widgetsChecked 423 | print('set widget', count) 424 | if count < 0: 425 | self._back_scroll_count = count 426 | self._back_scroll_flag = 'right' 427 | else: 428 | self._back_scroll_count = count 429 | self._back_scroll_flag = 'left' 430 | 431 | def buttonCallback(self, pin, msg): 432 | if msg == 0: 433 | print('viewpager button clicked', self.widgetsChecked, self.widgets) 434 | elif msg == 1: 435 | print('viewpager button long pressed', self.widgetsChecked, self.widgets) 436 | if self.buttonPassthrough: 437 | print('Passthrough button to', self.widgets[self.widgetsChecked]) 438 | self.widgets[self.widgetsChecked].buttonCallback(pin, msg) 439 | else: 440 | if self.widgets[self.widgetsChecked].isCheckable: 441 | self.buttonPassthrough = True 442 | self.widgets[self.widgetsChecked].focus(msg) 443 | else: 444 | print('window: %s is not Checkable' % self.widgets[self.widgetsChecked]) 445 | 446 | def gui_update(self): 447 | now_time = time.ticks_ms() 448 | if time.ticks_diff(now_time, self._last_time) > self.refresh_interval: 449 | self.update() 450 | self._last_time = now_time 451 | widgets = self.widgets 452 | if self.scroll_flag != None: 453 | widgets = [widgets[(self.widgetsChecked - 1) % len(widgets)], 454 | widgets[self.widgetsChecked], 455 | widgets[(self.widgetsChecked + 1) % len(widgets)]] 456 | for widget in widgets: 457 | widget.gui_update() 458 | else: 459 | widget = widgets[self.widgetsChecked] 460 | widget.gui_update() 461 | 462 | def update(self): 463 | self._scroll() 464 | 465 | def _scroll(self): 466 | pass 467 | 468 | def _get_show_list(self): 469 | pass 470 | 471 | def gui_show(self): 472 | self.buffer.fill(0) 473 | widgets = self.widgets 474 | count = -self.w 475 | if self.scroll_flag != None: 476 | widgets = [widgets[(self.widgetsChecked - 1) % len(widgets)], 477 | widgets[self.widgetsChecked], 478 | widgets[(self.widgetsChecked + 1) % len(widgets)]] 479 | for widget in widgets: 480 | if not widget.hidden: 481 | res = widget.gui_show() 482 | self.buffer.blit(res[0], ceil(count + self.scrollCount), 0, res[3]) 483 | count += self.w 484 | else: 485 | widget = widgets[self.widgetsChecked] 486 | res = widget.gui_show() 487 | self.buffer.blit(res[0], 0, 0, res[3]) 488 | return (self.buffer, self.x, self.y, self.brackGround) 489 | 490 | 491 | class Lable(Widget): 492 | 493 | def __init__(self, parant, widget_info, scrollSpeed=1, insert=None, loc="Top", brackGround=-1, refresh_interval=10): 494 | super().__init__(parant, widget_info, insert, loc, brackGround) 495 | self.scroll_count = 0 496 | self.scroll_flag = False 497 | self.refresh_interval = refresh_interval 498 | self.last_time = time.ticks_ms() 499 | self.pbm = None 500 | self.scrollSpeed = scrollSpeed 501 | 502 | def gui_update(self): 503 | now_time = time.ticks_ms() 504 | if time.ticks_diff(now_time, self.last_time) >= self.refresh_interval: 505 | if self.scroll_flag: 506 | if self.scroll_count > 0: 507 | self.scroll_count -= self.scrollSpeed 508 | else: 509 | self.scroll_count = self.pbm.w 510 | self.last_time = now_time 511 | 512 | def setPbm(self, pbm: Pbm): 513 | self.pbm = pbm 514 | if self.pbm.w > self.w: 515 | self.scroll_flag = True 516 | self.scroll_count = self.pbm.w 517 | else: 518 | self.buffer.fill(0) 519 | self.scroll_flag = False 520 | self.scroll_count = 0 521 | self.buffer.blit(self.pbm.pbmPrint(), round((self.w - self.pbm.w) / 2), 0, self.brackGround) 522 | 523 | def gui_show(self) -> (framebuf, int, int, int): 524 | if self.scroll_flag: 525 | self.buffer.fill(0) 526 | if self.scroll_count != 0: 527 | self.buffer.blit(self.pbm.pbmPrint(), round(-self.pbm.w + self.scroll_count), 0, self.brackGround) 528 | self.buffer.blit(self.pbm.pbmPrint(), round(self.scroll_count), 0, self.brackGround) 529 | else: 530 | self.buffer.blit(self.pbm.pbmPrint(), 0, 0, self.brackGround) 531 | 532 | return (self.buffer, self.x, self.y, self.brackGround) 533 | 534 | 535 | class Animation(Widget): 536 | 537 | def __init__(self, parant, widget_info, duration=30, insert=None, loc="Top", brackGround=-1): 538 | super().__init__(parant, widget_info, insert, loc, brackGround) 539 | self._pbmManager = None 540 | self._duration = duration 541 | self.dir = '' 542 | self._last_time = time.ticks_ms() 543 | self._pbmListRange = [0, 0] 544 | self._pbmCount = 0 545 | 546 | def setPbmManager(self, pbmManager): 547 | self._pbmManager = pbmManager 548 | 549 | def setRange(self, msg: list): 550 | self._pbmListRange = msg 551 | 552 | def gui_update(self): 553 | now_time = time.ticks_ms() 554 | if time.ticks_diff(now_time, self._last_time) > self._duration: 555 | self.buffer.fill(0) 556 | self._last_time = now_time 557 | if self._pbmCount == self._pbmListRange[1]: 558 | self._pbmCount = 0 559 | else: 560 | self._pbmCount += 1 561 | self.buffer.blit(self._pbmManager.get_Pbm('%s/%s.pbm' % (self.dir, str(self._pbmCount))).pbmPrint(), 562 | 0, 563 | 0, 564 | self.brackGround) 565 | else: 566 | pass 567 | 568 | def gui_show(self) -> (framebuf, int, int, int): 569 | return (self.buffer, self.x, self.y, self.brackGround) 570 | 571 | 572 | class ScreenButton(Animation): 573 | 574 | def __init__(self, parant, widget_info, duration=30, insert=None, loc="Top", brackGround=-1): 575 | super().__init__(parant, widget_info, insert, loc, brackGround) 576 | self._pbmManager = None 577 | self._duration = duration 578 | self.dir = '' 579 | self.state_dir = '/off' 580 | self._last_time = time.ticks_ms() 581 | self._pbmListRange = [0, 0] 582 | self._pbmCount = 0 583 | self.state = False 584 | self.play_flag = False 585 | 586 | def setPbmManager(self, pbmManager): 587 | self._pbmManager = pbmManager 588 | 589 | def setRange(self, msg: list): 590 | self._pbmListRange = msg 591 | 592 | def setState(self, flag: bool): 593 | if flag != self.state: 594 | self.state = flag 595 | self._change() 596 | 597 | def _change(self): 598 | self.play_flag = True 599 | self._pbmCount = 0 600 | if self.state: 601 | self.state_dir = '/on' 602 | else: 603 | self.state_dir = '/off' 604 | 605 | def gui_update(self): 606 | if self.play_flag: 607 | now_time = time.ticks_ms() 608 | if time.ticks_diff(now_time, self._last_time) > self._duration: 609 | self.buffer.fill(0) 610 | self._last_time = now_time 611 | if self._pbmCount == self._pbmListRange[1]: 612 | self.play_flag = False 613 | else: 614 | self._pbmCount += 1 615 | self.buffer.blit(self._pbmManager.get_Pbm( 616 | '%s%s/%s.pbm' % (self.dir, self.state_dir, str(self._pbmCount))).pbmPrint(), 617 | 0, 618 | 0, 619 | self.brackGround) 620 | else: 621 | pass 622 | else: 623 | self.buffer.blit( 624 | self._pbmManager.get_Pbm( 625 | '%s%s/%s.pbm' % (self.dir, self.state_dir, str(self._pbmListRange[1]))).pbmPrint(), 626 | 0, 627 | 0, 628 | self.brackGround) 629 | 630 | def gui_show(self) -> (framebuf, int, int, int): 631 | return (self.buffer, self.x, self.y, self.brackGround) 632 | 633 | 634 | class Number(Widget): 635 | 636 | def __init__(self, parant, widget_info, pbmManager, scrollSpeed=1, numberList=False, insert=None, loc="Top", 637 | brackGround=-1, refresh_interval=16, upper_limit_number=9): 638 | super().__init__(parant, widget_info, insert, loc, brackGround) 639 | self.value = 0 640 | self.value_old = None 641 | self.scroll_flag = False 642 | self.scroll_count = 0 643 | # 上限数字 644 | self.upper_limit_number = upper_limit_number 645 | 646 | self.refresh_interval = refresh_interval 647 | self.last_time = time.ticks_ms() 648 | self.pbmManager = None 649 | self.switchDirection = 0 650 | self.pbmManager = pbmManager 651 | self.numberList = numberList 652 | self.list = [] 653 | self.scrollSpeed = scrollSpeed 654 | self.scroll_list_flag = False 655 | 656 | def setUpperLimitNumber(self, value: int): 657 | self.upper_limit_number = value 658 | 659 | def setValue(self, value) -> int: 660 | if value != self.value or self.scroll_list_flag: 661 | if not self.scroll_flag and not self.scroll_list_flag: 662 | self._scroll(value) 663 | return 1 664 | else: 665 | if self.numberList: 666 | if len(self.list) > 5: 667 | self.list = self.list[-5:] 668 | if len(self.list) > 0: 669 | if value != self.list[-1]: 670 | self.list.append(value) 671 | else: 672 | self.scroll_list_flag = True 673 | self.list.append(value) 674 | print('now scrolling, add %s in list' % value) 675 | return 1 676 | else: 677 | print('now scrolling', str(self.value)) 678 | return 0 679 | 680 | def _scroll(self, value): 681 | self.scroll_flag = True 682 | if self.value != self.upper_limit_number and self.value != 0: 683 | if value < self.value: 684 | self.switchDirection = 1 685 | else: 686 | self.switchDirection = 0 687 | else: 688 | if self.value == self.upper_limit_number: 689 | if value != 0: 690 | self.switchDirection = 1 691 | else: 692 | self.switchDirection = 0 693 | elif value == self.upper_limit_number: 694 | if self.value != 0: 695 | self.switchDirection = 0 696 | else: 697 | self.switchDirection = 1 698 | if self.switchDirection == 0 or self.switchDirection == 1: 699 | self.scroll_count = self.h + 1 700 | else: 701 | self.scroll_count = self.w + 1 702 | self.value_old = self.value 703 | self.value = value 704 | 705 | def getValue(self) -> int: 706 | return self.value 707 | 708 | def gui_update(self): 709 | now_time = time.ticks_ms() 710 | if time.ticks_diff(now_time, self.last_time) > self.refresh_interval: 711 | if self.scroll_flag: 712 | if self.scroll_count > 0: 713 | if len(self.list) > 0: 714 | scrollSpeed = self.scrollSpeed * len(self.list) * 2 715 | if scrollSpeed > self.h: 716 | scrollSpeed = self.h 717 | else: 718 | scrollSpeed = self.scrollSpeed 719 | self.scroll_count -= scrollSpeed 720 | if self.scroll_count - 0 < scrollSpeed: 721 | self.scroll_count = 0 722 | else: 723 | self.scroll_flag = False 724 | if self.scroll_list_flag: 725 | if len(self.list) == 0: 726 | self.scroll_list_flag = False 727 | else: 728 | if len(self.list) > 0: 729 | self._scroll(self.list.pop(0)) 730 | self.last_time = now_time 731 | 732 | def gui_show(self) -> (framebuf, int, int, int): 733 | self.buffer.fill(0) 734 | if self.scroll_flag: 735 | if self.switchDirection == 0: # down 736 | self.buffer.blit(self.pbmManager.get_Pbm('%s.pbm' % self.value).pbmPrint(), 737 | 0, 738 | round(0 - self.scroll_count), 739 | self.brackGround) 740 | self.buffer.blit(self.pbmManager.get_Pbm('%s.pbm' % self.value_old).pbmPrint(), 741 | 0, 742 | round(0 - self.scroll_count + self.h + 1), 743 | self.brackGround) 744 | elif self.switchDirection == 1: # up 745 | self.buffer.blit(self.pbmManager.get_Pbm('%s.pbm' % self.value).pbmPrint(), 746 | 0, 747 | round(self.scroll_count), 748 | self.brackGround) 749 | self.buffer.blit(self.pbmManager.get_Pbm('%s.pbm' % self.value_old).pbmPrint(), 750 | 0, 751 | round(-self.h + self.scroll_count - 1), 752 | self.brackGround) 753 | else: 754 | self.buffer.blit(self.pbmManager.get_Pbm('%s.pbm' % self.value).pbmPrint(), 755 | 0, 756 | 0, 757 | self.brackGround) 758 | return (self.buffer, self.x, self.y, self.brackGround) 759 | 760 | 761 | class NumberGroup(Window): 762 | 763 | # first_digit_upper_limit 只有在 到达上限后归0 时需要手动给定,例如 显示分钟,错误的值 将导致 切换动画反向 764 | def __init__(self, parant, window_info, scrollSpeed=1, insert=None, loc="Top", brackGround=-1, 765 | first_digit_upper_limit=9): 766 | super().__init__(parant, window_info, insert, loc, brackGround) 767 | self.show_X = 0 768 | self.pbmManager = None 769 | self.scrollSpeed = scrollSpeed 770 | self._first_digit_upper_limit = first_digit_upper_limit 771 | self.value = '0' 772 | self.digit = 1 773 | self.numberList = [] 774 | 775 | def init(self): 776 | self.numberList = [] 777 | for i in range(0, self.digit): 778 | if i == self.digit - 1: 779 | self._add_num(self._first_digit_upper_limit) 780 | else: 781 | self._add_num(9) 782 | self._update_show_X() 783 | 784 | def setPbmManager(self, pbmManager): 785 | self.pbmManager = pbmManager 786 | 787 | def setDigit(self, digit: int): 788 | self.digit = digit 789 | self.init() 790 | 791 | def getValue(self): 792 | return int(self.value) 793 | 794 | def _update_show_X(self): 795 | x = self.show_X 796 | for num in self.numberList: 797 | num.x = x 798 | x += 5 799 | for i in self.numberList: 800 | print(self.parant, i, i.x) 801 | 802 | def setValue(self, value: str): 803 | if self.value != value: 804 | count_new = len(value) 805 | count_old = len(self.value) 806 | self.value = value 807 | # 匹配位数 808 | if count_new < self.digit: 809 | for num in range(0, self.digit - count_new): 810 | self.numberList[num].setValue(0) 811 | for num in value: 812 | self.numberList[-count_new].setValue(int(num)) 813 | count_new -= 1 814 | 815 | def _del_num(self): 816 | del self.numberList[0] 817 | 818 | def _add_num(self, value): 819 | self.numberList.insert(0, Number(self, (0, 0, 5, 7), self.pbmManager, self.scrollSpeed, True, 820 | upper_limit_number=value)) 821 | 822 | 823 | class RotaryViewPager(ViewPager): 824 | 825 | def __init__(self, parant, window_info, scrollSpeed=0.1, back=2, back_count=100, insert=None, loc="Top", 826 | brackGround=-1, refresh_interval=5, rotary_pin=(21, 22)): 827 | super().__init__(parant, window_info, scrollSpeed, back, back_count, insert, loc, brackGround, refresh_interval) 828 | self.rotary = Rotary(rotary_pin[0], rotary_pin[1]) 829 | print(self, self.rotary) 830 | self.rotary_value_old = 0 831 | self.rotary_value_count = 0 832 | self.pbmManager = PbmManager() 833 | 834 | def switch_widget(self): 835 | self.buttonPassthrough = False 836 | self.rotary.setEnable(True) 837 | 838 | def buttonCallback(self, pin, msg): 839 | if msg == 0: 840 | print('viewpager button', self.widgetsChecked, self.widgets) 841 | elif msg == 1: 842 | print('viewpager button long pressed', self.widgetsChecked, self.widgets) 843 | if self.buttonPassthrough: 844 | print('Passthrough button to', self.widgets[self.widgetsChecked]) 845 | self.widgets[self.widgetsChecked].buttonCallback(pin, msg) 846 | else: 847 | if self.widgets[self.widgetsChecked].isCheckable: 848 | self.buttonPassthrough = True 849 | self.rotary.setEnable(False) 850 | self.widgets[self.widgetsChecked].focus(msg) 851 | else: 852 | print('window: %s is not Checkable' % self.widgets[self.widgetsChecked]) 853 | 854 | def update(self): 855 | rotary_value = self.rotary.value() 856 | rotary_value_old = self.rotary_value_old 857 | if self._back_scroll_count == 0: 858 | if self.scroll_flag == None and rotary_value != 0: 859 | self.scroll_flag = 'mv' 860 | if self.scroll_flag == 'mv': 861 | if rotary_value < self.back and rotary_value > -self.back: 862 | self.scrollCount = rotary_value * 2 863 | if rotary_value == rotary_value_old: 864 | self.rotary_value_count += 1 865 | elif rotary_value < rotary_value_old: 866 | self.rotary_value_count -= 1 867 | self.rotary_value_old = rotary_value 868 | if self.rotary_value_count > self.back_count: 869 | self.checkable = False 870 | self.rotary_value_count = 0 871 | if rotary_value > 0: 872 | self.scroll_flag = 'back_left' 873 | else: 874 | self.scroll_flag = 'back_right' 875 | else: 876 | if rotary_value > 0: 877 | self.scroll_flag = 'right' 878 | else: 879 | self.scroll_flag = 'left' 880 | else: 881 | self._scroll() 882 | else: 883 | self.scroll_flag = self._back_scroll_flag 884 | self._scroll() 885 | 886 | def _scroll(self): 887 | if self.scroll_flag == 'right': 888 | self.checkable = False 889 | if self.scrollCount < self.w: 890 | count = (self.w - self.scrollCount) * self.scrollSpeed 891 | if count < 0.7: 892 | count = 0.7 893 | if self.scrollCount + count >= self.w: 894 | self.scrollCount = self.w 895 | else: 896 | self.scrollCount += count 897 | else: 898 | self.scrollCount = 0 899 | self.rotary_value_count = 0 900 | self.scroll_flag = None 901 | self.widgetsChecked = (self.widgetsChecked - 1) % len(self.widgets) 902 | if self._back_scroll_count > 0: 903 | self._back_scroll_count -= 1 904 | else: 905 | self.checkable = True 906 | self.rotary.setValue(0) 907 | elif self.scroll_flag == 'left': 908 | self.checkable = False 909 | if self.scrollCount > -self.w: 910 | count = abs(self.w + self.scrollCount) * self.scrollSpeed 911 | if count < 0.7: 912 | count = 0.7 913 | if self.scrollCount - count <= -self.w: 914 | self.scrollCount = -self.w 915 | else: 916 | self.scrollCount -= count 917 | else: 918 | self.scrollCount = 0 919 | self.rotary_value_count = 0 920 | self.scroll_flag = None 921 | self.widgetsChecked = (self.widgetsChecked + 1) % len(self.widgets) 922 | if self._back_scroll_count > 0: 923 | self._back_scroll_count -= 1 924 | else: 925 | self.checkable = True 926 | self.rotary.setValue(0) 927 | elif self.scroll_flag == 'back_right': 928 | self.checkable = False 929 | if self.scrollCount < 0: 930 | self.scrollCount += ceil((-self.scrollCount + 1) * self.scrollSpeed) 931 | else: 932 | self.scrollCount = 0 933 | self.rotary_value_count = 0 934 | self.scroll_flag = None 935 | self.checkable = True 936 | self.rotary.setValue(0) 937 | elif self.scroll_flag == 'back_left': 938 | self.checkable = False 939 | if self.scrollCount > 0: 940 | self.scrollCount -= ceil(self.scrollCount * self.scrollSpeed) 941 | else: 942 | self.scrollCount = 0 943 | self.rotary_value_count = 0 944 | self.scroll_flag = None 945 | self.checkable = True 946 | self.rotary.setValue(0) -------------------------------------------------------------------------------- /pymg_example.py: -------------------------------------------------------------------------------- 1 | from pymg import * 2 | import ntptime 3 | from machine import RTC 4 | import yeelight, json 5 | 6 | 7 | class RotaryPager(RotaryViewPager): 8 | 9 | def __init__(self, parant, window_info, scrollSpeed=0.1, back=2, back_count=100, insert=None, loc="Top"): 10 | super().__init__(parant, window_info, scrollSpeed, back, back_count, insert, loc) 11 | for i in range(0, 10): 12 | self.pbmManager.add_pbm(Pbm('%s.pbm' % i)) 13 | self.pbmManager.add_pbm(Pbm('colon.pbm')) 14 | 15 | self.window_1 = TimeWindow(self, (0, 0, 40, 7)) 16 | self.window_3 = SetBrightness(self, (0, 0, 40, 7)) 17 | self.window_4 = YeelightView(self, (0, 0, 40, 7), scrollSpeed=0.1) 18 | self.window_2 = NtpWindow(self, (0, 0, 40, 7)) 19 | self.window_5 = WifiWindow(self, (0, 0, 40, 7)) 20 | self.window_1.setPbmManager(self.pbmManager) 21 | self.window_2.setPbmManager(self.pbmManager) 22 | self.window_3.setPbmManager(self.pbmManager) 23 | self.window_4.setPbmManager(self.pbmManager) 24 | self.window_5.setPbmManager(self.pbmManager) 25 | 26 | self.rotary.setEnable(True) 27 | 28 | def to_set_wifi(self): 29 | count = abs(self.widgets.index(self.window_5) - self.widgetsChecked) 30 | print('set_wifi', count) 31 | if self.widgets.index(self.window_5) < self.widgetsChecked: 32 | self._back_scroll_count = count 33 | self._back_scroll_flag = 'right' 34 | else: 35 | self._back_scroll_count = count 36 | self._back_scroll_flag = 'left' 37 | 38 | 39 | class TimeWindow(Window): 40 | 41 | def __init__(self, parant, window_info, scrollSpeed=1, insert=None, loc="Top", flicker_interval=10, rotary_pin=(21, 22)): 42 | super().__init__(parant, window_info, insert, loc) 43 | self.pbmManager = None 44 | self._scrollSpeed = scrollSpeed 45 | self.flicker_interval = flicker_interval 46 | self.rtc = RTC() 47 | self._time = self._get_time() 48 | self._last_time = time.ticks_ms() 49 | self._numberList = [] 50 | self._secCount = 0 51 | self._setMode = False 52 | self.isCheckable = True 53 | self._setCount = 0 54 | self.rotary = Rotary(rotary_pin[0], rotary_pin[1]) 55 | self.rotary.setEnable(False) 56 | self.rotary.setRangeMode('MODE_WRAP') 57 | self.rotary.setValueMin(0) 58 | self.rotary.setValueMax(23) 59 | self.value_old = 0 60 | 61 | def setPbmManager(self, pbmManager): 62 | self.pbmManager = pbmManager 63 | self._numberList = [NumberGroup(self, (0, 0, 10, 7), self._scrollSpeed, first_digit_upper_limit=2), 64 | NumberGroup(self, (15, 0, 10, 7), self._scrollSpeed, first_digit_upper_limit=5), 65 | NumberGroup(self, (30, 0, 10, 7), self._scrollSpeed, first_digit_upper_limit=5)] 66 | for i in range(0, 3): 67 | self._numberList[i].setPbmManager(pbmManager) 68 | self._numberList[i].setDigit(2) 69 | 70 | def _get_time(self): 71 | return list(self.rtc.datetime()[4:7]) 72 | 73 | def focus(self, msg): 74 | if msg == 0: 75 | self.parant.switch_siganl.emit() 76 | else: 77 | self._setMode = True 78 | self.rotary.setValueMin(0) 79 | self.rotary.setValueMax(23) 80 | self.rotary.setValue(self._time[self._setCount]) 81 | self.rotary.setEnable(True) 82 | 83 | def buttonCallback(self, pin, msg): 84 | if msg == 0: 85 | print('wisget bt pushed', pin, self) 86 | if self._setMode: 87 | print(self._setCount) 88 | if self._setCount == 2: 89 | timeList = list(self.rtc.datetime()) 90 | timeList[4] = self._numberList[0].getValue() 91 | timeList[5] = self._numberList[1].getValue() 92 | timeList[6] = self._numberList[2].getValue() 93 | self.rtc.datetime(tuple(timeList)) 94 | self._setCount = 0 95 | self._setMode = False 96 | self.rotary.setEnable(False) 97 | self.parant.switch_siganl.emit() 98 | else: 99 | self._setCount += 1 100 | self.rotary.setValueMin(0) 101 | self.rotary.setValueMax(59) 102 | self.rotary.setValue(self._time[self._setCount]) 103 | self.rotary.setValue(self._time[self._setCount]) 104 | else: 105 | self.parant.switch_siganl.emit() 106 | else: 107 | print('wisget bt pushed', pin, self) 108 | self._setCount = 0 109 | self._setMode = False 110 | self.rotary.setEnable(False) 111 | self.parant.switch_siganl.emit() 112 | 113 | def update(self): 114 | new_time = self._get_time() 115 | now_time = time.ticks_ms() 116 | if time.ticks_diff(now_time, self._last_time) > self.flicker_interval: 117 | if self._secCount < 50: 118 | self._secCount += 1 119 | else: 120 | self._secCount = 0 121 | self._last_time = now_time 122 | if not self._setMode: 123 | if new_time != self._time: 124 | self._time = new_time 125 | for num in self._numberList: 126 | new_num = new_time[self._numberList.index(num)] 127 | if num.getValue() != new_num: 128 | num.setValue(str(new_num)) 129 | else: 130 | value = self.rotary.value() 131 | if self.value_old != value: 132 | self._numberList[self._setCount].setValue(str(value)) 133 | self.value_old = value 134 | 135 | def show(self): 136 | colon = self.pbmManager.get_Pbm('colon.pbm') 137 | self.buffer.blit(colon.pbmPrint(), 10, 0) 138 | if self._secCount < 25: 139 | self.buffer.blit(colon.pbmPrint(), 25, 0) 140 | if self._setMode: 141 | if self._secCount > 35: 142 | self.buffer.fill_rect(self._numberList[self._setCount].x, self._numberList[self._setCount].y, 143 | self._numberList[self._setCount].w, self._numberList[self._setCount].h, 0) 144 | return (self.buffer, self.x, self.y, self.brackGround) 145 | 146 | def gui_show(self): 147 | self.buffer.fill(0) 148 | widgets = self.widgets 149 | if len(widgets) > 0: 150 | for widget in widgets: 151 | if not widget.hidden: 152 | res = widget.gui_show() 153 | self.buffer.blit(res[0], res[1], res[2], res[3]) 154 | self.show() 155 | return (self.buffer, self.x, self.y, self.brackGround) 156 | 157 | 158 | class SetBrightness(Window): 159 | 160 | def __init__(self, parant, window_info, insert=None, loc="Top"): 161 | super().__init__(parant, window_info, insert, loc) 162 | self.pbmManager = None 163 | self.isCheckable = True 164 | self.rotary = Rotary(21, 22) 165 | self.rotary.setEnable(False) 166 | self.rotary.setRangeMode('MODE_BOUNDED') 167 | self.rotary.setValueMin(5) 168 | self.rotary.setValueMax(100) 169 | self.rotary.setValue(50) 170 | self.value = 50 171 | self.display = self.getDisplay() 172 | self.inver_flag = False 173 | self.numberGroup = NumberGroup(self, (25, 0, 15, 7), 0.6) 174 | self.dimLable = Lable(self, (0, 0, 25, 7), 0.2) 175 | self.dimLable.setPbm(Pbm('/lum/dim_label.pbm')) 176 | self.old_dim = 50 177 | 178 | def setPbmManager(self, pbmManager): 179 | self.pbmManager = pbmManager 180 | self.numberGroup.setPbmManager(pbmManager) 181 | self.numberGroup.setDigit(3) 182 | self.numberGroup.setValue(str(self.value)) 183 | 184 | def focus(self, msg): 185 | if msg == 0: 186 | self.dimLable.hidden = True 187 | self.inver_flag = True 188 | self.rotary.setEnable(True) 189 | self.old_dim = self.value 190 | else: 191 | self.parant.switch_siganl.emit() 192 | 193 | def buttonCallback(self, pin, msg): 194 | if msg == 0: 195 | print('wisget bt pushed', pin, self) 196 | self.rotary.setEnable(False) 197 | self.dimLable.hidden = False 198 | self.inver_flag = False 199 | self.old_dim = self.value 200 | self.parant.switch_siganl.emit() 201 | self.parant.back_home() 202 | elif msg == 1: 203 | self.rotary.setValue(self.old_dim) 204 | self.parant.switch_siganl.emit() 205 | 206 | def valueToLum(self, value): 207 | return round((value / 100) * 240) 208 | 209 | def update(self): 210 | value = self.rotary.value() 211 | if self.value != value: 212 | self.value = value 213 | self.numberGroup.setValue(str(self.value)) 214 | self.display.set_display_dimming(self.valueToLum(self.value)) 215 | 216 | def gui_show(self): 217 | self.buffer.fill(0) 218 | widgets = self.widgets 219 | if len(widgets) > 0: 220 | for widget in widgets: 221 | if not widget.hidden: 222 | res = widget.gui_show() 223 | self.buffer.blit(res[0], res[1], res[2], res[3]) 224 | if self.inver_flag: 225 | # framebuf_inversion(self.buffer, 0, 0, round(self.value / 100 * 25), self.h) 226 | self.buffer.fill_rect(0, 0, round(self.value / 100 * 25), self.h, 1) 227 | return (self.buffer, self.x, self.y, self.brackGround) 228 | 229 | 230 | class NtpWindow(Window): 231 | 232 | def __init__(self, parant, window_info, insert=None, loc="Top"): 233 | super().__init__(parant, window_info, insert, loc) 234 | self.isCheckable = True 235 | self.wlan = self.parant.getWlan() 236 | self.connect_flag = False 237 | self.ntp_flag = False 238 | self.lable = Lable(self, (10, 0, 30, 7), 0.15) 239 | self.wifi_icon = Animation(self, (0, 0, 7, 7), 200) 240 | self.wifi_icon.dir = '/ntp' 241 | self.down_count = -1 242 | self.ntp_res = None 243 | self.wifi_icon.setRange([0, 3]) 244 | 245 | def setPbmManager(self, pbmManager): 246 | self.pbmManager = pbmManager 247 | pbmManager.add_pbm(Pbm('/ntp/main.pbm')) 248 | pbmManager.add_pbm(Pbm('/ntp/ntp.pbm')) 249 | pbmManager.add_pbm(Pbm('/ntp/error.pbm')) 250 | pbmManager.add_pbm(Pbm('/ntp/ok.pbm')) 251 | pbmManager.add_pbm(Pbm('/ntp/wait.pbm')) 252 | self.wifi_icon.setPbmManager(pbmManager) 253 | for i in range(0, 4): 254 | pbmManager.add_pbm(Pbm('/ntp/%s.pbm' % i)) 255 | self.lable.setPbm(self.pbmManager.get_Pbm('/ntp/main.pbm')) 256 | 257 | def focus(self, msg): 258 | if msg == 1: 259 | if self.wlan.isconnected(): 260 | self.isCheckable = False 261 | self.lable.setPbm(self.pbmManager.get_Pbm('/ntp/ntp.pbm')) 262 | self._ntptime() 263 | else: 264 | print('wifi is not connected') 265 | self.parant.switch_siganl.emit() 266 | self.parant.to_set_wifi() 267 | else: 268 | self.parant.switch_siganl.emit() 269 | 270 | def buttonCallback(self, pin, msg): 271 | if msg == 0: 272 | print('wisget bt pushed', pin, self) 273 | self.parant.switch_siganl.emit() 274 | else: 275 | pass 276 | 277 | def _ntptime(self): 278 | try: 279 | ntptime.NTP_DELTA = 3155644800 280 | ntptime.settime() 281 | self.ntp_res = True 282 | except: 283 | print('ntp error') 284 | self.ntp_res = False 285 | self.ntp_flag = 2 286 | 287 | def update(self): 288 | if self.down_count > 0: 289 | self.down_count -= 1 290 | elif self.down_count == 0: 291 | self.down_count = -1 292 | self.lable.setPbm(self.pbmManager.get_Pbm('/ntp/main.pbm')) 293 | self.parant.switch_siganl.emit() 294 | self.parant.back_home() 295 | if self.ntp_flag == 2: 296 | self.ntp_flag = 0 297 | self.isCheckable = True 298 | self.down_count = 250 299 | if self.ntp_res: 300 | self.lable.setPbm(self.pbmManager.get_Pbm('/ntp/ok.pbm')) 301 | else: 302 | self.lable.setPbm(self.pbmManager.get_Pbm('/ntp/error.pbm')) 303 | 304 | def gui_show(self): 305 | self.buffer.fill(0) 306 | widgets = self.widgets 307 | if len(widgets) > 0: 308 | for widget in widgets: 309 | if not widget.hidden: 310 | res = widget.gui_show() 311 | self.buffer.blit(res[0], res[1], res[2], res[3]) 312 | return (self.buffer, self.x, self.y, self.brackGround) 313 | 314 | 315 | class WifiWindow(Window): 316 | i = 0 317 | 318 | def __init__(self, parant, window_info, insert=None, loc="Top"): 319 | super().__init__(parant, window_info, insert, loc) 320 | self.isCheckable = True 321 | self.wlan = self.parant.getWlan() 322 | self.connect_flag = False 323 | self.lable = Lable(self, (0, 0, 25, 7), 0.15) 324 | self.wifi_button = ScreenButton(self, (25, 0, 15, 7), 30) 325 | self.wifi_button.dir = '/wifi' 326 | self.down_count = -1 327 | self.wifi_button.setRange([0, 6]) 328 | 329 | def setPbmManager(self, pbmManager): 330 | self.pbmManager = pbmManager 331 | pbmManager.add_pbm(Pbm('/wifi/main.pbm')) 332 | pbmManager.add_pbm(Pbm('/wifi/error.pbm')) 333 | pbmManager.add_pbm(Pbm('/wifi/ok.pbm')) 334 | pbmManager.add_pbm(Pbm('/wifi/wait.pbm')) 335 | self.wifi_button.setPbmManager(pbmManager) 336 | for i in range(0, 7): 337 | pbmManager.add_pbm(Pbm('/wifi/on/%s.pbm' % i)) 338 | pbmManager.add_pbm(Pbm('/wifi/off/%s.pbm' % i)) 339 | self.lable.setPbm(self.pbmManager.get_Pbm('/wifi/main.pbm')) 340 | 341 | def focus(self, msg): 342 | if msg == 0: 343 | if not self.wlan.isconnected() and not self.connect_flag: 344 | self.i = 0 345 | self.lable.setPbm(self.pbmManager.get_Pbm('/wifi/wait.pbm')) 346 | self.wifi_button.setState(True) 347 | self.connect_flag = True 348 | self.do_connect('Reboot93--2.4G', 'LOVELIVEsaiko93') 349 | elif self.wlan.isconnected() and self.connect_flag: 350 | self.wifi_button.setState(False) 351 | self.dis_connect() 352 | self.i = 0 353 | self.parant.switch_siganl.emit() 354 | else: 355 | self.parant.switch_siganl.emit() 356 | 357 | def buttonCallback(self, pin, msg): 358 | if msg == 0: 359 | print('wisget bt pushed', pin, self) 360 | self.parant.switch_siganl.emit() 361 | else: 362 | pass 363 | 364 | def do_connect(self, ssid, passwd): 365 | self.connect_flag = True 366 | print(self.wlan.active()) 367 | self.wlan.active(True) 368 | self.wlan.connect(ssid, passwd) 369 | print('connecting to network...') 370 | 371 | def dis_connect(self): 372 | self.connect_flag = False 373 | self.wlan.disconnect() 374 | 375 | def update(self): 376 | if self.wlan.isconnected() and self.i == 0: 377 | print('network config:', self.wlan.ifconfig()) 378 | self.i += 1 379 | self.lable.setPbm(self.pbmManager.get_Pbm('/wifi/main.pbm')) 380 | if not self.i == 0: 381 | self.wifi_button.setState(self.wlan.isconnected()) 382 | 383 | def gui_show(self): 384 | self.buffer.fill(0) 385 | widgets = self.widgets 386 | if len(widgets) > 0: 387 | for widget in widgets: 388 | if not widget.hidden: 389 | res = widget.gui_show() 390 | self.buffer.blit(res[0], res[1], res[2], res[3]) 391 | return (self.buffer, self.x, self.y, self.brackGround) 392 | 393 | 394 | class YeelightSetBrightness(Window): 395 | 396 | def __init__(self, parant, window_info, insert=None, loc="Top"): 397 | super().__init__(parant, window_info, insert, loc) 398 | self.infoSignal = Signal() 399 | self.infoSignal.connect(self.init) 400 | self.pbmManager = None 401 | self.isCheckable = True 402 | self.rotary = Rotary(21, 22) 403 | self.rotary.setEnable(False) 404 | self.rotary.setRangeMode('MODE_BOUNDED') 405 | self.rotary.setValueMin(1) 406 | self.rotary.setValueMax(100) 407 | self.rotary.setValue(50) 408 | self.value = 50 409 | self.numberGroup = NumberGroup(self, (25, 0, 15, 7), 0.7) 410 | self.lable = Lable(self, (0, 0, 25, 7), 0.2) 411 | self.lable.setPbm(Pbm('/lum/dim_label.pbm')) 412 | self.old_brightness = 1 413 | 414 | def setPbmManager(self, pbmManager): 415 | self.pbmManager = pbmManager 416 | self.numberGroup.setPbmManager(pbmManager) 417 | self.numberGroup.setDigit(3) 418 | self.numberGroup.setValue(str(self.value)) 419 | 420 | def init(self): 421 | info = self.parant.blub.get_properties() 422 | print(info) 423 | if type(info) == dict: 424 | self.value = int(info['bright']) 425 | self.old_brightness = self.value 426 | self.rotary.setValue(self.value) 427 | self.numberGroup.setValue(str(self.value)) 428 | 429 | def focus(self, msg): 430 | if msg == 0: 431 | self.rotary.setEnable(True) 432 | else: 433 | self.parant.switch_siganl.emit() 434 | 435 | def buttonCallback(self, pin, msg): 436 | if msg == 0: 437 | print('wisget bt pushed', pin, self) 438 | self.parant.blub.set_brightness(self.value) 439 | self.rotary.setEnable(False) 440 | self.parant.switch_siganl.emit() 441 | self.parant.back_home() 442 | elif msg == 1: 443 | self.rotary.setValue(self.old_brightness) 444 | self.numberGroup.setValue(str(self.old_brightness)) 445 | self.parant.blub.set_brightness(self.old_brightness) 446 | self.rotary.setEnable(False) 447 | self.parant.switch_siganl.emit() 448 | self.parant.back_home() 449 | 450 | def update(self): 451 | value = self.rotary.value() 452 | if self.value != value: 453 | self.value = value 454 | self.numberGroup.setValue(str(self.value)) 455 | # self.parant.blub.set_brightness(self.value) 456 | 457 | def gui_show(self): 458 | self.buffer.fill(0) 459 | widgets = self.widgets 460 | if len(widgets) > 0: 461 | for widget in widgets: 462 | if not widget.hidden: 463 | res = widget.gui_show() 464 | self.buffer.blit(res[0], res[1], res[2], res[3]) 465 | return (self.buffer, self.x, self.y, self.brackGround) 466 | 467 | 468 | class YeelightSetColorTemperature(Window): 469 | 470 | def __init__(self, parant, window_info, insert=None, loc="Top"): 471 | super().__init__(parant, window_info, insert, loc) 472 | self.infoSignal = Signal() 473 | self.infoSignal.connect(self.init) 474 | self.pbmManager = None 475 | self.isCheckable = True 476 | self.rotary = Rotary(21, 22) 477 | self.rotary.setEnable(False) 478 | self.rotary.setIncr(100) 479 | self.rotary.setRangeMode('MODE_BOUNDED') 480 | self.rotary.setValueMin(2700) 481 | self.rotary.setValueMax(6500) 482 | self.rotary.setValue(6500) 483 | self.value = 6500 484 | self.numberGroup = NumberGroup(self, (20, 0, 20, 7), 0.7) 485 | self.lable = Lable(self, (0, 0, 20, 7), 0.2) 486 | self.lable.setPbm(Pbm('/yee/k.pbm')) 487 | self.old_ct = 6500 488 | 489 | def setPbmManager(self, pbmManager): 490 | self.pbmManager = pbmManager 491 | self.numberGroup.setPbmManager(pbmManager) 492 | self.numberGroup.setDigit(4) 493 | self.numberGroup.setValue(str(self.value)) 494 | 495 | def init(self): 496 | info = self.parant.blub.get_properties() 497 | print(info) 498 | if type(info) == dict: 499 | self.value = int(info['ct']) 500 | self.old_ct = self.value 501 | self.rotary.setValue(self.value) 502 | self.numberGroup.setValue(str(self.value)) 503 | 504 | def focus(self, msg): 505 | if msg == 0: 506 | self.rotary.setEnable(True) 507 | else: 508 | self.parant.switch_siganl.emit() 509 | 510 | def buttonCallback(self, pin, msg): 511 | if msg == 0: 512 | print('wisget bt pushed', pin, self) 513 | self.parant.blub.change_color_temperature(self.value) 514 | self.rotary.setEnable(False) 515 | self.parant.switch_siganl.emit() 516 | self.parant.back_home() 517 | elif msg == 1: 518 | self.rotary.setValue(self.old_ct) 519 | self.numberGroup.setValue(str(self.old_ct)) 520 | self.parant.blub.change_color_temperature(self.old_ct) 521 | self.rotary.setEnable(False) 522 | self.parant.switch_siganl.emit() 523 | self.parant.back_home() 524 | 525 | def update(self): 526 | value = self.rotary.value() 527 | if self.value != value: 528 | self.value = value 529 | self.numberGroup.setValue(str(self.value)) 530 | 531 | def gui_show(self): 532 | self.buffer.fill(0) 533 | widgets = self.widgets 534 | if len(widgets) > 0: 535 | for widget in widgets: 536 | if not widget.hidden: 537 | res = widget.gui_show() 538 | self.buffer.blit(res[0], res[1], res[2], res[3]) 539 | return (self.buffer, self.x, self.y, self.brackGround) 540 | 541 | 542 | class YeelightView(RotaryViewPager): 543 | 544 | def __init__(self, parant, window_info, scrollSpeed=0.1, back=2, back_count=100, insert=None, loc="Top"): 545 | super().__init__(parant, window_info, scrollSpeed, back, back_count, insert, loc) 546 | self.wlan = self.parant.getWlan() 547 | 548 | self.window_1 = Lable(self, (0, 0, 40, 7), 0.2) 549 | self.window_2 = YeelightSetBrightness(self, (0, 0, 40, 7)) 550 | self.window_3 = YeelightSetColorTemperature(self, (0, 0, 40, 7)) 551 | 552 | self.window_1.setPbm(Pbm('/yee/main.pbm')) 553 | 554 | def focus(self, msg): 555 | if msg == 0: 556 | if self.wlan.isconnected(): 557 | self.rotary.setEnable(True) 558 | self.blub = yeelight.Bulb('192.168.6.246') 559 | self.setWidget(1) 560 | self.window_2.infoSignal.emit() 561 | self.window_3.infoSignal.emit() 562 | else: 563 | print(self, 'wlan not connected') 564 | self.parant.switch_siganl.emit() 565 | self.parant.to_set_wifi() 566 | else: 567 | self.parant.switch_siganl.emit() 568 | 569 | def setPbmManager(self, pbmManager): 570 | self.pbmManager = pbmManager 571 | self.window_2.setPbmManager(self.pbmManager) 572 | self.window_3.setPbmManager(self.pbmManager) 573 | 574 | def buttonCallback(self, pin, msg): 575 | if msg == 0: 576 | print('viewpager button', self.widgetsChecked, self.widgets) 577 | if self.buttonPassthrough: 578 | print('Passthrough button to', self.widgets[self.widgetsChecked]) 579 | self.widgets[self.widgetsChecked].buttonCallback(pin, msg) 580 | else: 581 | if self.widgets[self.widgetsChecked].isCheckable: 582 | self.buttonPassthrough = True 583 | self.rotary.setEnable(False) 584 | self.widgets[self.widgetsChecked].focus(msg) 585 | else: 586 | print('window: %s is not Checkable' % self.widgets[self.widgetsChecked]) 587 | elif msg == 1: 588 | print('viewpager button long pressed', self.widgetsChecked, self.widgets) 589 | self.back_home() 590 | self.buttonPassthrough = False 591 | self.rotary.setEnable(False) 592 | self.parant.switch_siganl.emit() 593 | self.parant.back_home() -------------------------------------------------------------------------------- /rotary.py: -------------------------------------------------------------------------------- 1 | # MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Platform-independent MicroPython code for the rotary encoder module 6 | 7 | # Documentation: 8 | # https://github.com/MikeTeachman/micropython-rotary 9 | 10 | # Reboot93 adds some methods to the original version for customization 11 | ''' 12 | value(): 13 | setValue(value): 14 | setValueMax(value): 15 | setValueMin(value): 16 | setRangeMode(mode): 17 | setIncr(incr): 18 | reset(num) 19 | enable(): 20 | ''' 21 | 22 | 23 | import micropython 24 | 25 | _DIR_CW = const(0x10) # Clockwise step 26 | _DIR_CCW = const(0x20) # Counter-clockwise step 27 | 28 | # Rotary Encoder States 29 | _R_START = const(0x0) 30 | _R_CW_1 = const(0x1) 31 | _R_CW_2 = const(0x2) 32 | _R_CW_3 = const(0x3) 33 | _R_CCW_1 = const(0x4) 34 | _R_CCW_2 = const(0x5) 35 | _R_CCW_3 = const(0x6) 36 | _R_ILLEGAL = const(0x7) 37 | 38 | _transition_table = [ 39 | 40 | # |------------- NEXT STATE -------------| |CURRENT STATE| 41 | # CLK/DT CLK/DT CLK/DT CLK/DT 42 | # 00 01 10 11 43 | [_R_START, _R_CCW_1, _R_CW_1, _R_START], # _R_START 44 | [_R_CW_2, _R_START, _R_CW_1, _R_START], # _R_CW_1 45 | [_R_CW_2, _R_CW_3, _R_CW_1, _R_START], # _R_CW_2 46 | [_R_CW_2, _R_CW_3, _R_START, _R_START | _DIR_CW], # _R_CW_3 47 | [_R_CCW_2, _R_CCW_1, _R_START, _R_START], # _R_CCW_1 48 | [_R_CCW_2, _R_CCW_1, _R_CCW_3, _R_START], # _R_CCW_2 49 | [_R_CCW_2, _R_START, _R_CCW_3, _R_START | _DIR_CCW], # _R_CCW_3 50 | [_R_START, _R_START, _R_START, _R_START]] # _R_ILLEGAL 51 | 52 | _transition_table_half_step = [ 53 | [_R_CW_3, _R_CW_2, _R_CW_1, _R_START], 54 | [_R_CW_3 | _DIR_CCW, _R_START, _R_CW_1, _R_START], 55 | [_R_CW_3 | _DIR_CW, _R_CW_2, _R_START, _R_START], 56 | [_R_CW_3, _R_CCW_2, _R_CCW_1, _R_START], 57 | [_R_CW_3, _R_CW_2, _R_CCW_1, _R_START | _DIR_CW], 58 | [_R_CW_3, _R_CCW_2, _R_CW_3, _R_START | _DIR_CCW], 59 | [_R_START, _R_START, _R_START, _R_START], 60 | [_R_START, _R_START, _R_START, _R_START]] 61 | 62 | _STATE_MASK = const(0x07) 63 | _DIR_MASK = const(0x30) 64 | 65 | 66 | def _wrap(value, incr, lower_bound, upper_bound): 67 | range = upper_bound - lower_bound + 1 68 | value = value + incr 69 | 70 | if value < lower_bound: 71 | value += range * ((lower_bound - value) // range + 1) 72 | 73 | return lower_bound + (value - lower_bound) % range 74 | 75 | 76 | def _bound(value, incr, lower_bound, upper_bound): 77 | return min(upper_bound, max(lower_bound, value + incr)) 78 | 79 | 80 | def _trigger(rotary_instance): 81 | for listener in rotary_instance._listener: 82 | listener() 83 | 84 | 85 | class Rotary(object): 86 | RANGE_UNBOUNDED = const(1) 87 | RANGE_WRAP = const(2) 88 | RANGE_BOUNDED = const(3) 89 | 90 | def __init__(self, min_val, max_val, incr, reverse, range_mode, half_step, invert): 91 | self._min_val = min_val 92 | self._max_val = max_val 93 | self._incr = incr 94 | self._reverse = -1 if reverse else 1 95 | self._range_mode = range_mode 96 | self._value = min_val 97 | self._state = _R_START 98 | self._half_step = half_step 99 | self._invert = invert 100 | self._listener = [] 101 | 102 | def set(self, value=None, min_val=None, incr=None, 103 | max_val=None, reverse=None, range_mode=None): 104 | # disable DT and CLK pin interrupts 105 | self._hal_disable_irq() 106 | 107 | if value is not None: 108 | self._value = value 109 | if min_val is not None: 110 | self._min_val = min_val 111 | if max_val is not None: 112 | self._max_val = max_val 113 | if incr is not None: 114 | self._incr = incr 115 | if reverse is not None: 116 | self._reverse = -1 if reverse else 1 117 | if range_mode is not None: 118 | self._range_mode = range_mode 119 | self._state = _R_START 120 | 121 | # enable DT and CLK pin interrupts 122 | self._hal_enable_irq() 123 | 124 | def value(self): 125 | return self._value 126 | 127 | def setValue(self, value: int): 128 | self._value = value 129 | 130 | def setValueMax(self, value: int): 131 | self._max_val = value 132 | 133 | def setValueMin(self, value: int): 134 | self._min_val = value 135 | 136 | def setRangeMode(self, mode): 137 | self._range_mode = mode 138 | 139 | def setIncr(self, incr: int): 140 | self._incr = abs(incr) 141 | 142 | def reset(self, num=0): 143 | self._value = num 144 | 145 | def enable(self): 146 | self._hal_enable_irq() 147 | 148 | def close(self): 149 | self._hal_close() 150 | 151 | def add_listener(self, l): 152 | self._listener.append(l) 153 | 154 | def remove_listener(self, l): 155 | if l not in self._listener: 156 | raise ValueError('{} is not an installed listener'.format(l)) 157 | self._listener.remove(l) 158 | 159 | def _process_rotary_pins(self, pin): 160 | old_value = self._value 161 | clk_dt_pins = (self._hal_get_clk_value() << 162 | 1) | self._hal_get_dt_value() 163 | 164 | if self._invert: 165 | clk_dt_pins = ~clk_dt_pins & 0x03 166 | 167 | # Determine next state 168 | if self._half_step: 169 | self._state = _transition_table_half_step[self._state & 170 | _STATE_MASK][clk_dt_pins] 171 | else: 172 | self._state = _transition_table[self._state & 173 | _STATE_MASK][clk_dt_pins] 174 | direction = self._state & _DIR_MASK 175 | 176 | incr = 0 177 | if direction == _DIR_CW: 178 | incr = self._incr 179 | elif direction == _DIR_CCW: 180 | incr = -self._incr 181 | 182 | incr *= self._reverse 183 | 184 | if self._range_mode == self.RANGE_WRAP: 185 | self._value = _wrap( 186 | self._value, 187 | incr, 188 | self._min_val, 189 | self._max_val) 190 | elif self._range_mode == self.RANGE_BOUNDED: 191 | self._value = _bound( 192 | self._value, 193 | incr, 194 | self._min_val, 195 | self._max_val) 196 | else: 197 | self._value = self._value + incr 198 | 199 | try: 200 | if old_value != self._value and len(self._listener) != 0: 201 | _trigger(self) 202 | except: 203 | pass 204 | -------------------------------------------------------------------------------- /rotary_irp_esp.py: -------------------------------------------------------------------------------- 1 | # MIT License (MIT) 2 | # Copyright (c) 2020 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Platform-specific MicroPython code for the rotary encoder module 6 | # ESP8266/ESP32 implementation 7 | 8 | # Documentation: 9 | # https://github.com/MikeTeachman/micropython-rotary 10 | 11 | from machine import Pin 12 | from rotary import Rotary 13 | from sys import platform 14 | 15 | _esp8266_deny_pins = [16] 16 | 17 | 18 | class RotaryIRQ(Rotary): 19 | 20 | def __init__(self, pin_num_clk, pin_num_dt, min_val=0, max_val=10, incr=1, 21 | reverse=False, range_mode=Rotary.RANGE_UNBOUNDED, pull_up=False, half_step=False, invert=False): 22 | 23 | if platform == 'esp8266': 24 | if pin_num_clk in _esp8266_deny_pins: 25 | raise ValueError( 26 | '%s: Pin %d not allowed. Not Available for Interrupt: %s' % 27 | (platform, pin_num_clk, _esp8266_deny_pins)) 28 | if pin_num_dt in _esp8266_deny_pins: 29 | raise ValueError( 30 | '%s: Pin %d not allowed. Not Available for Interrupt: %s' % 31 | (platform, pin_num_dt, _esp8266_deny_pins)) 32 | 33 | super().__init__(min_val, max_val, incr, reverse, range_mode, half_step, invert) 34 | 35 | if pull_up == True: 36 | self._pin_clk = Pin(pin_num_clk, Pin.IN, Pin.PULL_UP) 37 | self._pin_dt = Pin(pin_num_dt, Pin.IN, Pin.PULL_UP) 38 | else: 39 | self._pin_clk = Pin(pin_num_clk, Pin.IN) 40 | self._pin_dt = Pin(pin_num_dt, Pin.IN) 41 | 42 | self._enable_clk_irq(self._process_rotary_pins) 43 | self._enable_dt_irq(self._process_rotary_pins) 44 | 45 | def _enable_clk_irq(self, callback=None): 46 | self._pin_clk.irq( 47 | trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, 48 | handler=callback) 49 | 50 | def _enable_dt_irq(self, callback=None): 51 | self._pin_dt.irq( 52 | trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, 53 | handler=callback) 54 | 55 | def _disable_clk_irq(self): 56 | self._pin_clk.irq(handler=None) 57 | 58 | def _disable_dt_irq(self): 59 | self._pin_dt.irq(handler=None) 60 | 61 | def _hal_get_clk_value(self): 62 | return self._pin_clk.value() 63 | 64 | def _hal_get_dt_value(self): 65 | return self._pin_dt.value() 66 | 67 | def _hal_enable_irq(self): 68 | self._enable_clk_irq(self._process_rotary_pins) 69 | self._enable_dt_irq(self._process_rotary_pins) 70 | 71 | def _hal_disable_irq(self): 72 | self._disable_clk_irq() 73 | self._disable_dt_irq() 74 | 75 | def _hal_close(self): 76 | self._hal_disable_irq() 77 | -------------------------------------------------------------------------------- /wifi/error.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/error.pbm -------------------------------------------------------------------------------- /wifi/main.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/main.pbm -------------------------------------------------------------------------------- /wifi/off/0.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/off/0.pbm -------------------------------------------------------------------------------- /wifi/off/1.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/off/1.pbm -------------------------------------------------------------------------------- /wifi/off/2.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/off/2.pbm -------------------------------------------------------------------------------- /wifi/off/3.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/off/3.pbm -------------------------------------------------------------------------------- /wifi/off/4.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/off/4.pbm -------------------------------------------------------------------------------- /wifi/off/5.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/off/5.pbm -------------------------------------------------------------------------------- /wifi/off/6.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/off/6.pbm -------------------------------------------------------------------------------- /wifi/ok.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/ok.pbm -------------------------------------------------------------------------------- /wifi/on/0.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/on/0.pbm -------------------------------------------------------------------------------- /wifi/on/1.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/on/1.pbm -------------------------------------------------------------------------------- /wifi/on/2.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/on/2.pbm -------------------------------------------------------------------------------- /wifi/on/3.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/on/3.pbm -------------------------------------------------------------------------------- /wifi/on/4.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/on/4.pbm -------------------------------------------------------------------------------- /wifi/on/5.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/on/5.pbm -------------------------------------------------------------------------------- /wifi/on/6.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/on/6.pbm -------------------------------------------------------------------------------- /wifi/wait.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/wifi/wait.pbm -------------------------------------------------------------------------------- /yee/k.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/yee/k.pbm -------------------------------------------------------------------------------- /yee/main.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reboot93/VFD-desktop-clock-micropython/d11571a4954ebc4d47d49ab21cec280bfd83d624/yee/main.pbm -------------------------------------------------------------------------------- /yeelight.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This library is from https://github.com/GianniDPC/Yeelight-micropython 3 | Reboot93 adds some methods according to the yeelight API document 4 | https://github.com/Reboot93/Yeelight-micropython 5 | ''' 6 | 7 | 8 | import usocket as socket 9 | import json 10 | 11 | 12 | class YeeLightException(Exception): 13 | pass 14 | 15 | 16 | class EFFECT: 17 | SMOOTH = "smooth" 18 | SUDDEN = "sudden" 19 | 20 | 21 | class MODE: 22 | NORMAL = 0 23 | CT_MODE = 1 24 | RGB_MODE = 2 25 | HSV_MODE = 3 26 | COLOR_FLOW_MODE = 4 27 | NIGHT_LIGHT_MODE = 5 28 | 29 | 30 | class ACTION: 31 | LED_RECOVER_STATE = 0 32 | LED_STAY = 1 33 | LED_TURN_OFF = 2 34 | 35 | 36 | class SCENE_CLASS: 37 | COLOR = "color" 38 | HSV = "hsv" 39 | CT = "ct" 40 | AUTO_DELAY_OFF = "auto_delay_off" 41 | 42 | 43 | class SET_ADJUST_ACTION: 44 | INCREASE = "increase" 45 | DECREASE = "decrease" 46 | CIRCLE = "circle" 47 | 48 | 49 | class SET_ADJUST_PROP: 50 | BRIGHT = "bright" 51 | CT = "ct" 52 | COLOR = "color" 53 | 54 | 55 | """ API DOCS: https://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf """ 56 | 57 | 58 | class Bulb(): 59 | def __init__(self, ip, port=55443, debug=False): 60 | self.cmd_id = 0 61 | self._ip = ip 62 | self._port = port 63 | self.debug = debug 64 | 65 | @property 66 | def get_ip(self): 67 | return self._ip 68 | 69 | @property 70 | def get_port(self): 71 | return self._port 72 | 73 | def turn_on(self, effect=EFFECT.SUDDEN, duration=30, mode=MODE.NORMAL): 74 | return self._handle_response(self._send_message("set_power", 75 | ["on", effect, duration, mode])) 76 | 77 | def turn_off(self, effect=EFFECT.SUDDEN, duration=30, mode=MODE.NORMAL): 78 | return self._handle_response(self._send_message("set_power", 79 | ["off", effect, duration, mode])) 80 | 81 | def toggle(self): 82 | return self._handle_response(self._send_message("toggle")) 83 | 84 | @property 85 | def is_on(self): 86 | result = self._handle_response(self._send_message("get_prop", ["power"])) 87 | return result[0] == "on" 88 | 89 | def change_color_temperature(self, color_temp_val, effect=EFFECT.SUDDEN, duration=30): 90 | """ 91 | :param color_temp_val: between 1700 and 6500K 92 | """ 93 | return self._handle_response(self._send_message("set_ct_abx", 94 | [color_temp_val, effect, duration])) 95 | 96 | def set_rgb(self, r, g, b, effect=EFFECT.SUDDEN, duration=30): 97 | """ 98 | :param r: red 99 | :param g: green 100 | :param b: blue 101 | """ 102 | rgb = (r * 65536) + (g * 256) + b 103 | return self._handle_response(self._send_message("set_rgb", 104 | [rgb, effect, duration])) 105 | 106 | def bg_set_rgb(self, r, g, b, effect=EFFECT.SUDDEN, duration=30): 107 | rgb = (r * 65536) + (g * 256) + b 108 | return self._handle_response(self._send_message("bg_set_rgb", 109 | [rgb, effect, duration])) 110 | 111 | def set_hsv(self, hue, sat, effect=EFFECT.SUDDEN, duration=30): 112 | """ 113 | :param hue: ranges from 0 to 359 114 | :param sat: ranges from 0 to 100 115 | """ 116 | return self._handle_response(self._send_message("set_hsv", 117 | [hue, sat, effect, duration])) 118 | 119 | def set_brightness(self, brightness, effect=EFFECT.SUDDEN, duration=30): 120 | """ 121 | :param brightness: between 1 and 100 122 | """ 123 | return self._handle_response(self._send_message("set_bright", 124 | [brightness, effect, duration])) 125 | 126 | def save_current_state(self): 127 | return self._handle_response(self._send_message("set_default")) 128 | 129 | def start_color_flow(self, count, flow_expression, action=ACTION.LED_RECOVER_STATE): 130 | """ 131 | :param count: is the total number of visible state changing before color flow 132 | stopped. 0 means infinite loop on the state changing. 133 | :param flow_expression: is the expression of the state changing series (see API docs) 134 | :param action: is the action taken after the flow is stopped. 135 | 0 means smart LED recover to the state before the color flow started. 136 | 1 means smart LED stay at the state when the flow is stopped. 137 | 2 means turn off the smart LED after the flow is stopped. 138 | """ 139 | return self._handle_response(self._send_message("start_cf", 140 | [count, action, flow_expression])) 141 | 142 | def stop_color_flow(self): 143 | return self._handle_response(self._send_message("stop_cf")) 144 | 145 | def set_scene(self, val1, val2, val3, opt=SCENE_CLASS.COLOR): 146 | """ 147 | :param val1: :param val2: :param val3: are class specific. (see API docs) 148 | :param opt: can be "color", "hsv", "ct", "cf", "auto_delay_off". 149 | "color" means change the smart LED to specified color and brightness. 150 | "hsv" means change the smart LED to specified color and brightness. 151 | "ct" means change the smart LED to specified ct and brightness. 152 | "cf" means start a color flow in specified fashion. 153 | "auto_delay_off" means turn on the smart LED to specified 154 | brightness and start a sleep timer to turn off the light after the specified minutes. 155 | """ 156 | return self._handle_response(self._send_message("set_scene", 157 | [opt, val1, val2, val3])) 158 | 159 | def sleep_timer(self, time_minutes, type=0): 160 | return self._handle_response(self._send_message("cron_add", 161 | [type, time_minutes])) 162 | 163 | def get_background_job(self, type=0): 164 | return self._handle_response(self._send_message("cron_get", 165 | [type])) 166 | 167 | def get_properties(self, requested_properties=['power', 168 | 'bright', 169 | 'ct', 170 | 'rgb', 171 | 'hue', 172 | 'sat', 173 | 'color_mode', 174 | 'flowing', 175 | 'delayoff', 176 | 'music_on', 177 | 'name', 178 | 'bg_power', 179 | 'bg_flowing', 180 | 'bg_ct', 181 | 'bg_bright', 182 | 'bg_hue', 183 | 'bg_sat', 184 | 'bg_rgb', 185 | 'nl_br', 186 | 'active_mode']): 187 | try: 188 | res = self._handle_response(self._send_message("get_prop", requested_properties)) 189 | count = 0 190 | properties = {} 191 | for i in res: 192 | properties[requested_properties[count]] = i 193 | count += 1 194 | return properties 195 | except: 196 | return -1 197 | 198 | return res 199 | 200 | def delete_background_job(self, type=0): 201 | return self._handle_response(self._send_message("cron_del", 202 | [type])) 203 | 204 | def set_adjust(self, action=SET_ADJUST_ACTION.INCREASE, prop=SET_ADJUST_PROP.BRIGHT): 205 | """ 206 | :param action: the direction of the adjustment. The valid value can be: 207 | "increase": increase the specified property 208 | "decrease": decrease the specified property 209 | "circle": increase the specified property, after it reaches the max 210 | value, go back to minimum value. 211 | :param prop: the property to adjust. The valid value can be: 212 | "bright": adjust brightness. 213 | "ct": adjust color temperature. 214 | "color": adjust color. (When “prop" is “color", the “action" can only 215 | be “circle", otherwise, it will be deemed as invalid request.) 216 | """ 217 | return self._handle_response(self._send_message("set_adjust", 218 | [action, prop])) 219 | 220 | def adjust_brightness(self, percentage, duration=30): 221 | """ 222 | :param percentage: the percentage to be adjusted. The range is: -100 ~ 100 223 | """ 224 | return self._handle_response(self._send_message("adjust_bright", 225 | [percentage, duration])) 226 | 227 | def adjust_color_temperature(self, percentage, duration=30): 228 | """ 229 | :param percentage: the percentage to be adjusted. The range is: -100 ~ 100 230 | """ 231 | return self._handle_response(self._send_message("adjust_ct", 232 | [percentage, duration])) 233 | 234 | def adjust_color(self, percentage, duration=30): 235 | """ 236 | :param percentage: the percentage to be adjusted. The range is: -100 ~ 100 237 | """ 238 | return self._handle_response(self._send_message("adjust_color", 239 | [percentage, duration])) 240 | 241 | def set_music(self, enable=True): 242 | """ 243 | :param host: the IP address of the music server. 244 | :param port: the TCP port music application is listening on. 245 | :param enable: 0: turn off music mode. 246 | 1: turn on music mode. 247 | """ 248 | return self._handle_response(self._send_message("set_music", 249 | [1 if enable else 0])) 250 | 251 | def set_name(self, name): 252 | """ 253 | :param name: the name of the device 254 | """ 255 | return self._handle_response(self._send_message("set_name", 256 | [name])) 257 | 258 | def _send_message(self, method, params=None): 259 | if params is None: 260 | params = [] 261 | 262 | self.cmd_id += 1 263 | 264 | message = '{{"id": {id}, "method": "{method}", "params": {params}}}\r\n'. \ 265 | format(id=self.cmd_id, method=method, params=json.dumps(params)) 266 | 267 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 268 | 269 | try: 270 | sock.connect((self.get_ip, self.get_port)) 271 | sock.send(message.encode()) 272 | recv_data = sock.recv(1024) 273 | except socket.timeout: 274 | return "" 275 | finally: 276 | sock.close() 277 | 278 | return recv_data 279 | 280 | def _handle_response(self, response): 281 | response = json.loads(response.decode('utf-8')) 282 | 283 | if self.debug: 284 | print(response) 285 | 286 | if "params" in response: 287 | return response["params"] 288 | elif "id" in response and not "error" in response: 289 | return response["result"] 290 | elif "error" in response: 291 | raise YeeLightException(response["error"]) 292 | else: 293 | raise YeeLightException("Unknown Exception occurred.") 294 | --------------------------------------------------------------------------------