diff --git a/.gitignore b/.gitignore index 238ef96becff3e0984ff48f6de9310184dc1c28c..563f1b4e8cd7d5f9e90bcde572b1be1964c3ba10 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ test.py sdk/__pycache__/ logs __pycache__/ +configs diff --git a/README.md b/README.md index 5e895ed4348d228dd785d2166dd542fcb21a0813..d14e973f01c1102cc371e32c9f2281b2a5c65d1a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ # PiZeroW-eink-Clock -Master分支待成熟后发布 +#### 介绍 +一个基于树莓派Zero W的水墨屏天气时钟。本仓库是该项目中树莓派部分的程序,是本项目中最重要的部分 + +#### 软件架构 +├── main.py 主程序
+├── modules 模块
+│   ├── common 通用模块
+│   │   ├── sdk_beta.py SDK
+│   │   └── updater.py 更新程序
+│   └── custom 用户自定义模块
+└── resources 资源文件夹
+>详细说明待软件成熟后放出 + +#### 安装教程 + +1. 暂未发布 + +#### 使用说明 + +1. 需要树莓派Zero Wh与微雪触摸水墨屏 + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + +#### 补充 +半夜惊醒发现今天要断更了 diff --git a/configs/README.md b/configs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..161ab6fd9419c51cf762ddad1731b2bb1c2c4e48 --- /dev/null +++ b/configs/README.md @@ -0,0 +1 @@ +这里被用于放置配置文件 diff --git a/main.py b/main.py new file mode 100644 index 0000000000000000000000000000000000000000..b48146b7b5b0715927e9fdda936c06246f9368c0 --- /dev/null +++ b/main.py @@ -0,0 +1,220 @@ +import json +import os +import threading +import importlib +import time +import traceback + +# from sdk import environment # 真机环境 +from sdk import environment_dev as environment # 调试环境 + +from sdk import logger +from sdk import configurator +from PIL import Image + +# TODO:添加themeApp的状态栏 + + +example_config = { + "main": { + "enable_plugins": ["hello_world"], + "enable_theme": "default", + "enable_apps": ["简单清单", "简单计算器", "账号管理", "随机数生成器", "祖安宝典", "系统设置", "hello_world"], + "opening_images": [ + "resources/images/raspberry.jpg", + ], + "loading_image": "resources/images/loading.jpg", + "env_configs": { + "auto_sleep_time": 30, + "refresh_time": 43200, + "refresh_interval": 30, + "threadpool_size": 20 + } + }, + "plugins": {}, + "themes": {}, + "apps": {}, + "updater": {}, + "update_tddadf7": 1 +} + +class DependenceError(Exception): + pass + + +def mainThread(): # 主线程:UI管理(如果有模拟器就不是主线程了) + + opening_images = [] # 准备开屏动画 + opening_images_path = configurator_main.read("opening_images") + for path in opening_images_path: + file = open(path, "rb") + opening_images.append(Image.open(file)) + paperNow = environment.graphics.Paper(env, opening_images[0]) + load_lock = threading.Barrier(2) + + def opening(): + paperNow.init() + if len(opening_images) >= 1: + for i in opening_images[1:]: + paperNow.update_background(i) + if load_lock.n_waiting == 0: # 如果等动画显示完后主线程还没进入等待,则显示Loading画面 + _file = open(configurator_main.read("loading_image"), "rb") + paperNow.update_background(Image.open(_file)) + _file.close() + load_lock.wait() + + try: + # 显示开屏动画 + env.pool.add(opening) + # 在这里放置要预加载的东西(主题与插件等) + touch_recoder_new = environment.touchpad.TouchRecoder() # 触摸 + touch_recoder_old = environment.touchpad.TouchRecoder() + + wheels_name = [] + plugins = {} + theme = None + apps = {} + enable_plugins = configurator_main.read( + "enable_plugins", raise_error=True) + for wheel_name in os.listdir("modules/wheels"): # 检测当前的轮子 + if wheel_name.endswith(".py") and (wheel_name != "__init__.py"): + wheels_name.append(wheel_name[:-3]) + + for plugin_name in enable_plugins: # 加载已注册的插件 + try: + file = open("modules/plugins/%s/index.json" % plugin_name) + plugin_info = json.load(file) + file.close() + for wheel_ in plugin_info["depended-wheels"]: + if wheel_ not in wheels_name: + raise DependenceError("No wheel named %s!" % wheel_) + plugins[plugin_name] = importlib.import_module( + "modules.plugins.%s.index" % plugin_name) + + except FileNotFoundError and json.JSONDecodeError and DependenceError: + logger_main.error("插件[%s]加载失败:\n" + traceback.format_exc()) + + plugin_init_lock = threading.Barrier(len(plugins) + 1) + + def plugin_init(plugin): + plugin.init(env) + plugin_init_lock.wait() + + for value in plugins.values(): + env.pool.add(plugin_init, value) + + theme_name = configurator_main.read( + "enable_theme", raise_error=True) # 加载主题 + try: + file = open("modules/themes/%s/index.json" % theme_name) + theme_info = json.load(file) + file.close() + for wheel_ in theme_info["depended-wheels"]: + if wheel_ not in wheels_name: + raise DependenceError("No wheel named %s!" % wheel_) + for plugin_ in theme_info["depended-plugins"]: + if plugin_ not in plugins: + raise DependenceError("No plugin named %s!" % plugin_) + try: + if theme_info["icon_18px"]: + icon_18px = theme_info["icon_18px"] + else: + icon_18px = None + except: + icon_18px = None + try: + if theme_info["icon_20px"]: + icon_20px = theme_info["icon_20px"] + else: + icon_20px = None + except: + icon_20px = None + theme = [importlib.import_module( + "modules.themes.%s.index" % theme_name), (icon_18px, icon_20px)] + except FileNotFoundError and json.JSONDecodeError and DependenceError: + logger_main.error("主题[%s]加载失败:\n" + traceback.format_exc()) + try: + file = open("module/themes/default/index.json" % theme_name) + theme_info = json.load(file) + file.close() + except FileNotFoundError and json.JSONDecodeError and DependenceError as e: + logger_main.error("默认主题加载失败:\n%s程序已退出" % + traceback.format_exc()) + raise e + + enable_apps = configurator_main.read( + "enable_apps", raise_error=True) # 加载程序 + for app_name in enable_apps: + try: + file = open("modules/apps/%s/index.json" % app_name) + app_info = json.load(file) + file.close() + for wheel_ in app_info["depended-wheels"]: + if wheel_ not in wheels_name: + raise DependenceError("No wheel named %s!" % wheel_) + for plugin_ in app_info["depended-plugins"]: + if plugin_ not in plugins: + raise DependenceError("No plugin named %s!" % plugin_) + try: + if app_info["icon_18px"]: + icon_18px = app_info["icon_18px"] + else: + icon_18px = None + except: + icon_18px = None + try: + if app_info["icon_20px"]: + icon_20px = app_info["icon_20px"] + else: + icon_20px = None + except: + icon_20px = None + apps[app_name] = [importlib.import_module("modules.apps.%s.index" % app_name), + (icon_18px, icon_20px), None] + except FileNotFoundError and json.JSONDecodeError and DependenceError: + logger_main.error("程序[%s]加载失败:\n" + traceback.format_exc()) + + plugin_init_lock.wait(timeout=5) + load_lock.wait() + # 主程序开始 + env.init(theme[0].build(env), plugins, apps) + + if environment.DEV: # 调试环境 + pass # 模拟器会处理点击事件,所以就pass掉了 + else: # 真机环境 + while 1: # 据说 while 1 的效率比 while True 高 + env.touchpad_driver.ICNT_Scan(touch_recoder_new, touch_recoder_old) + env.touch_handler.handle(touch_recoder_new, touch_recoder_old) + + + except KeyboardInterrupt: + print("ctrl+c") + except: # ⚠️只在生产环境使用 会影响调试结果!!! + logger_main.error(traceback.format_exc()) + finally: + env.epd_driver.sleep() + env.epd_driver.exit() + + + +if __name__ == "__main__": + + logger_main = logger.Logger(logger.DEBUG) # 日志 + + configurator_main = configurator.Configurator(logger_main) # 配置 + configurator_main.check(example_config, True) + configurator_main.change_path("/main") + + if environment.DEV: #判断环境 + simulator = environment.Simulator() # 我是一个模拟器 + env = environment.Env(configurator_main.read( + "env_configs"), logger_main, simulator) # 有模拟器的env + mainThrd = threading.Thread(target=mainThread, daemon=True) # 因为模拟器必须得是主线程 + mainThrd.start() # 原来的主线程就得让位了~ + + simulator.open(env) # 打开模拟器 + + else: + env = environment.Env(configurator_main.read( + "env_configs"), logger_main) # 真机env + mainThread() #主线程 diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 0000000000000000000000000000000000000000..746cb11db39e779676d22ade11c0c824d822c74c --- /dev/null +++ b/modules/README.md @@ -0,0 +1 @@ +此处用于存放插件与主题(即表盘) \ No newline at end of file diff --git a/modules/apps/hello_world/__init__.py b/modules/apps/hello_world/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/apps/hello_world/index.json b/modules/apps/hello_world/index.json new file mode 100644 index 0000000000000000000000000000000000000000..0cc33678d261ee40a9e32ae2e07090612af2c0e1 --- /dev/null +++ b/modules/apps/hello_world/index.json @@ -0,0 +1,9 @@ +{ + "author": "fu1fan", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": null +} \ No newline at end of file diff --git a/modules/apps/hello_world/index.py b/modules/apps/hello_world/index.py new file mode 100644 index 0000000000000000000000000000000000000000..198ef98b5bbe6f4f0bb061a7f28460933a4a8c27 --- /dev/null +++ b/modules/apps/hello_world/index.py @@ -0,0 +1,18 @@ +from sdk.graphics import paper_lib, element_lib + + +def build(env): + + paper = paper_lib.PaperApp(env) + + text_label = element_lib.Label( + (100, 40), paper, "Hello world!", (150, 30), bgcolor="black", textColor="white") + + def changeText(): + text_label.setText("Button Clicked!") + + paper.addElement("mainPage", text_label) + paper.addElement("mainPage", element_lib.Button( + (100, 80), paper, "I'm a button", changeText, (150, 30), bgcolor="black", textColor="white")) + + return paper diff --git "a/modules/apps/\347\245\226\345\256\211\345\256\235\345\205\270/index.json" "b/modules/apps/\347\245\226\345\256\211\345\256\235\345\205\270/index.json" new file mode 100644 index 0000000000000000000000000000000000000000..d1366b5ba44dc6d79a5bf50b0f06bee5862a7fe4 --- /dev/null +++ "b/modules/apps/\347\245\226\345\256\211\345\256\235\345\205\270/index.json" @@ -0,0 +1,9 @@ +{ + "author": "xuanzhi33", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": "resources/images/zuan.png" + } \ No newline at end of file diff --git "a/modules/apps/\347\245\226\345\256\211\345\256\235\345\205\270/index.py" "b/modules/apps/\347\245\226\345\256\211\345\256\235\345\205\270/index.py" new file mode 100644 index 0000000000000000000000000000000000000000..2eb982d95f3a6eef2456eb68a402c6a5caa90cd3 --- /dev/null +++ "b/modules/apps/\347\245\226\345\256\211\345\256\235\345\205\270/index.py" @@ -0,0 +1,30 @@ +import threading +import sdk.graphics.paper_lib +from sdk.graphics import element_lib +import requests +import json + + +def build(env): + + paper = sdk.graphics.paper_lib.PaperApp(env) + zuanLabel = element_lib.LabelWithMultipleLines((5, 35), paper, "加载中...", (282, 80), "black", "white") + def getZuAn(): + zuan = requests.get("https://api.shadiao.app/nmsl?level=min").text + zuan = json.loads(zuan)["data"]["text"] + zuanLabel.setText(zuan) + + + + paper.addElement("mainPage", element_lib.Label( + (100, 0), paper, "祖安宝典", (150, 30), bgcolor="black", textColor="white")) + + paper.addElement("mainPage", zuanLabel) + + getThread = threading.Thread(target=getZuAn) # 使用子线程请求api + getThread.start() # 启动子线程 + + paper.addElement("mainPage",element_lib.Button((100, 98), paper, "换一个", getZuAn, (75, 30), "white", "black")) + + + return paper diff --git "a/modules/apps/\347\256\200\345\215\225\345\267\245\345\205\267/index.json" "b/modules/apps/\347\256\200\345\215\225\345\267\245\345\205\267/index.json" new file mode 100644 index 0000000000000000000000000000000000000000..feba647189a347a3016d12de7e1ea80cc875f76d --- /dev/null +++ "b/modules/apps/\347\256\200\345\215\225\345\267\245\345\205\267/index.json" @@ -0,0 +1,9 @@ +{ + "author": "xuanzhi33", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": "resources/images/jian.png" + } \ No newline at end of file diff --git "a/modules/apps/\347\256\200\345\215\225\345\267\245\345\205\267/index.py" "b/modules/apps/\347\256\200\345\215\225\345\267\245\345\205\267/index.py" new file mode 100644 index 0000000000000000000000000000000000000000..f833b18bd66de27094858c034cd8be0045ba9969 --- /dev/null +++ "b/modules/apps/\347\256\200\345\215\225\345\267\245\345\205\267/index.py" @@ -0,0 +1,25 @@ +from sdk.graphics import paper_lib, element_lib, page_lib + + +def build(env): + paper = paper_lib.PaperApp(env) + + def toolsItemOncick(index): + if index == 0: + paper.env.openApp("随机数生成器") + elif index == 1: + paper.env.openApp("简单计算器") + elif index == 2: + paper.env.openApp("祖安宝典") + + tools = [ + ["随机数生成器", "resources/images/random.png", toolsItemOncick], + ["简单计算器", "resources/images/calculator.png", toolsItemOncick], + ["祖安宝典", "resources/images/zuan.png",toolsItemOncick] + ] + + toolsList = page_lib.ListPage(paper,"mainPage") + paper.pages["mainPage"] = toolsList + toolsList.show(tools, "简单工具", None) + + return paper diff --git "a/modules/apps/\347\256\200\345\215\225\346\270\205\345\215\225/index.json" "b/modules/apps/\347\256\200\345\215\225\346\270\205\345\215\225/index.json" new file mode 100644 index 0000000000000000000000000000000000000000..874131df8b5dbfc7738fb6cca7dc57e53c2dafb7 --- /dev/null +++ "b/modules/apps/\347\256\200\345\215\225\346\270\205\345\215\225/index.json" @@ -0,0 +1,9 @@ +{ + "author": "xuanzhi33", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": "resources/images/todo.png" + } \ No newline at end of file diff --git "a/modules/apps/\347\256\200\345\215\225\346\270\205\345\215\225/index.py" "b/modules/apps/\347\256\200\345\215\225\346\270\205\345\215\225/index.py" new file mode 100644 index 0000000000000000000000000000000000000000..bbd205c614d7566905b89c2dcf332aabe05326db --- /dev/null +++ "b/modules/apps/\347\256\200\345\215\225\346\270\205\345\215\225/index.py" @@ -0,0 +1,77 @@ +from sdk.graphics import page_lib, paper_lib, element_lib +import sdk.configurator +import requests +import threading +import json + + +mainBtn = None +listJson = None + +def build(env): + global mainBtn + + paper = paper_lib.PaperApp(env) + + config = sdk.configurator.Configurator( + env.logger_env, "configs/account.json", auto_save=True) + username = config.read("user/name") + usertoken = config.read("user/token") + + def showTodo(): + + def loadTodo(): + global listJson + todoListPage = page_lib.ListPage(paper,"todoList") + paper.addPage("todoList", todoListPage) + listJson = requests.post("https://pi.simplebytes.cn/api/todo.php", + {"name": username, "token": usertoken}).text + listJson = json.loads(listJson) + todoListTitle = listJson[0]["title"] + todoListContent = [] + def todoItemClickHandler(): + pass + + for item in listJson[0]["content"]: + if not item["finished"]: + imgpath = "resources/images/unfinished.png" + todoListContent.append([item["name"], imgpath, todoItemClickHandler]) + + for item in listJson[0]["content"]: + if item["finished"]: + imgpath = "resources/images/ok.png" + todoListContent.append([item["name"], imgpath, todoItemClickHandler]) + + + todoListPage.show(todoListContent, todoListTitle, backToMain) + paper.changePage("todoList") + + mainBtn.setText("加载中...") + loadTodoThread = threading.Thread(target=loadTodo) + loadTodoThread.start() + + def bindAccount(): + paper.env.openApp("账号管理") + + if (username and usertoken): + mainBtn = element_lib.Button( + (0, 35), paper, "显示您第一个清单", showTodo, (296, 30), "white", "black" + ) + else: + mainBtn = element_lib.Button( + (0, 35), paper, "点击绑定账号", bindAccount, (296, 30), "white", "black" + ) + + + + def backToMain(): + mainBtn.setText("显示您第一个清单") + paper.changePage("mainPage") + + + paper.addElement("mainPage", element_lib.Label( + (100, 0), paper, "简单清单", (150, 30), bgcolor="black", textColor="white")) + + paper.addElement("mainPage", mainBtn) + + return paper diff --git "a/modules/apps/\347\256\200\345\215\225\350\256\241\347\256\227\345\231\250/index.json" "b/modules/apps/\347\256\200\345\215\225\350\256\241\347\256\227\345\231\250/index.json" new file mode 100644 index 0000000000000000000000000000000000000000..938b75e93b4036f886a321f75a03a88a60636255 --- /dev/null +++ "b/modules/apps/\347\256\200\345\215\225\350\256\241\347\256\227\345\231\250/index.json" @@ -0,0 +1,9 @@ +{ + "author": "xuanzhi33", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": "resources/images/calculator.png" + } \ No newline at end of file diff --git "a/modules/apps/\347\256\200\345\215\225\350\256\241\347\256\227\345\231\250/index.py" "b/modules/apps/\347\256\200\345\215\225\350\256\241\347\256\227\345\231\250/index.py" new file mode 100644 index 0000000000000000000000000000000000000000..5b5c135b1eb6276d04b7d627241956e3d5105fcb --- /dev/null +++ "b/modules/apps/\347\256\200\345\215\225\350\256\241\347\256\227\345\231\250/index.py" @@ -0,0 +1,15 @@ +from sdk.graphics import paper_lib, element_lib + + +def build(env): + + paper = paper_lib.PaperApp(env) + + number_label = element_lib.Label( + (100, 40), paper, "Hello world!", (150, 30), bgcolor="black", textColor="white") + + + #paper.addElement("mainPage", element_lib.Button( + # (100, 80), paper, "I'm a button", , (150, 30), bgcolor="black", textColor="white")) + + return paper diff --git "a/modules/apps/\347\263\273\347\273\237\350\256\276\347\275\256/index.json" "b/modules/apps/\347\263\273\347\273\237\350\256\276\347\275\256/index.json" new file mode 100644 index 0000000000000000000000000000000000000000..f71606992414c58a6267dee53c7196b8ded3ad66 --- /dev/null +++ "b/modules/apps/\347\263\273\347\273\237\350\256\276\347\275\256/index.json" @@ -0,0 +1,9 @@ +{ + "author": "xuanzhi33", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": "resources/images/settings.png" + } \ No newline at end of file diff --git "a/modules/apps/\347\263\273\347\273\237\350\256\276\347\275\256/index.py" "b/modules/apps/\347\263\273\347\273\237\350\256\276\347\275\256/index.py" new file mode 100644 index 0000000000000000000000000000000000000000..ed9bd192e78b6315df10e181aa0b6121ed1e1242 --- /dev/null +++ "b/modules/apps/\347\263\273\347\273\237\350\256\276\347\275\256/index.py" @@ -0,0 +1,23 @@ +from sdk.graphics import paper_lib, element_lib, page_lib + + +def build(env): + paper = paper_lib.PaperApp(env) + + def settingItemOncick(index): + print(index) + if index == 0: + paper.env.openApp("账号管理") + elif index == 1: + paper.changePage["wifi"] + + settings = [ + ["账号管理", "resources/images/settings.png", settingItemOncick], + ["网络设置(暂未完工)", None, None], + ] + + settingsList = page_lib.ListPage(paper,"mainPage") + paper.pages["mainPage"] = settingsList + settingsList.show(settings, "系统设置", None) + + return paper diff --git "a/modules/apps/\350\264\246\345\217\267\347\256\241\347\220\206/index.json" "b/modules/apps/\350\264\246\345\217\267\347\256\241\347\220\206/index.json" new file mode 100644 index 0000000000000000000000000000000000000000..633666548ca30a7070e225793c13ca3ae1efb258 --- /dev/null +++ "b/modules/apps/\350\264\246\345\217\267\347\256\241\347\220\206/index.json" @@ -0,0 +1,9 @@ +{ + "author": "xuanzhi33", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": "resources/images/account.png" + } \ No newline at end of file diff --git "a/modules/apps/\350\264\246\345\217\267\347\256\241\347\220\206/index.py" "b/modules/apps/\350\264\246\345\217\267\347\256\241\347\220\206/index.py" new file mode 100644 index 0000000000000000000000000000000000000000..394090b18f7cfc1717f08bbf4f5493102d4429d2 --- /dev/null +++ "b/modules/apps/\350\264\246\345\217\267\347\256\241\347\220\206/index.py" @@ -0,0 +1,137 @@ +import threading +import sdk.graphics +import sdk.graphics.element_lib +import sdk.graphics.paper_lib + +import requests +import json + + + +import sdk.configurator + +paper = None +configurator = None +infoLabel = None +actionButton = None +paircode = 0 + +def backToMain(): + paper.pause_update() # 上锁,防止setText重复刷新屏幕 + + paper.changePage("mainPage") + refreshMain() + + paper.recover_update() # 解锁 + + + +def logout(): + configurator.delete("user") + paper.pause_update() # 上锁,防止setText重复刷新屏幕 + refreshMain() + paper.recover_update() # 解锁 + +def refreshMain(): + + if (configurator.read("user/name")): + infoLabel.setText("你好,"+configurator.read("user/name")) + actionButton.setText("点击退出账号") + actionButton.setOnclick(logout) + else: + infoLabel.setText("暂未绑定账号") + actionButton.setText("点击绑定账号") + actionButton.setOnclick(pair) + + +def pair(): + + codeLabel = sdk.graphics.element_lib.Label( + (0, 88), paper, "请稍等...", (169, 40), bgcolor="black", textColor="white", fontSize=30) + paper.addElement("pairPage", codeLabel) + + def getPairCode(): + global paircode + paircode = json.loads(requests.get( + "https://pi.simplebytes.cn/api/getPairCode.php").text)["paircode"] + codeLabel.setText(str(paircode)) + getPairCodeThread = threading.Thread(target=getPairCode) + getPairCodeThread.start() + + def nextStep(): + resultLabel = sdk.graphics.element_lib.Label( + (0, 30), paper, "请稍等片刻...", (296, 30), "black", "white") + def getResult(): + result = requests.post( + "https://pi.simplebytes.cn/api/getPairInfo.php", {"paircode": paircode}).text + result = json.loads(result) + if (result["msg"] != "WAIT_PAIRING"): + msg = "已绑定:"+result["username"] + configurator.set("user/name", result["username"]) + configurator.set("user/token", result["usertoken"]) + + else: + msg = "输完配对码后再点下一步哦" + + resultLabel.setText(msg) + + paper.addElement("nextPage", resultLabel) + + paper.addElement("nextPage", sdk.graphics.element_lib.Button( + (0, 70), paper, "返回首页", backToMain, (296, 30), "white", "black")) + + getResultThread = threading.Thread(target=getResult) + getResultThread.start() + + paper.changePage("nextPage") + + paper.addElement("pairPage", sdk.graphics.element_lib.Label( + (100, 0), paper, "绑定账号", (150, 30), bgcolor="black", textColor="white")) + paper.addElement("pairPage", sdk.graphics.element_lib.Label( + (0, 30), paper, "请访问 pi.simplebytes.cn", (296, 30), bgcolor="black", textColor="white")) + paper.addElement("pairPage", sdk.graphics.element_lib.Label( + (0, 60), paper, "登录后,输入下方的配对码", (296, 30), bgcolor="black", textColor="white")) + paper.addElement("pairPage", sdk.graphics.element_lib.Button( + (170, 90), paper, "下一步", nextStep, (85, 35), "white", "black", fontSize=24)) + + paper.changePage("pairPage") + + + + +def build(env): + global paper + global configurator + global infoLabel + global actionButton + + configurator = sdk.configurator.Configurator( + env.logger_env, "configs/account.json", auto_save=True) + + + paper = sdk.graphics.paper_lib.PaperApp(env) + + paper.addElement("mainPage", sdk.graphics.element_lib.Label( + (100, 0), paper, "账号管理", (150, 30), bgcolor="black", textColor="white")) + + if (configurator.read("user/name")): + infoLabel = sdk.graphics.element_lib.Label( + (0, 35), paper, "你好,"+configurator.read("user/name"), (296, 30), bgcolor="black", textColor="white") + + actionButton = sdk.graphics.element_lib.Button( + (0, 95), paper, "点击退出账号", logout, (296, 30), "white", "black") + else: + infoLabel = sdk.graphics.element_lib.Label( + (0, 35), paper, "暂未绑定账号", (296, 30), bgcolor="black", textColor="white") + + actionButton = sdk.graphics.element_lib.Button( + (0, 95), paper, "点击绑定账号", pair, (296, 30), "white", "black") + + + paper.addElement("mainPage", infoLabel) + paper.addElement("mainPage", actionButton) + + paper.addPage("pairPage") + paper.addPage("nextPage") + + return paper diff --git "a/modules/apps/\351\232\217\346\234\272\346\225\260\347\224\237\346\210\220\345\231\250/index.json" "b/modules/apps/\351\232\217\346\234\272\346\225\260\347\224\237\346\210\220\345\231\250/index.json" new file mode 100644 index 0000000000000000000000000000000000000000..86acf0014d7c8b7ba7b4a10bda1f91fb3df10330 --- /dev/null +++ "b/modules/apps/\351\232\217\346\234\272\346\225\260\347\224\237\346\210\220\345\231\250/index.json" @@ -0,0 +1,9 @@ +{ + "author": "xuanzhi33", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [], + "icon_18px": null, + "icon_20px": "resources/images/random.png" + } \ No newline at end of file diff --git "a/modules/apps/\351\232\217\346\234\272\346\225\260\347\224\237\346\210\220\345\231\250/index.py" "b/modules/apps/\351\232\217\346\234\272\346\225\260\347\224\237\346\210\220\345\231\250/index.py" new file mode 100644 index 0000000000000000000000000000000000000000..0e5753e31399de391c2a42aedef9345977c554bd --- /dev/null +++ "b/modules/apps/\351\232\217\346\234\272\346\225\260\347\224\237\346\210\220\345\231\250/index.py" @@ -0,0 +1,12 @@ +from sdk.graphics import paper_lib, element_lib + + +def build(env): + + paper = paper_lib.PaperApp(env) + + paper.addElement("mainPage", element_lib.Label( + (100, 0), paper, "随机数生成器", (150, 30), bgcolor="black", textColor="white")) + + + return paper diff --git a/modules/plugins/hello_world/__init__.py b/modules/plugins/hello_world/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/plugins/hello_world/index.json b/modules/plugins/hello_world/index.json new file mode 100644 index 0000000000000000000000000000000000000000..31791d96b1a9459619c19d7c928eb52f83bb1fa9 --- /dev/null +++ b/modules/plugins/hello_world/index.json @@ -0,0 +1,8 @@ +{ + "author": "fu1fan", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "icon_18px": null, + "icon_20px": null +} \ No newline at end of file diff --git a/modules/plugins/hello_world/index.py b/modules/plugins/hello_world/index.py new file mode 100644 index 0000000000000000000000000000000000000000..576f4a7287d1c1ae9606cf9bfa12d25ee2924910 --- /dev/null +++ b/modules/plugins/hello_world/index.py @@ -0,0 +1,2 @@ +def init(env): + print("Hello world!") \ No newline at end of file diff --git a/modules/themes/default/__init__.py b/modules/themes/default/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/themes/default/index.json b/modules/themes/default/index.json new file mode 100644 index 0000000000000000000000000000000000000000..2f4f40950bcd4f28cd9d4d03e83f4fa2b2790086 --- /dev/null +++ b/modules/themes/default/index.json @@ -0,0 +1,7 @@ +{ + "author": "fu1fan", + "version": 1, + "adapted-version": "all", + "depended-wheels": [], + "depended-plugins": [] +} \ No newline at end of file diff --git a/modules/themes/default/index.py b/modules/themes/default/index.py new file mode 100644 index 0000000000000000000000000000000000000000..2b4a39b6707424b191d40540bdafb47710bd59a1 --- /dev/null +++ b/modules/themes/default/index.py @@ -0,0 +1,62 @@ +import threading +import time + +from PIL import Image, ImageFont, ImageDraw + +import sdk.graphics.element_lib +import sdk.graphics.paper_lib +from main import environment + +graphics = environment.graphics + + +class TextClock(graphics.Element): + def __init__(self, xy, paper): + super().__init__(xy, paper, (296, 128)) + self.last_update = -1 + self.image = Image.new("RGB", (296, 128), 0) + self.stop_sign = False + self.font25 = ImageFont.truetype( + "resources/fonts/PTSerifCaption.ttc", 53) + + def update(self): + while True: + if self.stop_sign: + return + if self.last_update != time.localtime(time.time()).tm_min: + self.paper.update(self.page.name) + time.sleep(1) + + def init(self): + t = threading.Thread(target=self.update) + t.setDaemon(True) + t.start() + + def exit(self): + self.stop_sign = True + + def build(self) -> Image: + now_time = time.strftime("%H : %M", time.localtime()) + now_image = self.image.copy() + draw_image = ImageDraw.Draw(now_image) + draw_image.text((58, 32), now_time, font=self.font25) + self.last_update = time.localtime(time.time()).tm_min + return now_image + + +def build(env: environment): + paper = sdk.graphics.paper_lib.PaperTheme(env) + # refreshBtn = sdk.graphics.lib.Button((0, 0), paper, "刷新", paper.refresh) + # textLabel = sdk.graphics.element_lib.Label((0, 90), paper, "标签1") + + # testBtn = sdk.graphics.lib.Button( + # (60, 90), paper, "测试", changeTheTextOfLabal) + + text_clock = TextClock((0, 0), paper) + paper.addElement("mainPage", text_clock) + + # paper.addElement("mainPage", refreshBtn) + # paper.addElement("mainPage", textLabel) + # paper.addElement("mainPage", testBtn) + + return paper diff --git a/modules/wheels/__init__.py b/modules/wheels/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/resources/README.md b/resources/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c73322aa0d4341c9d1380aee05daf979a778db36 --- /dev/null +++ b/resources/README.md @@ -0,0 +1 @@ +这里将用于储存用于构建系统洁面的资源文件。 \ No newline at end of file diff --git a/resources/fonts/PTSerifCaption.ttc b/resources/fonts/PTSerifCaption.ttc new file mode 100644 index 0000000000000000000000000000000000000000..07a2d57e432a466f4469b2eb1a0c5283e0955f11 Binary files /dev/null and b/resources/fonts/PTSerifCaption.ttc differ diff --git a/resources/fonts/STHeiti_Light.ttc b/resources/fonts/STHeiti_Light.ttc new file mode 100644 index 0000000000000000000000000000000000000000..ab62947f26000aa40bd00b3790e7f926a1a34eef Binary files /dev/null and b/resources/fonts/STHeiti_Light.ttc differ diff --git a/resources/fonts/STHeiti_Medium.ttc b/resources/fonts/STHeiti_Medium.ttc new file mode 100644 index 0000000000000000000000000000000000000000..fb746ab4653c91e0eaa40020e785c23be45638cb Binary files /dev/null and b/resources/fonts/STHeiti_Medium.ttc differ diff --git a/resources/images/None18px.jpg b/resources/images/None18px.jpg new file mode 100644 index 0000000000000000000000000000000000000000..caf2488960cf3f802140fddcf9afacf2f0044a21 Binary files /dev/null and b/resources/images/None18px.jpg differ diff --git a/resources/images/None1px.jpg b/resources/images/None1px.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb02639f2a0a8abf962d7729cb118651571f0ff5 Binary files /dev/null and b/resources/images/None1px.jpg differ diff --git a/resources/images/None20px.jpg b/resources/images/None20px.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e642e6566a2b4421a9895dbea41681bdc164bc0d Binary files /dev/null and b/resources/images/None20px.jpg differ diff --git a/resources/images/account.png b/resources/images/account.png new file mode 100644 index 0000000000000000000000000000000000000000..a9168954113d518763f2dbf93df799abd91eea17 Binary files /dev/null and b/resources/images/account.png differ diff --git a/resources/images/calculator.png b/resources/images/calculator.png new file mode 100644 index 0000000000000000000000000000000000000000..8da1fa29fe8828feb1d6f0d5c5b4a192fc38a692 Binary files /dev/null and b/resources/images/calculator.png differ diff --git a/resources/images/change.jpg b/resources/images/change.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2d3122a8f19d5790590f078d71af2eab57c9a8c Binary files /dev/null and b/resources/images/change.jpg differ diff --git a/resources/images/docker.jpg b/resources/images/docker.jpg new file mode 100644 index 0000000000000000000000000000000000000000..978f702c1a4752252d0a04a25840c74479efe768 Binary files /dev/null and b/resources/images/docker.jpg differ diff --git a/resources/images/done.jpg b/resources/images/done.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d8238cef1f8db26064fa68939cb2b74ae938dc51 Binary files /dev/null and b/resources/images/done.jpg differ diff --git a/resources/images/github.jpg b/resources/images/github.jpg new file mode 100644 index 0000000000000000000000000000000000000000..75efa4dfd8df36e83394f92f1d3494793b7803bc Binary files /dev/null and b/resources/images/github.jpg differ diff --git a/resources/images/jian.png b/resources/images/jian.png new file mode 100644 index 0000000000000000000000000000000000000000..ad9cb24d44b251eec7f4ed03037e8c17c3ac6b2d Binary files /dev/null and b/resources/images/jian.png differ diff --git a/resources/images/list.jpg b/resources/images/list.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0e2ef46ceec7b825a508f95e77ce26b37692908c Binary files /dev/null and b/resources/images/list.jpg differ diff --git a/resources/images/loading.jpg b/resources/images/loading.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8acf2699a225e58671bea52ac40f4f6998bf1591 Binary files /dev/null and b/resources/images/loading.jpg differ diff --git a/resources/images/more_items_dots.jpg b/resources/images/more_items_dots.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f450a13af6f49d9cc263ff58801da72e7649d39 Binary files /dev/null and b/resources/images/more_items_dots.jpg differ diff --git a/resources/images/ok.png b/resources/images/ok.png new file mode 100644 index 0000000000000000000000000000000000000000..e6cf7080c0eac40d1aa6fcbc7ac903fc08338835 Binary files /dev/null and b/resources/images/ok.png differ diff --git a/resources/images/random.png b/resources/images/random.png new file mode 100644 index 0000000000000000000000000000000000000000..4e11d2eb8add2474e5c43ec79edf69f8a9a193d4 Binary files /dev/null and b/resources/images/random.png differ diff --git a/resources/images/raspberry.jpg b/resources/images/raspberry.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d155313e08a4f0179b82c0d2c60d7c15b747fc09 Binary files /dev/null and b/resources/images/raspberry.jpg differ diff --git a/resources/images/reset.jpg b/resources/images/reset.jpg new file mode 100644 index 0000000000000000000000000000000000000000..174c04cc4201d22afaadc218a651040141ff9bce Binary files /dev/null and b/resources/images/reset.jpg differ diff --git a/resources/images/settings.png b/resources/images/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..be3cab5b19fe32ced31419cae21c2496b927edab Binary files /dev/null and b/resources/images/settings.png differ diff --git a/resources/images/simplebytes.jpg b/resources/images/simplebytes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9203fceff70fbee84d25c11969e579500fd1d0a0 Binary files /dev/null and b/resources/images/simplebytes.jpg differ diff --git a/resources/images/todo.png b/resources/images/todo.png new file mode 100644 index 0000000000000000000000000000000000000000..83e50b557be85ef176b8eabdcd17225be4dcd73f Binary files /dev/null and b/resources/images/todo.png differ diff --git a/resources/images/unfinished.png b/resources/images/unfinished.png new file mode 100644 index 0000000000000000000000000000000000000000..b050c09c4b5600aacbdd45a78b8599c07f127abb Binary files /dev/null and b/resources/images/unfinished.png differ diff --git a/resources/images/updating.jpg b/resources/images/updating.jpg new file mode 100644 index 0000000000000000000000000000000000000000..755cefede0f9552df01cba3c7a8076bae6c21e18 Binary files /dev/null and b/resources/images/updating.jpg differ diff --git a/resources/images/zuan.png b/resources/images/zuan.png new file mode 100644 index 0000000000000000000000000000000000000000..d4e92e05f4ed38a1d3649cf8e964ce59a24f2c3f Binary files /dev/null and b/resources/images/zuan.png differ diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 0000000000000000000000000000000000000000..649f3281057f678767de2b8cf191c8db353471a1 --- /dev/null +++ b/sdk/README.md @@ -0,0 +1 @@ +sdk已移动至此 \ No newline at end of file diff --git a/sdk/__init__.py b/sdk/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sdk/configurator.py b/sdk/configurator.py new file mode 100644 index 0000000000000000000000000000000000000000..c9afc7460086acaa0cf35c2033ca679724a339da --- /dev/null +++ b/sdk/configurator.py @@ -0,0 +1,171 @@ +import os +import json +from sdk import logger + +default_config_path = "configs/config.json" +try: + os.mkdir("configs") +except OSError: + pass + + +class TargetExists(Exception): + pass + + +class Configurator: # 没有对多线程进行适配,需要自行加锁 + def __init__(self, logger_config: logger.Logger, file_path=default_config_path, auto_save=False): + self.path = file_path + self.__current_path = "/" + self.logger_config = logger_config + self.file_path = file_path + if os.path.exists(file_path): + try: + file = open(file_path, "r") + self.config = json.load(file) + file.close() + except json.decoder.JSONDecodeError: + os.rename(default_config_path, default_config_path + ".bk") + file = open(file_path, "w") + file.write("{}") + file.close() + self.config = {} + logger_config.warn("配置文件不是json文件,已备份到 '%s.bk' 并已创建新的空白文件" % file_path) + else: + file = open(file_path, "w") + file.write("{}") + file.close() + self.config = {} + self.current_config = self.config + self.auto_save = auto_save + + @property + def current_path(self): + return self.__current_path + + def save(self): + file = open(self.path, "w") + json.dump(self.config, file, indent=4) + file.close() + + def change_path(self, path): + if path[0] != "/": + raise ValueError("The path of target must start with '/'!") + path = path[1:].split("/") + # 检查合法性 + finder = self.config + for i in range(len(path)): + try: + finder = finder[path[i]] + if type(finder) != dict: + raise ValueError("Target path does not a dict!") + except KeyError: + raise ValueError("The target does not exist!") + # 设置目录 + self.current_config = finder + + def readOrCreate(self, path: str, default_value): + if path[0] == "/": + finder = self.config + path = path[1:] + else: + finder = self.current_config + path = path.split("/") + for i in range(len(path) - 1): + try: + finder = finder[path[i]] + except KeyError: + finder[path[i]] = {} + finder = finder[path[1]] + try: + return finder[path[-1]] + except KeyError: + finder[path[-1]] = default_value + if self.auto_save: + self.save() + return default_value + + def read(self, path, raise_error=False): + if path[0] == "/": + finder = self.config + path = path[1:] + else: + finder = self.current_config + path = path.split("/") + for i in range(len(path) - 1): + try: + finder = finder[path[i]] + except KeyError as e: + if raise_error: + raise e + return None + try: + return finder[path[-1]] + except KeyError as e: + if raise_error: + raise e + return None + + def set(self, path, value) -> bool: + if path[0] == "/": + finder = self.config + path = path[1:] + else: + finder = self.current_config + path = path.split("/") + for i in range(len(path) - 1): + try: + finder = finder[path[i]] + except KeyError: + finder[path[i]] = {} + finder = finder[path[i]] + finder[path[-1]] = value + if self.auto_save: + self.save() + return True + + def delete(self, path, raise_error=False) -> bool: + if path[0] == "/": + finder = self.config + path = path[1:] + else: + finder = self.current_config + path = path.split("/") + for i in range(len(path) - 1): + try: + finder = finder[path[i]] + except KeyError as e: + if raise_error: + raise e + return False + try: + del finder[path[-1]] + if self.auto_save: + self.save() + return True + except KeyError as e: + if raise_error: + raise e + return False + + def check(self, example: dict, fix=False) -> bool: + def fix_file(): + os.rename(default_config_path, default_config_path + ".bk") + file = open(self.file_path, "w") + json.dump(example, file, indent=4) + file.close() + self.config = example + self.logger_config.warn("配置文件已失效,已备份到 '%s.bk' 并已创建新的文件" % self.file_path) + + try: + for key, value in example.items(): + if type(self.current_config[key]) != type(value): + if fix: + fix_file() + return False + except KeyError: + if fix: + fix_file() + return False + + return True diff --git a/sdk/environment.py b/sdk/environment.py new file mode 100644 index 0000000000000000000000000000000000000000..9dbf7291ae522cd3edb9511b00f1187b145d9729 --- /dev/null +++ b/sdk/environment.py @@ -0,0 +1,288 @@ +import threading +import time + +from PIL import Image + +from sdk import logger, icnt86, epd2in9_V2 +from sdk import touchpad +from sdk import timing_task +from sdk import threadpool_mini +from sdk import graphics + +config = icnt86.config + + +DEV = False + + +class EpdController(epd2in9_V2.EPD_2IN9_V2): + """ + 用这个类来显示图片可能会被阻塞(当多个线程尝试访问屏幕时) + """ + + def __init__(self, + logger_: logger.Logger, + lock: threading.RLock, + init=True, + auto_sleep_time=30, + refresh_time=3600, + refresh_interval=20): + super().__init__() + self.last_update = time.time() + self.partial_time = 0 + self.refresh_time = refresh_time + self.refresh_interval = refresh_interval + self.__auto_sleep_time = auto_sleep_time + self.logger_ = logger_ + self.lock = lock + self.tk = timing_task.TimingTask(auto_sleep_time, self.controller) + self.sleep_status = threading.Lock() # 上锁表示休眠 + self.upside_down = False + if auto_sleep_time > 0: + self.tk.start() + if init: + self.init() + + def __del__(self): + self.exit() + + @property + def auto_sleep_time(self): + return self.__auto_sleep_time + + def set_auto_sleep_time(self, auto_sleep_time): + self.tk.cycle = auto_sleep_time + self.__auto_sleep_time = auto_sleep_time + if auto_sleep_time > 0: + self.tk.start() + else: + self.tk.stop() + + def controller(self): # 自动休眠 + if time.time() - self.last_update >= self.auto_sleep_time: + if not self.lock.acquire(blocking=False): + return + self.sleep() + self.lock.release() + + def init(self): + super().init() + try: + self.sleep_status.release() + except RuntimeError: + pass + self.logger_.debug("屏幕初始化") + + def display(self, image, timeout=-1): + if not self.lock.acquire(timeout=timeout): + raise TimeoutError + super().display(image) + self.lock.release() + self.last_update = time.time() + self.partial_time = 0 + + def display_Base(self, image, timeout=-1): + if not self.lock.acquire(timeout=timeout): + raise TimeoutError + super().display_Base(image) + self.lock.release() + self.last_update = time.time() + self.partial_time = 0 + + def display_Partial(self, image, timeout=-1): + if not self.lock.acquire(timeout=timeout): + raise TimeoutError + super().display_Partial(image) + self.lock.release() + self.last_update = time.time() + self.partial_time += 1 + + def display_Auto(self, image, timeout=-1): + if not self.lock.acquire(timeout=timeout): + raise TimeoutError + if (time.time() - self.last_update > self.refresh_time) or (self.partial_time >= self.refresh_interval): + self.display_Base(image, timeout) + else: + self.display_Partial_Wait(image, timeout) + self.lock.release() + + def display_Partial_Wait(self, image, timeout=-1): + if not self.lock.acquire(timeout=timeout): + raise TimeoutError + super().display_Partial_Wait(image) + self.lock.release() + self.last_update = time.time() + self.partial_time += 1 + + def clear(self, color, timeout=-1): + if not self.lock.acquire(timeout=timeout): + raise TimeoutError + super().clear(color) + self.lock.release() + self.last_update = time.time() + self.partial_time = 0 + + def exit(self): + self.tk.stop() + self.sleep() + super().exit() + + def sleep(self): + if self.sleep_status.acquire(blocking=False): + super().sleep() + self.logger_.debug("屏幕休眠") + + def get_buffer(self, image: Image.Image): + if self.upside_down: + return super().get_buffer(image.transpose(Image.ROTATE_180)) + else: + return super().get_buffer(image) + + def acquire(self, timeout=-1): + return self.lock.acquire(timeout=timeout) + + def release(self): + return self.lock.release() + + def set_upside_down(self, value: bool): + self.upside_down = value + + +class TouchDriver(icnt86.INCT86): + def __init__(self, logger_touch: logger): + super().__init__() + self.logger_touch = logger_touch + + def ICNT_Reset(self): + super().ICNT_Reset() + self.logger_touch.debug("触摸屏重置") + + def ICNT_ReadVersion(self): + buf = self.ICNT_Read(0x000a, 4) + self.logger_touch.debug("触摸屏的版本为:" + str(buf)) + + def ICNT_Init(self): + super().ICNT_Init() + self.logger_touch.debug("触摸屏初始化") + + def ICNT_Scan(self, ICNT_Dev: touchpad.TouchRecoder, ICNT_Old: touchpad.TouchRecoder): + mask = 0x00 + + ICNT_Old.Touch = ICNT_Dev.Touch + ICNT_Old.TouchGestureId = ICNT_Dev.TouchGestureId + ICNT_Old.TouchCount = ICNT_Dev.TouchCount + ICNT_Old.TouchEvenId = ICNT_Dev.TouchEvenId + ICNT_Old.X = ICNT_Dev.X.copy() + ICNT_Old.Y = ICNT_Dev.Y.copy() + ICNT_Old.P = ICNT_Dev.P.copy() + + n = None + for _ in range(100): + n = self.digital_read(self.INT) + if n == 0: + break + time.sleep(0.001) + + if n == 0: # 检测屏幕是否被点击,不是每次都能扫描出来 + ICNT_Dev.Touch = 1 + buf = self.ICNT_Read(0x1001, 1) + + if buf[0] == 0x00: + self.ICNT_Write(0x1001, mask) + config.delay_ms(1) + self.logger_touch.warn("touchpad buffers status is 0!") + return + else: + ICNT_Dev.TouchCount = buf[0] + + if ICNT_Dev.TouchCount > 5 or ICNT_Dev.TouchCount < 1: + self.ICNT_Write(0x1001, mask) + ICNT_Dev.TouchCount = 0 + self.logger_touch.warn("TouchCount number is wrong!") + return + + buf = self.ICNT_Read(0x1002, ICNT_Dev.TouchCount * 7) + self.ICNT_Write(0x1001, mask) + + for i in range(0, ICNT_Dev.TouchCount, 1): + ICNT_Dev.TouchEvenId[i] = buf[6 + 7 * i] + ICNT_Dev.X[i] = 295 - ((buf[2 + 7 * i] << 8) + buf[1 + 7 * i]) + ICNT_Dev.Y[i] = 127 - ((buf[4 + 7 * i] << 8) + buf[3 + 7 * i]) + ICNT_Dev.P[i] = buf[5 + 7 * i] + + return + else: + ICNT_Dev.Touch = 0 + return + + +class Env: + def __init__(self, configs, logger_env: logger.Logger): + self.logger_env = logger_env + self.epd_lock = threading.RLock() + self.epd_driver = EpdController(self.logger_env, + self.epd_lock, True, + auto_sleep_time=configs["auto_sleep_time"], + refresh_time=configs["refresh_time"], + refresh_interval=configs["refresh_interval"]) + if self.epd_driver.IsBusy(): + self.logger_env.error("The screen is busy!") + raise RuntimeError("The screen is busy!") + + self.pool = threadpool_mini.ThreadPool(configs["threadpool_size"], handler=logger_env.warn) + self.pool.start() + + self.touch_handler = touchpad.TouchHandler(self) + self.touchpad_driver = TouchDriver(self.logger_env) + self.touchpad_driver.ICNT_Init() + self.paper = None + self.plugins = None + self.apps = None + self.inited = False + self.theme = None + + def init(self, paper, plugins, apps): + if self.inited: + return + self.inited = True + self.theme = paper + self.paper = paper + self.plugins = plugins + self.apps = apps + self.paper.init() + + def changePaper(self, paper, exit_paper=False): + if not self.inited: + return + self.touch_handler.clear() + if exit_paper: + self.paper.exit() + else: + self.paper.pause() # pause()能暂停页面 + self.paper = paper + if paper.inited: + self.paper.recover() + else: + self.paper.init() + + def openApp(self, appName, exit_paper=False): + if not self.inited: + return + if appName in self.apps: + if self.apps[appName][2] is None: + self.apps[appName][2] = self.apps[appName][0].build(self) + self.changePaper(self.apps[appName][2], exit_paper=exit_paper) + + def backHome(self, exit_paper=False): + if not self.inited: + return + self.touch_handler.clear() + if exit_paper: + self.paper.exit() + else: + self.paper.pause() # pause()能暂停页面 + self.paper = self.theme + if self.theme.inited: + self.paper.recover() + else: + self.paper.init() diff --git a/sdk/environment_dev.py b/sdk/environment_dev.py new file mode 100644 index 0000000000000000000000000000000000000000..e5ce5341a79c738767cd89729a2483b083344589 --- /dev/null +++ b/sdk/environment_dev.py @@ -0,0 +1,324 @@ +import threading +import time + +from PIL import Image,ImageTk + +from sdk import logger +from sdk import touchpad +from sdk import graphics +from sdk import timing_task +from sdk import threadpool_mini + +import tkinter + + +DEV = True + + +class Simulator: + def SIM_touch(self,x,y,ICNT_Dev: touchpad.TouchRecoder, ICNT_Old: touchpad.TouchRecoder): + + ICNT_Old.Touch = ICNT_Dev.Touch + ICNT_Old.TouchGestureId = ICNT_Dev.TouchGestureId + ICNT_Old.TouchCount = ICNT_Dev.TouchCount + ICNT_Old.TouchEvenId = ICNT_Dev.TouchEvenId + ICNT_Old.X = ICNT_Dev.X.copy() + ICNT_Old.Y = ICNT_Dev.Y.copy() + ICNT_Old.P = ICNT_Dev.P.copy() + if x is None or y is None: + ICNT_Dev.Touch = 0 + else: + ICNT_Dev.Touch = 1 + ICNT_Dev.X[0] = x + ICNT_Dev.Y[0] = y + + + def clickHandler(self,event): + print("(x, y) = (%d, %d)" % (event.x,event.y)) + self.SIM_touch(event.x,event.y,self.touch_recoder_new, self.touch_recoder_old) + self.env.touch_handler.handle(self.touch_recoder_new, self.touch_recoder_old) + + self.SIM_touch(None,None,self.touch_recoder_new, self.touch_recoder_old) + self.env.touch_handler.handle(self.touch_recoder_new, self.touch_recoder_old) + + def open(self,env) -> None: + + self.env = env + + self.touch_recoder_new = touchpad.TouchRecoder() # 触摸 + self.touch_recoder_old = touchpad.TouchRecoder() + + self.window=tkinter.Tk() + + self.window.title('水墨屏模拟器 by xuanzhi33') + + self.window.geometry('296x128') + + pilImage = Image.new("RGB", (296, 128), "white") + tkImage = ImageTk.PhotoImage(image=pilImage) + + self.display = tkinter.Label(self.window,image=tkImage) + self.display.bind("",self.clickHandler) + self.display.pack() + self.window.mainloop() + + def updateImage(self,PILImg): + tkImage = ImageTk.PhotoImage(image=PILImg) + self.display.configure(image=tkImage) + self.display.image=tkImage + + +class EpdController: + """ + 用这个类来显示图片可能会被阻塞(当多个线程尝试访问屏幕时) + """ + + def __init__(self, + simulator: Simulator, + logger_: logger.Logger, + lock: threading.RLock, + init=True, + auto_sleep_time=30, + refresh_time=3600, + refresh_interval=20): + super().__init__() + + self.simulator = simulator + + self.last_update = time.time() + self.partial_time = 0 + self.refresh_time = refresh_time + self.refresh_interval = refresh_interval + self.__auto_sleep_time = auto_sleep_time + self.logger_ = logger_ + self.lock = lock + self.tk = timing_task.TimingTask(auto_sleep_time, self.controller) + self.sleep_status = threading.Lock() # 上锁表示休眠 + self.upside_down = False + if auto_sleep_time > 0: + self.tk.start() + if init: + self.init() + + def __del__(self): + self.exit() + + @property + def auto_sleep_time(self): + return self.__auto_sleep_time + + def set_auto_sleep_time(self, auto_sleep_time): + self.tk.cycle = auto_sleep_time + self.__auto_sleep_time = auto_sleep_time + if auto_sleep_time > 0: + self.tk.start() + else: + self.tk.stop() + + def controller(self): # 自动休眠 + if time.time() - self.last_update >= self.auto_sleep_time: + if not self.lock.acquire(blocking=False): + return + self.sleep() + self.lock.release() + + def init(self): + try: + self.sleep_status.release() + except RuntimeError: + pass + self.logger_.debug("屏幕初始化") + + def display(self, image: Image.Image, timeout=-1): + self.lock.acquire(timeout=timeout) + #image.show() + + self.simulator.updateImage(image) + + self.lock.release() + self.last_update = time.time() + self.partial_time = 0 + + def display_Base(self, image, timeout=-1): + self.lock.acquire() + #image.show() + + self.simulator.updateImage(image) + + self.lock.release() + self.last_update = time.time() + self.partial_time = 0 + + def display_Partial(self, image, timeout=-1): + self.lock.acquire(timeout=timeout) + #image.show() + + self.simulator.updateImage(image) + + self.lock.release() + self.last_update = time.time() + self.partial_time += 1 + + def display_Auto(self, image, timeout=-1): + self.lock.acquire(timeout=timeout) + if (time.time() - self.last_update > self.refresh_time) or (self.partial_time >= self.refresh_interval): + self.display_Base(image, timeout) + else: + self.display_Partial_Wait(image, timeout) + self.lock.release() + + def display_Partial_Wait(self, image, timeout=-1): + self.lock.acquire(timeout=timeout) + #image.show() + + self.simulator.updateImage(image) + + self.lock.release() + self.last_update = time.time() + self.partial_time += 1 + + def clear(self, color, timeout=-1): + self.lock.acquire(timeout=timeout) + pass + self.lock.release() + self.last_update = time.time() + self.partial_time = 0 + + def exit(self): + self.tk.stop() + self.sleep() + + def sleep(self): + if self.sleep_status.acquire(blocking=False): + self.logger_.debug("屏幕休眠") + + def get_buffer(self, image: Image.Image): + if self.upside_down: + return image.transpose(Image.ROTATE_180) + else: + return image + + def acquire(self, timeout=-1): + return self.lock.acquire(timeout=timeout) + + def release(self): + return self.lock.release() + + def set_upside_down(self, value: bool): + self.upside_down = value + + @staticmethod + def IsBusy(): + return False + + +class TouchDriver: + def __init__(self, logger_touch: logger): + self.logger_touch = logger_touch + + def ICNT_Reset(self): + self.logger_touch.debug("触摸屏重置") + + def ICNT_ReadVersion(self): + self.logger_touch.debug("触摸屏的版本为:" + "调试器模式") + + def ICNT_Init(self): + self.logger_touch.debug("触摸屏初始化") + + @staticmethod + def ICNT_Scan(ICNT_Dev: touchpad.TouchRecoder, ICNT_Old: touchpad.TouchRecoder): + try: + x = int(input("x:")) + y = int(input("y:")) + except ValueError: + x = None + y = None + ICNT_Old.Touch = ICNT_Dev.Touch + ICNT_Old.TouchGestureId = ICNT_Dev.TouchGestureId + ICNT_Old.TouchCount = ICNT_Dev.TouchCount + ICNT_Old.TouchEvenId = ICNT_Dev.TouchEvenId + ICNT_Old.X = ICNT_Dev.X.copy() + ICNT_Old.Y = ICNT_Dev.Y.copy() + ICNT_Old.P = ICNT_Dev.P.copy() + if x is None or y is None: + ICNT_Dev.Touch = 0 + else: + ICNT_Dev.Touch = 1 + ICNT_Dev.X[0] = x + ICNT_Dev.Y[0] = y + + +class Env: + def __init__(self, configs, logger_env: logger.Logger, simulator): + + self.simulator=simulator + + self.logger_env = logger_env + self.epd_lock = threading.RLock() + self.epd_driver = EpdController(self.simulator, + self.logger_env, + self.epd_lock, True, + auto_sleep_time=configs["auto_sleep_time"], + refresh_time=configs["refresh_time"], + refresh_interval=configs["refresh_interval"]) + if self.epd_driver.IsBusy(): + self.logger_env.error("The screen is busy!") + raise RuntimeError("The screen is busy!") + + self.pool = threadpool_mini.ThreadPool(configs["threadpool_size"], handler=logger_env.warn) + self.pool.start() + + self.touch_handler = touchpad.TouchHandler(self) + self.touchpad_driver = TouchDriver(self.logger_env) + self.touchpad_driver.ICNT_Init() + self.paper = None + self.plugins = None + self.apps = None + self.inited = False + self.theme = None + + def init(self, paper, plugins, apps): + if self.inited: + return + self.inited = True + self.theme = paper + self.paper = paper + self.plugins = plugins + self.apps = apps + self.paper.init() + + def changePaper(self, paper, exit_paper=False): + if not self.inited: + return + self.touch_handler.clear() + if exit_paper: + self.paper.exit() + else: + self.paper.pause() # pause()能暂停页面 + self.paper = paper + if paper.inited: + self.paper.recover() + else: + self.paper.init() + + def openApp(self, appName, exit_paper=False): + if not self.inited: + return + if appName in self.apps: + if self.apps[appName][2] is None: + self.apps[appName][2] = self.apps[appName][0].build(self) + self.changePaper(self.apps[appName][2], exit_paper=exit_paper) + + def backHome(self, exit_paper=False): + if not self.inited: + return + self.touch_handler.clear() + if exit_paper: + self.paper.exit() + else: + self.paper.pause() # pause()能暂停页面 + self.paper = self.theme + if self.theme.inited: + self.paper.recover() + else: + self.paper.init() diff --git a/sdk/epd2in9_V2.py b/sdk/epd2in9_V2.py new file mode 100644 index 0000000000000000000000000000000000000000..b89ae42d80f39ba956917f49e3d82c6a501772e7 --- /dev/null +++ b/sdk/epd2in9_V2.py @@ -0,0 +1,354 @@ +# ***************************************************************************** +# * | File : epd2in9_V2.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2020-10-20 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# 已被fu1fan修改,勿直接应用于生产环境 + +from sdk import epdconfig + +# Display resolution +EPD_WIDTH = 128 +EPD_HEIGHT = 296 + + +class EPD_2IN9_V2: + def __init__(self): + self.reset_pin = epdconfig.EPD_RST_PIN + self.dc_pin = epdconfig.EPD_DC_PIN + self.busy_pin = epdconfig.EPD_BUSY_PIN + self.cs_pin = epdconfig.EPD_CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + epdconfig.address = 0x48 + + WF_PARTIAL_2IN9 = [ + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, + 0x22, 0x17, 0x41, 0xB0, 0x32, 0x36, + ] + + WF_PARTIAL_2IN9_Wait = [ + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, + 0x22, 0x17, 0x41, 0xB0, 0x32, 0x36, + ] + + # Hardware reset + def reset(self): # 不建议进行操作 + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(2) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + + def send_command(self, command): # 不建议进行操作 + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): # 不建议进行操作 + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data2(self, data): # 不建议进行操作 + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte2(data) + epdconfig.digital_write(self.cs_pin, 1) + + def WaitBusy(self): # 等待直到屏幕结束忙碌 + # logging.debug("e-Paper busy") + while epdconfig.digital_read(self.busy_pin) == 1: # 0: idle, 1: busy + epdconfig.delay_ms(0.1) + # logging.debug("e-Paper busy release") + + def IsBusy(self): + return epdconfig.digital_read(self.busy_pin) + + def TurnOnDisplay(self): # 不建议进行操作 + self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2 + self.send_data(0xF7) + self.send_command(0x20) # MASTER_ACTIVATION + self.WaitBusy() + + def TurnOnDisplay_Partial(self): # 不建议进行操作 + self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2 + self.send_data(0x0F) + self.send_command(0x20) # MASTER_ACTIVATION + # self.ReadBusy() + + def TurnOnDisplay_Partial_Wait(self): # 不建议进行操作 + self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2 + self.send_data(0x0F) + self.send_command(0x20) # MASTER_ACTIVATION + self.WaitBusy() + + def SendLut(self, lut): # 不建议进行操作 + self.send_command(0x32) + # for i in range(0, 153): + # self.send_data(self.WF_PARTIAL_2IN9[i]) + if lut: + self.send_data2(self.WF_PARTIAL_2IN9) + else: + self.send_data2(self.WF_PARTIAL_2IN9_Wait) + self.WaitBusy() + + def SetWindow(self, x_start, y_start, x_end, y_end): # 不建议进行操作 + self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION + # x point must be the multiple of 8 or the last 3 bits will be ignored + self.send_data((x_start >> 3) & 0xFF) + self.send_data((x_end >> 3) & 0xFF) + self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION + self.send_data(y_start & 0xFF) + self.send_data((y_start >> 8) & 0xFF) + self.send_data(y_end & 0xFF) + self.send_data((y_end >> 8) & 0xFF) + + def SetCursor(self, x, y): # 不建议进行操作 + self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER + # x point must be the multiple of 8 or the last 3 bits will be ignored + self.send_data(x & 0xFF) + + self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER + self.send_data(y & 0xFF) + self.send_data((y >> 8) & 0xFF) + self.WaitBusy() + + def init(self): # 初始化 + if epdconfig.module_init() != 0: + return -1 + # EPD hardware init start + self.reset() + + self.WaitBusy() + self.send_command(0x12) # SWRESET + self.WaitBusy() + + self.send_command(0x01) # Driver output control + self.send_data(0x27) + self.send_data(0x01) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width - 1, self.height - 1) + + self.send_command(0x21) # Display update control + self.send_data(0x00) + self.send_data(0x80) + + self.SetCursor(0, 0) + self.WaitBusy() + # EPD hardware init end + return 0 + + def get_buffer(self, image): # 将图片转换为buffer + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if imwidth == self.width and imheight == self.height: + # logging.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + elif imwidth == self.height and imheight == self.width: + # logging.debug("Horizontal") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def display(self, image): # 显示图片 + if image is None: + return + self.send_command(0x24) # WRITE_RAM + # for j in range(0, self.height): + # for i in range(0, int(self.width / 8)): + # self.send_data(images[i + j * int(self.width / 8)]) + self.send_data2(image) + self.TurnOnDisplay() + + def display_Base(self, image): # 显示静态底图 + if image is None: + return + + self.send_command(0x24) # WRITE_RAM + # for j in range(0, self.height): + # for i in range(0, int(self.width / 8)): + # self.send_data(images[i + j * int(self.width / 8)]) + self.send_data2(image) + + self.send_command(0x26) # WRITE_RAM + # for j in range(0, self.height): + # for i in range(0, int(self.width / 8)): + # self.send_data(images[i + j * int(self.width / 8)]) + self.send_data2(image) + + self.TurnOnDisplay() + + def display_Partial(self, image): # 局部显示 + if image is None: + return + + # epdconfig.digital_write(self.reset_pin, 0) + # epdconfig.delay_ms(2) + # epdconfig.digital_write(self.reset_pin, 1) + # epdconfig.delay_ms(2) + + self.SendLut(1) + self.send_command(0x37) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x40) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x80) + + self.send_command(0x22) + self.send_data(0xC0) + self.send_command(0x20) + self.WaitBusy() + + self.SetWindow(0, 0, self.width - 1, self.height - 1) + self.SetCursor(0, 0) + + self.send_command(0x24) # WRITE_RAM + # for j in range(0, self.height): + # for i in range(0, int(self.width / 8)): + # self.send_data(images[i + j * int(self.width / 8)]) + self.send_data2(image) + + self.TurnOnDisplay_Partial() + + def display_Partial_Wait(self, image): # 局部显示并等待显示完成 + if image is None: + return + + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(1) + epdconfig.digital_write(self.reset_pin, 1) + # epdconfig.delay_ms(2) + + self.SendLut(0) + self.send_command(0x37) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x40) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x80) + + self.send_command(0x22) + self.send_data(0xC0) + self.send_command(0x20) + self.WaitBusy() + + self.SetWindow(0, 0, self.width - 1, self.height - 1) + self.SetCursor(0, 0) + + self.send_command(0x24) # WRITE_RAM + # for j in range(0, self.height): + # for i in range(0, int(self.width / 8)): + # self.send_data(images[i + j * int(self.width / 8)]) + self.send_data2(image) + + self.TurnOnDisplay_Partial_Wait() + + def clear(self, color): # 清屏 + self.send_command(0x24) # WRITE_RAM + for j in range(0, self.height): + for i in range(0, int(self.width / 8)): + self.send_data(color) + self.TurnOnDisplay() + + def sleep(self): # 睡眠模式 + self.send_command(0x10) # DEEP_SLEEP_MODE + self.send_data(0x01) + + @staticmethod + def exit(): # 退出模块 + epdconfig.module_exit() +### END OF FILE ### diff --git a/sdk/epdconfig.py b/sdk/epdconfig.py new file mode 100644 index 0000000000000000000000000000000000000000..2e9ec1bde43ba71fa36bdc52029a1bcfc6b33dfb --- /dev/null +++ b/sdk/epdconfig.py @@ -0,0 +1,121 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2020-12-21 +# * | Info : +# ****************************************************************************** +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import logging +import time + +import RPi.GPIO as GPIO +import spidev +from smbus import SMBus + +# e-Paper +EPD_RST_PIN = 17 +EPD_DC_PIN = 25 +EPD_CS_PIN = 8 +EPD_BUSY_PIN = 24 + +# TP +TRST = 22 +INT = 27 + +spi = spidev.SpiDev(0, 0) +address = 0x0 +# address = 0x14 +# address = 0x48 +bus = SMBus(1) + + +def digital_write(pin, value): + GPIO.output(pin, value) + + +def digital_read(pin): + return GPIO.input(pin) + + +def delay_ms(delaytime): + time.sleep(delaytime / 1000.0) + + +def spi_writebyte(data): + spi.writebytes(data) + + +def spi_writebyte2(data): + spi.writebytes2(data) + + +def i2c_writebyte(reg, value): + bus.write_word_data(address, (reg >> 8) & 0xff, (reg & 0xff) | ((value & 0xff) << 8)) + + +def i2c_write(reg): + bus.write_byte_data(address, (reg >> 8) & 0xff, reg & 0xff) + + +def i2c_readbyte(reg, __len): + i2c_write(reg) + rbuf = [] + for i in range(__len): + rbuf.append(int(bus.read_byte(address))) + return rbuf + + +def module_init(): + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(EPD_RST_PIN, GPIO.OUT) + GPIO.setup(EPD_DC_PIN, GPIO.OUT) + GPIO.setup(EPD_CS_PIN, GPIO.OUT) + GPIO.setup(EPD_BUSY_PIN, GPIO.IN) + + GPIO.setup(TRST, GPIO.OUT) + GPIO.setup(INT, GPIO.IN) + + spi.max_speed_hz = 10000000 + spi.mode = 0b00 + + return 0 + + +def module_exit(): + logging.debug("spi end") + spi.close() + bus.close() + + logging.debug("close 5V, Module enters 0 power consumption ...") + GPIO.output(EPD_RST_PIN, 0) + GPIO.output(EPD_DC_PIN, 0) + GPIO.output(EPD_CS_PIN, 0) + + GPIO.output(TRST, 0) + + GPIO.cleanup() + +### END OF FILE ### diff --git a/sdk/graphics/__init__.py b/sdk/graphics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..64159aff1cd2466f3d8768a81bef74d3899ba349 --- /dev/null +++ b/sdk/graphics/__init__.py @@ -0,0 +1,242 @@ +import threading +import traceback + +from PIL import Image + + +class Paper: + """ + 用于显示静态图像,不支持切换Page + """ + + def __init__(self, + env, + background_image=Image.new("RGB", (296, 128), (255, 255, 255))): + self.active = False + self.inited = False + self.background_image = background_image + self.epd = env.epd_driver + self.image_old = self.background_image + self.update_lock = threading.Lock() + + def __del__(self): + if self.active: + self.exit() + + def display(self, image: Image): + b_image = self.epd.get_buffer(image) + self.epd.display_Base(b_image) # 是这样的吗???迷人的驱动 + + def display_partial(self, image: Image): + b_image = self.epd.get_buffer(image) + self.epd.display_Partial_Wait(b_image) + + def display_auto(self, image: Image): + b_image = self.epd.get_buffer(image) + self.epd.display_Auto(b_image) + + def build(self) -> Image: + self.image_old = self.background_image + return self.background_image + + def init(self): + self.inited = True + self.active = True + self.display(self.build()) + return True + + def exit(self): + self.active = False + self.inited = False + + def refresh(self): + if not self.active: + return + self.display(self.image_old) + return True + + def update_background(self, image, refresh=None): + if not self.active: + return + self.background_image = image + if self.update_lock.acquire(blocking=False): + self.epd.acquire() # 重入锁,保证到屏幕刷新时使用的是最新的 self.build() + self.update_lock.release() + if refresh is None: + self.display_auto(self.build()) + elif refresh: + self.display(self.build()) + else: + self.display_partial(self.build()) + self.epd.release() + + +class Page(list): # page是对list的重写,本质为添加一个构造器 + def __init__(self, paper, name): + super().__init__() + self.paper = paper + self.name = name + self.inited = False + + def addElement(self, element): + self.append(element) + element.page = self + + def init(self): + for i in self: + i.init() + self.inited = True + + def pause(self): + for i in self: + i.pause() + + def recover(self): + for i in self: + i.recover() + + def exit(self): + for i in self: + i.exit() + self.inited = False + + +class PaperDynamic(Paper): + def __init__(self, + env, + background_image=Image.new("RGB", (296, 128), 1)): + super().__init__(env, background_image) + # 实例化各种定时器 + # self.pool = env.pool + self.pages = {"mainPage": Page(self, "mainPage"), "infoHandler": Page(self, "infoHandler"), "warnHandler": Page(self, "warnHandler"), "errorHandler": Page(self, "errorHandler")} + # TODO:为Handler页面添加内容 + self.nowPage = "mainPage" + self.oldPage = "mainPage" + # self.touch_handler = env.touch_handler + self.env = env + + def exit(self): + for page in self.pages.values(): + page.exit() + + def pause(self): + self.pages[self.nowPage].pause() + self.active = False + + def recover(self): + self.pages[self.nowPage].recover() + self.active = True + self.display(self.build()) + + def build(self) -> Image: + new_image = self.background_image.copy() + for element in self.pages[self.nowPage]: + element_image = element.build() + if element_image: + new_image.paste(element_image, (element.xy[0], element.xy[1])) + self.image_old = new_image + return new_image + + def addPage(self, name: str, page=None): + if page is None: + page = Page(self, name) + self.pages[name] = page + + def addElement(self, target: str, element): + self.pages[target].append(element) + element.page = self.pages[target] + if self.active: + element.init() + + def changePage(self, name, refresh=None): + if name in self.pages: + self.env.touch_handler.clear() + self.pages[self.nowPage].pause() + self.oldPage = self.nowPage + self.nowPage = name + if self.pages[name].inited: + self.pages[name].recover() + else: + self.pages[name].init() + self._update(refresh) + else: + raise ValueError("The specified page does not exist!") + + def infoHandler(self): + pass + + def warnHandler(self): + pass + + def errorHandler(self): + pass + + def _update(self, refresh=None): + if self.update_lock.acquire(blocking=False): + self.epd.acquire() # 重入锁,保证到屏幕刷新时使用的是最新的 self.build() + self.update_lock.release() + if refresh is None: + self.display_auto(self.build()) + elif refresh: + self.display(self.build()) + else: + self.display_partial(self.build()) + self.epd.release() + + def update(self, page_name: str, refresh=None): + if not (self.active and page_name == self.nowPage): + return + self._update(refresh) + + def pause_update(self, timeout=-1): + if not self.update_lock.acquire(timeout=-1): + raise TimeoutError + + def recover_update(self, raise_for_unlocked=False): + try: + self.update_lock.release() + except RuntimeError as e: + if raise_for_unlocked: + raise e + else: + self.env.logger_env.warn(traceback.format_exc()) + self.env.pool.add_immediately(self._update) + + def update_async(self, page_name: str, refresh=None): + self.env.pool.add_immediately(self.update, page_name, refresh) + + def init(self): + self.pages[self.nowPage].init() + super().init() + + +class Element: + def __init__(self, xy: tuple, paper: PaperDynamic, size=(0, 0), background=None): + self.xy = xy + self.size = size + self.paper = paper + self.pool = paper.env.pool + self.inited = False + self.active = False + self.page = None + self.background = background + + def __del__(self): + self.exit() + + def init(self): # 初始化函数,当被添加到动态Paper时被调用 + self.inited = True + self.active = True + + def exit(self): # 退出时调用 + self.inited = False + self.active = False + + def pause(self): # 切换出page时调用 + self.active = False + + def recover(self): # 切换回page时调用 + self.active = True + + def build(self) -> Image: # 当页面刷新时被调用,须返回一个图像 + return self.background diff --git a/sdk/graphics/element_lib.py b/sdk/graphics/element_lib.py new file mode 100644 index 0000000000000000000000000000000000000000..533fc94acf3f1327a1d55bd1543187e9013b1a24 --- /dev/null +++ b/sdk/graphics/element_lib.py @@ -0,0 +1,166 @@ +import traceback + +from PIL import ImageFont, Image, ImageDraw + +from sdk.graphics import Element, PaperDynamic + + +class ImageElement(Element): + def __init__(self, xy: tuple, paper: PaperDynamic, image_path: str): + super().__init__(xy, paper) + self._setImage(image_path) + + def _setImage(self, image_path): + try: + file = open(image_path, "rb") + self.image = Image.open(file) + self.size = (self.image.size[0], self.image.size[1]) + except: + self.image = None + self.paper.env.logger_env.error(traceback.format_exc()) + + def setImage(self, new_image_path): + self._setImage(new_image_path) + self.paper.update(self.page.name) + + def getImage(self) -> Image: + return self.image + + def build(self) -> Image: + return self.image + + +class TextElement(Element): + def __init__(self, xy, paper: PaperDynamic, text, size=(50, 30), bgcolor="white", textColor="black", fontSize=20, + *args, **kwargs): + super().__init__(xy, paper, size) + self.text = text + self._visible = True + self.size = size + self.args = args + self.kwargs = kwargs + self.font = ImageFont.truetype( + "resources/fonts/STHeiti_Light.ttc", fontSize) + self.textColor = textColor + self.background_image = Image.new("RGB", size, bgcolor) + + def isVisible(self): + return self._visible + + def setVisible(self, m: bool): + self._visible = m + self.paper.update(self.page.name) + + def getText(self): + return self.text + + def setText(self, newText): + self.text = newText + self.paper.update(self.page.name) + + def build(self) -> Image: + if self.inited and self._visible: + image = self.background_image.copy() + image_draw = ImageDraw.ImageDraw(image) + # image_draw.rectangle((0, 0, self.size[0], self.size[1]), fill="white", outline="black", width=1) + image_draw.text((5, 5), self.text, + font=self.font, fill=self.textColor) + return image + elif not self._visible: + return None + + +class Button(TextElement): + def __init__(self, xy, paper: PaperDynamic, text, onclick, size=(50, 30), bgcolor="black", textColor="white", + fontSize=20, *args, **kwargs): + super().__init__(xy, paper, text, size, bgcolor, + textColor, fontSize, *args, **kwargs) + self.on_clicked = onclick + + def clickedHandler(self, *args, **kwargs): + if self._visible and self.inited: + self.on_clicked(*args, **kwargs) + + def setOnclick(self, onclickFunc): + self.on_clicked = onclickFunc + + def _addButtonClicked(self): + self.paper.env.touch_handler.add_clicked((self.xy[0], self.xy[0] + self.size[0], + self.xy[1], self.xy[1] + self.size[1]), + self.clickedHandler, + *self.args, + **self.kwargs) + + def init(self): + self._addButtonClicked() + super().init() + + def recover(self): + self._addButtonClicked() + super().recover() + + +class Label(TextElement): + def __init__(self, xy, paper: PaperDynamic, text, size=(50, 30), bgcolor="white", textColor="black", fontSize=20, + *args, **kwargs): + super().__init__(xy, paper, text, size=size, bgcolor=bgcolor, textColor=textColor, fontSize=fontSize, + *args, **kwargs) + + +class LabelWithMultipleLines(TextElement): + def build(self) -> Image: + if self.inited and self._visible: + self.width = self.size[0] + # 段落 , 行数, 行高 + self.duanluo, self.note_height, self.line_height = self.split_text() + + image = self.background_image.copy() + draw = ImageDraw.Draw(image) + # 左上角开始 + x, y = 0, 0 + for duanluo, line_count in self.duanluo: + draw.text((x, y), duanluo, fill=self.textColor, font=self.font) + y += self.line_height * line_count + + return image + + elif not self._visible: + return None + + + def get_duanluo(self, text): + txt = Image.new('RGBA', self.size, (255, 255, 255, 0)) + draw = ImageDraw.Draw(txt) + # 所有文字的段落 + duanluo = "" + # 宽度总和 + sum_width = 0 + # 几行 + line_count = 1 + # 行高 + line_height = 0 + for char in text: + width, height = draw.textsize(char, self.font) + sum_width += width + if sum_width > self.width: # 超过预设宽度就修改段落 以及当前行数 + line_count += 1 + sum_width = 0 + duanluo += '\n' + duanluo += char + line_height = max(height, line_height) + if not duanluo.endswith('\n'): + duanluo += '\n' + return duanluo, line_height, line_count + + def split_text(self): + # 按规定宽度分组 + max_line_height, total_lines = 0, 0 + allText = [] + for text in self.text.split('\n'): + duanluo, line_height, line_count = self.get_duanluo(text) + max_line_height = max(line_height, max_line_height) + total_lines += line_count + allText.append((duanluo, line_count)) + line_height = max_line_height + total_height = total_lines * line_height + return allText, total_height, line_height diff --git a/sdk/graphics/page_lib.py b/sdk/graphics/page_lib.py new file mode 100644 index 0000000000000000000000000000000000000000..2cfa8c4b9a3f00e081af33b867008b3da89d3dff --- /dev/null +++ b/sdk/graphics/page_lib.py @@ -0,0 +1,185 @@ +import time +import math + +import sdk +from sdk.graphics import Page as _Page +from sdk.graphics.element_lib import ImageElement as _ImageElement +import sdk.graphics.element_lib + + +class ListPage(_Page): + def __init__(self, paper, name): + super().__init__(paper, name) + + self.addElement(sdk.graphics.element_lib.Button( + (0, 0), self.paper, "", self.close, (45, 30))) + + self.addElement(sdk.graphics.element_lib.Button( + (200, 0), self.paper, "", self.goPrev, (53, 30))) + self.addElement(sdk.graphics.element_lib.Button( + (254, 0), self.paper, "", self.goNext, (41, 30))) + + self.addElement(_ImageElement( + (0, 0), self.paper, "resources/images/list.jpg")) + + self.icons = ( + sdk.graphics.element_lib.ImageElement( + (8, 36), self.paper, "resources/images/None20px.jpg"), + sdk.graphics.element_lib.ImageElement( + (8, 66), self.paper, "resources/images/None20px.jpg"), + sdk.graphics.element_lib.ImageElement( + (8, 96), self.paper, "resources/images/None20px.jpg") + ) + + for icon in self.icons: + self.addElement(icon) + + self.label_of_page = sdk.graphics.element_lib.Label( + (155, 0), self.paper, "", (55, 28)) + self.addElement(self.label_of_page) + + self.title_of_list = sdk.graphics.element_lib.Label( + (50, 0), self.paper, "", (105, 28)) + self.addElement(self.title_of_list) + + self.listTexts = ( + sdk.graphics.element_lib.Button( + (35, 32), self.paper, "", self.itemOnclickHandler, (260, 28), "white", "black", index=0), + sdk.graphics.element_lib.Button( + (35, 62), self.paper, "", self.itemOnclickHandler, (260, 28), "white", "black", index=1), + sdk.graphics.element_lib.Button( + (35, 92), self.paper, "", self.itemOnclickHandler, (260, 28), "white", "black", index=2) + ) + for listText in self.listTexts: + self.addElement(listText) + + self.more_items_dots = sdk.graphics.element_lib.ImageElement( + (105, 122), self.paper, "resources/images/more_items_dots.jpg") + + self.addElement(self.more_items_dots) + + self.total_pages_of_content = 0 + self.current_page_of_content = 0 + + self.content = [] + # 格式为:[[text, image, func]] + # 其中 func 会收到一个index参数,来知道自己是第几个(以0开始) + + def itemOnclickHandler(self, index=None): + if index != None: + indexInList = (self.current_page_of_content-1)*3+index + if indexInList < len(self.content): + self.content[indexInList][2](indexInList) # 传入index + + def close(self): + if self.closeEvent != None: + self.closeEvent() + + def showItems(self): + + self.label_of_page.setText( + "%d/%d" % (self.current_page_of_content, self.total_pages_of_content)) + + if self.current_page_of_content < self.total_pages_of_content: + self.more_items_dots.setImage( + "resources/images/more_items_dots.jpg") + else: + self.more_items_dots.setImage("resources/images/None1px.jpg") + + index_of_the_first = (self.current_page_of_content-1)*3 + for i in range(0, 3): + if index_of_the_first + i < len(self.content): + # 设置item的文字 + self.listTexts[i].setText( + self.content[index_of_the_first + i][0]) + # 设置item的图标 + if self.content[index_of_the_first + i][1] != None: + self.icons[i].setImage( + self.content[index_of_the_first + i][1]) + else: + self.icons[i].setImage("resources/images/None20px.jpg") + # 不必在此更改点击事件 + + else: + self.listTexts[i].setText("") + self.icons[i].setImage("resources/images/None1px.jpg") + # 也不必在此更改点击事件了 + + def goPrev(self): + if (self.current_page_of_content > 1): + self.paper.pause_update() # 上锁,防止setText重复刷新屏幕 + self.current_page_of_content -= 1 + self.showItems() + self.paper.recover_update() # 解锁 + + def goNext(self): + if (self.current_page_of_content < self.total_pages_of_content): + self.paper.pause_update() # 上锁,防止setText重复刷新屏幕 + self.current_page_of_content += 1 + self.showItems() + self.paper.recover_update() # 解锁 + + def show(self, content=None, listTitle="", closeEvent=None): + if content is None: + content = [] + self.content = content + self.listTitle = listTitle + self.closeEvent = closeEvent + + self.total_pages_of_content = math.ceil(len(self.content) / 3) + self.current_page_of_content = 1 + + self.paper.pause_update() # 上锁,防止setText重复刷新屏幕 + + self.title_of_list.setText(self.listTitle) + self.showItems() + + self.paper.recover_update() # 解锁 + + + + +class appListPage(ListPage): + def openAppByIndex(self, index): + if index >= 0: + self.paper.env.openApp(list(self.paper.env.apps.keys())[index]) + + + def backToMainPage(self): + self.paper.changePage("mainPage") + + def show(self): + appList = [] + + for appName, appContent in self.paper.env.apps.items(): + appList.append([appName, appContent[1][1], self.openAppByIndex]) + + super().show(appList, "应用列表", self.backToMainPage) + + +# keyboardPage 还未完成哦 +class keyboardPage(_Page): + def __init__(self, paper, textHandler, pageName="keyboardPage"): + super().__init__(paper, pageName) + self.keyboardList = [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], + ["A", "S", "D", "F", "G", "H", "J", "K", "L", "←"], + ["↑", "Z", "X", "C", "V", "B", "N", "M", ",", "."]] + self.keyboard = {} + + self.textInput = sdk.graphics.element_lib.Label( + (0, 0), paper, "请点按键盘 :)", (295, 30)) + self.addElement(self.textInput) + + def showKeyboard(self): + for i in range(3): + for j in range(10): + nowChar = self.keyboardList[i][j] + self.keyboard[nowChar] = sdk.graphics.element_lib.Button( + (j * 29 + 3, i * 30 + 36), self.paper, nowChar, self.addChar, (28, 29), char=nowChar) + self.addElement(self.keyboard[nowChar]) + + def addChar(self, char): + self.textInput.setText(self.textInput.getText()+char) + + def show(self, inputType="text"): + pass diff --git a/sdk/graphics/paper_lib.py b/sdk/graphics/paper_lib.py new file mode 100644 index 0000000000000000000000000000000000000000..073a2477879cd00199dc2289c27eda6c8bf80edb --- /dev/null +++ b/sdk/graphics/paper_lib.py @@ -0,0 +1,96 @@ +import time + +from PIL import Image + +from sdk.graphics import Element, PaperDynamic, element_lib, page_lib + + +class _Docker(Element): + def __init__(self, paper: PaperDynamic): + super().__init__((60, 0), paper, (176, 30)) + self.image = Image.open(open("resources/images/docker.jpg", "rb")) + self.__active = False + self.inited = False + + def appbox_click_handler(self): + if self.__active: + self.paper.changePage("appList") + self.paper.pages["appList"].show() + self.__active = False + + def settings_click_handler(self): + if self.__active: + self.paper.env.openApp("系统设置") + self.__active = False + + def clicked_handler(self): + time.sleep(0.1) + if (self.paper.nowPage != self.page.name) or self.__active or (not self.inited): + return + self.__active = True + self.paper.update(self.page.name) + time.sleep(10) + self.__active = False + self.paper.update(self.page.name) + + def init(self): + self.paper.env.touch_handler.add_clicked( + (0, 296, 0, 30), self.clicked_handler) + self.paper.env.touch_handler.add_clicked( + (60, 100, 0, 30), self.appbox_click_handler) + self.paper.env.touch_handler.add_clicked( + (195, 235, 0, 30), self.settings_click_handler) + + self.inited = True + + def recover(self): + self.paper.env.touch_handler.add_clicked( + (0, 296, 0, 30), self.clicked_handler) + self.paper.env.touch_handler.add_clicked( + (60, 100, 0, 30), self.appbox_click_handler) + self.paper.env.touch_handler.add_clicked( + (195, 235, 0, 30), self.settings_click_handler) + + + + def exit(self): + self.inited = False + + def build(self) -> Image: + if self.__active: + return self.image + else: + return + + +class appBackButton(element_lib.Button): # 先做个临时的返回按钮哦 + def __init__(self, paper: PaperDynamic): + super().__init__((0, 0), paper, "返回", self.goBack, bgcolor="white", textColor="black") + + def goBack(self): + self.paper.env.backHome() + + +class PaperTheme(PaperDynamic): + def __init__(self, env): + super().__init__(env) + self.pages["appList"] = page_lib.appListPage(self, "appList") + self.first_init = True + + def init(self): + if self.first_init: + self.addElement("mainPage", _Docker(self)) + self.first_init = False + super().init() + + +class PaperApp(PaperDynamic): + def __init__(self, env): + super().__init__(env) + self.first_init = True + + def init(self): + if self.first_init: + self.addElement("mainPage", appBackButton(self)) + self.first_init = False + super().init() diff --git a/sdk/icnt86.py b/sdk/icnt86.py new file mode 100644 index 0000000000000000000000000000000000000000..47066d8bb1b5ebeb03332a48462f1ea255840ceb --- /dev/null +++ b/sdk/icnt86.py @@ -0,0 +1,94 @@ +# 已被fu1fan修改,勿直接应用于生产环境 + +from sdk import epdconfig as config + + +class ICNT_Development: + def __init__(self): + self.Touch = 0 + self.TouchGestureId = 0 + self.TouchCount = 0 + + self.TouchEvenId = [0, 1, 2, 3, 4] + self.X = [0, 1, 2, 3, 4] + self.Y = [0, 1, 2, 3, 4] + self.P = [0, 1, 2, 3, 4] + + +class INCT86: + def __init__(self): + # e-Paper + self.ERST = config.EPD_RST_PIN + self.DC = config.EPD_DC_PIN + self.CS = config.EPD_CS_PIN + self.BUSY = config.EPD_BUSY_PIN + # TP + self.TRST = config.TRST + self.INT = config.INT + + @staticmethod + def digital_read(pin): + return config.digital_read(pin) + + def ICNT_Reset(self): + config.digital_write(self.TRST, 1) + config.delay_ms(100) + config.digital_write(self.TRST, 0) + config.delay_ms(100) + config.digital_write(self.TRST, 1) + config.delay_ms(100) + + @staticmethod + def ICNT_Write(reg, data): + config.i2c_writebyte(reg, data) + + @staticmethod + def ICNT_Read(reg, __len): + return config.i2c_readbyte(reg, __len) + + def ICNT_ReadVersion(self): + buf = self.ICNT_Read(0x000a, 4) + print(buf) + + def ICNT_Init(self): + self.ICNT_Reset() + self.ICNT_ReadVersion() + + def ICNT_Scan(self, ICNT_Dev, ICNT_Old): + # buf = [] + mask = 0x00 + + if ICNT_Dev.Touch == 1: + # ICNT_Dev.Touch = 0 + buf = self.ICNT_Read(0x1001, 1) + + if buf[0] == 0x00: + self.ICNT_Write(0x1001, mask) + config.delay_ms(1) + # print("buffers status is 0") + return + else: + ICNT_Dev.TouchCount = buf[0] + + if ICNT_Dev.TouchCount > 5 or ICNT_Dev.TouchCount < 1: + self.ICNT_Write(0x1001, mask) + ICNT_Dev.TouchCount = 0 + # print("TouchCount number is wrong") + return + + buf = self.ICNT_Read(0x1002, ICNT_Dev.TouchCount * 7) + self.ICNT_Write(0x1001, mask) + + ICNT_Old.X[0] = ICNT_Dev.X[0] + ICNT_Old.Y[0] = ICNT_Dev.Y[0] + ICNT_Old.P[0] = ICNT_Dev.P[0] + + for i in range(0, ICNT_Dev.TouchCount, 1): + ICNT_Dev.TouchEvenId[i] = buf[6 + 7 * i] + ICNT_Dev.X[i] = 295 - ((buf[2 + 7 * i] << 8) + buf[1 + 7 * i]) + ICNT_Dev.Y[i] = 127 - ((buf[4 + 7 * i] << 8) + buf[3 + 7 * i]) + ICNT_Dev.P[i] = buf[5 + 7 * i] + + # print(ICNT_Dev.X[0], ICNT_Dev.Y[0], ICNT_Dev.P[0]) + return + return diff --git a/sdk/logger.py b/sdk/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..abcdab65c9d7ca187576d1a244b1513b42fdacc2 --- /dev/null +++ b/sdk/logger.py @@ -0,0 +1,107 @@ +import inspect +import time +import os +import threading + +DEBUG = 0 +INFO = 1 +WARNING = 2 +ERROR = 3 + + +def get_name(index=1): # 获取上上级调用者的__name__ + frm = inspect.stack()[index] # 0是本函数,1是上级调用,2是上上级,以此类推 + mod = inspect.getmodule(frm[0]) + try: + return mod.__name__ + except AttributeError: + return None + + +class Logger: + def __init__(self, level, folder="logs", tag=None, debug_handler=None, info_handler=None, + warn_handler=None, error_handler=None) -> None: + if level < 0 or level > 3: + raise + if folder[-1] != "/": # 防止文件名直接加到文件夹名后😂 + folder += "/" + self.folder = folder + self.__level = level + self.__levelDic = {0: "[DBUG]", 1: "[INFO]", + 2: "[WARN]", 3: "[ERRO]"} # 单纯只是为了给__write函数用 + + # 在线表演💩山代码,但我是在不知道相关的语法🍬 + if debug_handler is None: + self.debugHandler = self.__defaultHandler + else: + self.debugHandler = debug_handler + if debug_handler is None: + self.infoHandler = self.__defaultHandler + else: + self.infoHandler = info_handler + if debug_handler is None: + self.warnHandler = self.__defaultHandler + else: + self.warnHandler = warn_handler + if debug_handler is None: + self.errorHandler = self.__defaultHandler + else: + self.errorHandler = error_handler + + self.lock = threading.Lock() + if tag is None: + self.name = time.strftime("%Y%m%d-%H:%M:%S", time.localtime()) + else: + self.name = "[%s]%s" % (tag, time.strftime("%Y%m%d-%H:%M:%S", time.localtime())) + if not os.path.exists(folder): + os.mkdir(folder) + + @staticmethod + def __defaultHandler(_): + pass + + def __write(self, level, text, the_name): + if level >= self.__level: + self.lock.acquire() + file = open(self.folder + self.name, + "a+", encoding="utf-8") + if len(text) == 0: + text = "\n" + elif "\n" in text: + text = "\n%s" % text + elif text[-1] != "\n": + text = text + "\n" + content = "%s%s[%s]%s" % ( + self.__levelDic[level], time.strftime("[%Y%m%d-%H:%M:%S]", time.localtime()), the_name, + text) # 格式[level][time][name]--event-- + file.write(content) + file.close() + print(content, end="") + self.lock.release() + + def debug(self, text, info=None) -> None: # text为写入日志的内容,info为为用户显示的内容,只有当启用Handler时info才会被使用 + name = get_name(2) + self.__write(DEBUG, text, name) + if info is not None: + self.debugHandler(info) + + def info(self, text, info=None) -> None: + name = get_name(2) + self.__write(INFO, text, name) + if info is not None: + self.infoHandler(info) + + def warn(self, text, info=None) -> None: + name = get_name(2) + self.__write(WARNING, text, name) + if info is not None: + self.warnHandler(info) + + def error(self, text, info=None) -> None: + name = get_name(2) + self.__write(ERROR, text, name) + if info is not None: + self.errorHandler(info) + + def setLevel(self, level) -> None: + self.__level = level diff --git a/sdk/threadpool_mini.py b/sdk/threadpool_mini.py new file mode 100644 index 0000000000000000000000000000000000000000..6a073c3ac9e0ab64c06cd703d9235431376b6ce8 --- /dev/null +++ b/sdk/threadpool_mini.py @@ -0,0 +1,178 @@ +import ctypes +import inspect +import queue +import threading +import traceback +from queue import Queue + + +def _async_raise(tid, exc_type): + """raises the exception, performs cleanup if needed""" + tid = ctypes.c_long(tid) + if not inspect.isclass(exc_type): + exc_type = type(exc_type) + res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exc_type)) + if res == 0: + raise ValueError("invalid thread id") + elif res != 1: + # """if it returns a number greater than one, you're in trouble, + # and you should call it again with exc=NULL to revert the effect""" + ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) + raise SystemError("PyThreadState_SetAsyncExc failed") + + +def stop_thread(thread): + _async_raise(thread.ident, SystemExit) + + +class ThreadPool: + def __init__(self, thread_num: int, handler=None): + self.tasks = Queue() + self.threads = [] + self.running = False + self.__inited = False + if handler is None: + handler = self.__errorHandler + else: + handler = handler + self.handler = handler + self.__lock = threading.Lock() + self.__lock_wait = threading.Lock() + self.__running_num = 0 + self.__thread_num = thread_num + self.succeed = 0 + self.fail = 0 + for _ in range(thread_num): + self.threads.append(Worker(self.tasks, + self.is_running, + self.handler, + self.__thread_start_work, + self.__thread_finish_work)) + + @staticmethod + def __errorHandler(_): + pass + + def __thread_monitor(self, add=True): + self.__lock.acquire() + if add: + self.__running_num += 1 + else: + self.__running_num -= 1 + self.__lock.release() + if self.__running_num == 0: + try: + self.__lock_wait.release() + except RuntimeError: + pass + else: + self.__lock_wait.acquire(blocking=False) + + def __thread_start_work(self): + self.__lock.acquire() + self.__running_num += 1 + self.__lock.release() + self.__lock_wait.acquire(blocking=False) + + def __thread_finish_work(self, succeed=True): + self.__lock.acquire() + self.__running_num -= 1 + if succeed: + self.succeed += 1 + else: + self.fail += 1 + if self.__running_num == 0: + try: + self.__lock_wait.release() + except RuntimeError: + pass + self.__lock.release() + + def is_running(self): + return self.running + + def start(self): + if self.__inited: + raise RuntimeError("A ThreadPool can only be started once!") + self.__inited = True + self.running = True + for i in self.threads: + i.start() + + def add(self, func, *args, **kwargs): + self.tasks.put((func, args, kwargs)) + + def add_immediately(self, func, *args, **kwargs): + """ + 这种方法添加的线程可能不受线程池控制 + """ + if self.full(): + new_thread = threading.Thread(target=func, args=args, kwargs=kwargs) + new_thread.start() + return new_thread + else: + self.tasks.put((func, args, kwargs)) + + def stop(self): + self.running = False + + def stop_mandatory(self): # 不稳定,不建议使用! + self.running = False + for i in self.threads: + try: + stop_thread(i) + except (ValueError, SystemError): # 这行代码有点危 + self.handler(traceback.format_exc()) + + def task_qsize(self): + return self.tasks.qsize() + + def empty_thread(self): + return self.__thread_num - self.__running_num + + def running_thread(self): + return self.__running_num + + def clear(self): + self.tasks.queue.clear() # TODO:未测试 + + def wait(self, timeout=-1): + self.__lock_wait.acquire(timeout=timeout) + + def full(self): + if self.__running_num == self.__thread_num: + return True + else: + return False + + def empty(self): + if self.__running_num == 0: + return False + + +class Worker(threading.Thread): + def __init__(self, tasks: Queue, is_running, handler, start_log, finish_log): + super().__init__() + self.setDaemon(True) + self.tasks = tasks + self.is_running = is_running + self.handler = handler + self.start_log = start_log + self.finish_log = finish_log + + def run(self): + while True: + try: + task = self.tasks.get(block=True, timeout=2) + self.start_log() + task[0](*task[1], **task[2]) + self.finish_log() + except queue.Empty: + pass + except: + self.handler(traceback.format_exc()) + self.finish_log(False) + else: + if not self.is_running(): + break + diff --git a/sdk/timing_task.py b/sdk/timing_task.py new file mode 100644 index 0000000000000000000000000000000000000000..048a8b002d4b3c87e068787e218133003b2e595b --- /dev/null +++ b/sdk/timing_task.py @@ -0,0 +1,88 @@ +import signal +import threading +import traceback + + +def set_timeout(time: int, callback): + def wrap(func): + def handle(signum, frame): # 收到信号 SIGALRM 后的回调函数,第一个参数是信号的数字,第二个参数是the interrupted stack frame. + raise RuntimeError + + def to_do(*args, **kwargs): + try: + signal.signal(signal.SIGALRM, handle) # 设置信号和回调函数 + signal.alarm(time) # 设置 num 秒的闹钟 + r = func(*args, **kwargs) + signal.alarm(0) # 关闭闹钟 + return r + except RuntimeError: + callback(traceback.format_exc()) + + return to_do + + def no_limited(func): + return func + + if time == 0: # 如果输入time=0则不限制运行时间 + return no_limited + else: + return wrap + + +class TimingTask: + def __init__(self, cycle: int, func, limit=None, timeoutHandler=None, daemonic=True, *args, **kwargs): + """ + cycle:\n + -int:每隔cycle秒执行一次func\n + func:被执行的函数\n + limit:\n + -None:使用一次循环的时间作为函数执行的限制时间\n + -0:不限制函数的运行时间\n + -int:限制函数运行limit秒\n + timeoutHandler:超时后调用的函数,会返回一个str\n + """ + self.cycle = cycle + self.func = func + self.daemonic = daemonic + self.__timer = threading.Timer(self.cycle, self.__run) + self.__running = False + self.args = args + self.kwargs = kwargs + if limit is None: + self.limit = cycle + else: + self.limit = limit + if timeoutHandler is None: + self.handler = self.__timeoutHandler + else: + self.handler = timeoutHandler + + def __del__(self): # 不知道有没有效果,以后再调试 + self.stop() + + def is_running(self): + return self.__running + + def stop(self): + self.__timer.cancel() + self.__running = False + + @staticmethod + def __timeoutHandler(_): + pass + + def __run(self): + if (not self.__running) | self.cycle <= 0: # TODO:测试实例被删除后执行这段语句的结果 + return + self.__timer = threading.Timer(self.cycle, self.__run) + self.__timer.setDaemon(self.daemonic) + self.__timer.start() + self.func(*self.args, **self.kwargs) + + def start(self): + if self.__running | self.cycle <= 0: + return + self.__running = True + self.__timer = threading.Timer(self.cycle, self.__run) + self.__timer.setDaemon(self.daemonic) + self.__timer.start() diff --git a/sdk/touchpad.py b/sdk/touchpad.py new file mode 100644 index 0000000000000000000000000000000000000000..3da2956b2cfdbb18e5c5a96fb45926c0c0e2b592 --- /dev/null +++ b/sdk/touchpad.py @@ -0,0 +1,243 @@ +import threading +import time + + +class TouchRecoder: + def __init__(self): + self.Touch = 0 + self.TouchGestureId = 0 + self.TouchCount = 0 + + self.TouchEvenId = [0, 1, 2, 3, 4] + self.X = [0, 1, 2, 3, 4] + self.Y = [0, 1, 2, 3, 4] + self.P = [0, 1, 2, 3, 4] + + +class TouchHandler: + def __init__(self, env): + self.pool = env.pool + self.clicked = [] # 当对象被点击并松开后调用指定函数 ((x1, x2, y1, y2), func, args, kwargs) + self.touched = [] # 当对象被按下后调用指定函数,直到松开后再次调用另一指定函数 ((x1, x2, y1, y2), func1, func2, args, kwargs) + self.slide_x = [] # 当屏幕从指定区域被横向滑动后调用指定函数 ((x1, x2, y1, y2), func, args, kwargs) + self.slide_y = [] # 当屏幕从指定区域被纵向滑动后调用指定函数 ((x1, x2, y1, y2), func, args, kwargs) + self.data_lock = threading.Lock() + self.logger_touch = env.logger_env + self.signal_1 = False + self.signal_2 = False + + def add_clicked(self, area, func, *args, **kwargs): + """ + 添加一个触摸元件,⚠️:所有的回调函数必须能接收 *args, **kwargs + :param func: 回调函数 + :param area: (x1, x2, y1, y2) + :return: None + """ + if area[0] > area[1] or area[2] > area[3] or area[0] < 0 or area[1] > 296 or area[2] < 0 or area[3] > 128: + raise ValueError("Area out of range!") + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + self.clicked.append([area, func, args, kwargs, False]) + self.signal_1 = False + + def remove_clicked(self, func) -> bool: # 未测试 + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + counter = 0 + remove_list = [] + for i in self.clicked: + if i[1] == func: + remove_list.append(counter) + counter += 1 + counter = 0 + if len(remove_list) == 0: + self.signal_1 = False + return False + for i in remove_list: + del self.clicked[i - counter] + counter += 1 + self.signal_1 = False + return True + + def add_touched(self, area, func1, func2, *args, **kwargs): # TODO:添加批量导入 + if area[0] > area[1] or area[2] > area[3] or area[0] < 0 or area[1] > 296 or area[2] < 0 or area[3] > 128: + raise ValueError("Area out of range!") + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + self.touched.append([area, func1, func2, args, kwargs, False]) + self.signal_1 = False + + def remove_touched(self, func) -> bool: + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + counter = 0 + remove_list = [] + for i in self.touched: + if i[1] == func: # 只对func1进行匹配 + remove_list.append(counter) + counter += 1 + counter = 0 + if len(remove_list) == 0: + self.signal_1 = False + return False + for i in remove_list: + del self.touched[i - counter] + counter += 1 + self.signal_1 = False + return True + + def add_slide_x(self, area, func): + if area[0] > area[1] or area[2] > area[3] or area[0] < 0 or area[1] > 296 or area[2] < 0 or area[3] > 128: + raise ValueError("Area out of range!") + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + self.slide_x.append([area, func, None]) + self.signal_1 = False + + def remove_slide_x(self, func) -> bool: + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + counter = 0 + remove_list = [] + for i in self.slide_x: + if i[1] == func: + remove_list.append(counter) + counter += 1 + counter = 0 + if len(remove_list) == 0: + self.signal_1 = False + return False + for i in remove_list: + del self.slide_x[i - counter] + counter += 1 + self.signal_1 = False + return True + + def add_slide_y(self, area, func): + if area[0] > area[1] or area[2] > area[3] or area[0] < 0 or area[1] > 296 or area[2] < 0 or area[3] > 128: + raise ValueError("Area out of range!") + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + self.slide_y.append([area, func, None]) + self.signal_1 = False + + def remove_slide_y(self, func) -> bool: + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + counter = 0 + remove_list = [] + for i in self.slide_y: + if i[1] == func: + remove_list.append(counter) + counter += 1 + counter = 0 + if len(remove_list) == 0: + self.signal_1 = False + return False + for i in remove_list: + del self.slide_y[i - counter] + counter += 1 + self.signal_1 = False + return True + + def clear(self): + self.signal_1 = True + while True: + if not self.signal_2: + break + time.sleep(0.1) + self.clicked = [] + self.touched = [] + self.slide_x = [] + self.slide_y = [] + self.signal_1 = False + + def handle(self, ICNT_Dev: TouchRecoder, ICNT_Old: TouchRecoder): # 此函数只可在主线程中运行 + while True: + if not self.signal_1: + break + time.sleep(0.1) + self.signal_2 = True + if ICNT_Dev.Touch and ICNT_Old.Touch: # 如果保持一直触摸不变 + if not (ICNT_Dev.X[0] == ICNT_Old.X[0] and ICNT_Dev.Y[0] == ICNT_Old.Y[0]): + self.logger_touch.debug("触摸位置变化:[%s, %s]" % (ICNT_Dev.X[0], ICNT_Dev.Y[0])) + for i in self.touched: # 扫描touch + if i[0][0] <= ICNT_Dev.X[0] <= i[0][1] and i[0][2] <= ICNT_Dev.Y[0] <= i[0][3]: + if not i[-1]: + self.pool.add(i[1], *i[3], **i[4]) # 如果被点击,且标记为False,则执行func1 + i[-1] = True + else: + if i[-1]: + self.pool.add(i[2], *i[3], **i[4]) # 如果没有被点击,且标记为True,则执行func2 + i[-1] = False + + elif ICNT_Dev.Touch and (not ICNT_Old.Touch): # 如果开始触摸 + self.logger_touch.debug("触摸事件开始:[%s, %s]" % (ICNT_Dev.X[0], ICNT_Dev.Y[0])) + for i in self.touched: # 扫描touch + if i[0][0] <= ICNT_Dev.X[0] <= i[0][1] and i[0][2] <= ICNT_Dev.Y[0] <= i[0][3]: + self.pool.add(i[1], *i[3], **i[4]) # 如果被点击,且标记为False,则执行func1 + i[-1] = True + + for i in self.clicked: + if i[0][0] <= ICNT_Dev.X[0] <= i[0][1] and i[0][2] <= ICNT_Dev.Y[0] <= i[0][3]: + i[-1] = True + + for i in self.slide_x: + if i[0][0] <= ICNT_Dev.X[0] <= i[0][1] and i[0][2] <= ICNT_Dev.Y[0] <= i[0][3]: + i[-1] = (ICNT_Dev.X[0], ICNT_Dev.Y[0]) + + for i in self.slide_y: + if i[0][0] <= ICNT_Dev.X[0] <= i[0][1] and i[0][2] <= ICNT_Dev.Y[0] <= i[0][3]: + i[-1] = (ICNT_Dev.X[0], ICNT_Dev.Y[0]) + + elif (not ICNT_Dev.Touch) and ICNT_Old.Touch: # 如果停止触摸 + self.logger_touch.debug("触摸事件终止:[%s, %s]" % (ICNT_Dev.X[0], ICNT_Dev.Y[0])) + + for i in self.touched: + if i[-1]: + self.pool.add(i[2], *i[3], **i[4]) # 如果没有被点击,且标记为True,则执行func2 + i[-1] = False + + for i in self.clicked: + if i[-1]: + if i[0][0] <= ICNT_Old.X[0] <= i[0][1] and i[0][2] <= ICNT_Old.Y[0] <= i[0][3]: + self.pool.add(i[1], *i[2], **i[3]) + i[-1] = False + + for i in self.slide_x: # ⚠️参数需要经过测试后调整 + if i[-1] is not None: + if (abs(ICNT_Dev.Y[0] - i[-1][1]) <= 85) and (abs(ICNT_Dev.X[0] - i[-1][0]) >= 50): + self.pool.add(i[1], ICNT_Dev.X[0] - i[-1][0]) + i[-1] = None + + for i in self.slide_y: + if i[-1] is not None: + if (abs(ICNT_Dev.X[0] - i[-1][0]) <= 50) and (abs(ICNT_Dev.Y[0] - i[-1][1]) >= 40): + self.pool.add(i[1], ICNT_Dev.Y[0] - i[-1][1]) + i[-1] = None + + self.signal_2 = False diff --git a/updater.py b/updater.py new file mode 100644 index 0000000000000000000000000000000000000000..2809fc34b8052c9e401b9edc6c8528cfbc5c17a4 --- /dev/null +++ b/updater.py @@ -0,0 +1,117 @@ +import json +import os +import threading + +import requests +import traceback + +from sdk import logger +from sdk import graphics +from sdk import environment +from PIL import Image +from sdk import configurator + +branch = "develop" +version = 1 +version_name = "beta_0_1" +repository = "https://gitee.com/fu1fan/eink-clock-mP" + + +class VersionCtrl: + def __init__(self, __logger: logger.Logger) -> None: + self.logger = __logger + self.data = None + pass + + def __refresh(self) -> bool: + try: + response = requests.get("https://gitee.com/fu1fan/pi-zero-w-eink-clock.web/raw/master/update/newest.json") + response.raise_for_status() + self.data = json.loads(response.text) + except requests.RequestException: + self.logger.error(traceback.format_exc(), "无法获取最新版本信息") + return False + except json.JSONDecodeError: + self.logger.error(traceback.format_exc(), "无法解析最新版本信息") + return False + return True + + def if_update(self): + if self.__refresh(): + try: + if version >= self.data[branch]["version"]: + return True + elif version < self.data[branch]["version"]: + return self.data[branch] + except json.JSONDecodeError: + self.logger.error(traceback.format_exc(), "无法解析版本信息") + return False + return False + + def get_branches(self): # 若返回为空,则错误 + branches = [] + if self.__refresh(): + for i in self.data: + branches.append(i) + return branches + + def change_branch(self, target_branch: str): + if self.__refresh(): + try: + if target_branch == branch: + self.logger.info('用户所选分支("%s")与目标分支相同' % target_branch, "目标分支与当前分支相同") + return False + elif self.data[target_branch] is None: + self.logger.warn('"%s"分支为空' % target_branch, "分支无内容") + return False + else: + __file = open("changeBranch", "w", encoding="utf-8") + __file.write(target_branch) + self.logger.debug('准备从"%s"切换到"%s"' % (branch, target_branch)) + return self.data[target_branch]["version_name"] + except: + self.logger.error(traceback.format_exc(), "获取分支信息失败") + return False + +if __name__ == "__main__": + epd_lock = threading.RLock() + logger_updater = logger.Logger(logger.DEBUG, tag="updater") + env = environment.Env(dict(auto_sleep_time=30, refresh_time=43200, refresh_interval=30, threadpool_size=20), + logger_updater) + epd = env.epd_driver + paper = graphics.Paper(env) + if epd.IsBusy(): + logger_updater.error("The screen is busy!") + os.system("python3 main.py &") + raise RuntimeError("The screen is busy!") + if os.path.exists("reset"): + logger_updater.info("reset") + paper.background_image = Image.open(open("resources/images/reset.jpg", mode="rb")) + paper.init() + # logger = Logger(0, tag="reset") + result = os.popen("git checkout .") + os.remove("reset") + logger_updater.info("已重置") + elif os.path.exists("update"): + logger_updater.info("update") + paper.background_image = Image.open(open("resources/images/updating.jpg", mode="rb")) + paper.init() + # logger = Logger(0, tag="updater") + result = os.popen("git pull") + logger_updater.info(result.read()) + elif os.path.exists("changeBranch"): + logger_updater.info("changeBranch") + paper.background_image = Image.open(open("resources/images/change.jpg", mode="rb")) + paper.init() + # logger = Logger(0, tag="changeBranch") + file = open("changeBranch", encoding="utf-8") + targetBranch = file.read() + result = os.popen("git pull") + logger_updater.info(result.read()) + os.popen("git checkout .") + logger_updater.info("已重置") + result = os.popen("git checkout " + targetBranch) + logger_updater.info(result.read()) + paper.update_background(Image.open(open("resources/images/done.jpg", mode="rb"))) + epd.sleep() + os.system("python3 main.py &")