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 &")