71 Commits

Author SHA1 Message Date
星光-k
1ac9ca91ef Update weather.py 2025-05-09 00:46:43 +08:00
星光-k
5c8d24f43c Update main.py 2025-05-09 00:46:14 +08:00
星光-k
13adf24df4 Update weather.py 2025-03-15 11:01:52 +08:00
星光-k
09c6c3c7ac Update README.md 2025-02-08 20:15:29 +08:00
星光-k
caf0228277 Update README.md 2025-02-08 19:17:44 +08:00
星光-k
54b76b4d26 Update install.sh 2025-02-08 18:56:22 +08:00
星光-k
f25f0d8dcf Update install.sh 2025-02-08 15:01:20 +08:00
星光-k
8f0fd2e6d6 Delete bin/vendor/1 2025-02-08 14:59:05 +08:00
星光-k
7350d120ad Add files via upload 2025-02-08 06:58:26 +00:00
星光-k
db8c4344ba Create 1 2025-02-08 14:57:23 +08:00
星光-k
7759cabcf0 Update install.sh 2025-02-08 13:56:42 +08:00
星光-k
a56b337975 Rename requirements.txt to bin/requirements.txt 2025-02-08 13:50:06 +08:00
星光-k
7cd6136696 Add files via upload 2025-02-08 05:49:03 +00:00
星光-k
569852a1dc 更新 weather.py 2025-02-08 01:17:52 +00:00
星光-k
7c48d92bc1 更新 main.py 2025-02-08 00:59:44 +00:00
星光-k
9b3afeb323 Update start.sh 2025-01-27 21:52:38 +08:00
星光-k
2d3459f3d0 更新 clock.py 2025-01-23 15:21:54 +00:00
星光-k
c05eb73fb6 更新 main.py1 2025-01-23 15:21:34 +00:00
星光-k
b9cd0bab9d 更新 main.py 2025-01-23 15:20:47 +00:00
星光-k
db174e366d Update main.py 2025-01-23 12:30:07 +08:00
星光-k
0c4f04e612 Update clock.py 2025-01-23 12:29:23 +08:00
星光-k
a0f636a8d5 Update main.py 2025-01-23 12:26:28 +08:00
星光-k
d0fe962c06 Update clock.py 2025-01-23 11:35:51 +08:00
星光-k
8d3bb91ed3 Update weather.py 2025-01-23 11:10:45 +08:00
星光-k
9d69de92f2 Update main.py 2025-01-23 08:30:19 +08:00
星光-k
77fef0187e Add files via upload 2025-01-22 05:41:51 +00:00
星光-k
19177a14ba 更新 main.py 2025-01-21 16:23:15 +00:00
星光-k
6d7fdab6e1 更新 main.py 2025-01-21 15:46:36 +00:00
星光-k
3842f61add 更新 main.py 2025-01-21 14:08:26 +00:00
星光-k
f1a2e59e4b 更新 main.py 2025-01-21 13:46:07 +00:00
星光-k
b8c1df3ee2 Update README.md 2025-01-21 13:46:38 +08:00
星光-k
da6b99e99c Add files via upload 2025-01-21 05:46:11 +00:00
星光-k
01ea753627 Add files via upload 2025-01-21 05:43:27 +00:00
星光-k
4c3fefa9ff Update README.md 2025-01-21 13:41:02 +08:00
星光-k
c7275aa400 Update README.md 2025-01-21 13:40:47 +08:00
星光-k
a1e9356c83 Update README.md 2025-01-21 13:40:27 +08:00
星光-k
914129a181 Update main.py 2025-01-21 13:38:15 +08:00
星光-k
c0d776da36 更新 clean.sh 2025-01-21 00:53:36 +00:00
星光-k
7a76aef402 更新 e-Paper_clean.servic 2025-01-20 23:19:27 +00:00
星光-k
c3666059ce 更新 clean.sh 2025-01-20 23:16:51 +00:00
星光-k
dfae4181cb 更新 e-Paper_clean.service 2025-01-20 23:16:32 +00:00
星光-k
0085656d20 更新 weather.py 2025-01-20 18:26:50 +00:00
星光-k
f8857cbd17 更新 weather.py 2025-01-20 18:20:54 +00:00
星光-k
90b6ae1403 更新 main.py 2025-01-20 17:19:47 +00:00
星光-k
e9b284a9b3 更新 main.py 2025-01-20 15:23:38 +00:00
星光-k
7405aabf2c 更新 raspi_e-Paper.service 2025-01-20 13:38:04 +00:00
星光-k
ca450afb14 更新 raspi_e-Paper.service 2025-01-20 13:28:20 +00:00
星光-k
c82aadd4e7 更新 raspi_e-Paper.service 2025-01-20 13:27:31 +00:00
星光-k
0a96f265f7 更新 raspi_e-Paper.service 2025-01-20 12:33:21 +00:00
星光-k
298a3f43f1 更新 main.py 2025-01-19 06:51:43 +00:00
星光-k
17544b607a 更新 main.py 2025-01-19 06:41:04 +00:00
星光-k
99780f1bf9 Update main.py 2025-01-19 10:35:23 +08:00
星光-k
118cc0a2ee Update main.py 2025-01-19 06:35:24 +08:00
星光-k
3155088921 Update weather.py 2025-01-19 06:11:12 +08:00
星光-k
28f0a9d37d Update main.py 2025-01-19 06:10:35 +08:00
星光-k
fc1678aa95 Update install.sh 2025-01-19 05:48:39 +08:00
星光-k
f0eb8dc53e Update start.sh 2025-01-19 05:47:35 +08:00
星光-k
9e5114b7bd Update start.sh 2025-01-19 05:47:17 +08:00
星光-k
bb44237caa Delete webui directory 2025-01-19 05:46:48 +08:00
星光-k
e82fd225f5 Update main.py 2025-01-19 05:44:46 +08:00
星光-k
c67ae6bd78 Update main.py 2025-01-19 04:11:49 +08:00
星光-k
72c2b23566 Update main.py 2025-01-19 04:02:43 +08:00
星光-k
590494d734 Update start.sh 2025-01-19 01:20:53 +08:00
星光-k
8a29bf9110 Update main.py 2025-01-19 01:20:15 +08:00
星光-k
ab6abaf2e3 Update weather.py 2025-01-19 01:19:21 +08:00
星光-k
5436cba3d0 Update install.sh 2025-01-18 09:38:47 +08:00
星光-k
70d1b3d710 Delete webui/1 2025-01-18 09:11:52 +08:00
星光-k
6fa3fe1407 Add files via upload 2025-01-18 01:11:29 +00:00
星光-k
cd6fc91cda Create 1 2025-01-18 09:10:49 +08:00
星光-k
60272f75ea Delete app directory 2025-01-18 09:09:51 +08:00
星光-k
faf0e2f505 Add files via upload 2025-01-18 01:09:34 +00:00
33 changed files with 803 additions and 1697 deletions

View File

@@ -1,6 +1,6 @@
# 墨水屏展示当前时间及天气数据
本项目复刻自[Seek-Huang](https://github.com/Seek-Huang/2.13-Ink-screen-clock)的代码仓库
# 此分支专属于本人的个人设备实验分支以及备份
本项目复刻自[Seek-Huang](https://github.com/Seek-Huang)的[代码仓库](https://github.com/Seek-Huang/2.13-Ink-screen-clock)
并在此基础上进行改进
## 本仓库已添加[一键安装部署脚本](https://github.com/kxgx/2.13-Ink-screen-clock#%E4%BD%BF%E7%94%A8%E8%84%9A%E6%9C%AC%E7%9B%B4%E6%8E%A5%E5%AE%89%E8%A3%85%E6%8E%A8%E8%8D%90)
@@ -66,6 +66,7 @@ sudo reboot
--zh 设置系统语言为zh_CN,UTF-8
--cn 替换apt镜像源为中国镜像源
--gitcn 克隆中国仓库
--pip-offline pip依赖离线安装
--pisugar-wifi-conf 安装pisugar-wifi-conf
--pisugar-power-manager 安装pisugar-power-manager
--version <tag> 版本号(使用方法 --version + 仓库标签,格式例如 v1.x.x ,可以是主仓库main)
@@ -73,22 +74,29 @@ sudo reboot
```
###
```Bash
#中国源默认设置
#中国源默认设置(不加参数)
curl -sSL https://gitee.com/xingguangk/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash
```
```Bash
#中国源参数设置(不使用--debug参数)
curl -sSL https://gitee.com/xingguangk/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash -s -- --zh --cn --gitcn --pisugar-power-manager --pisugar-wifi-conf --version <tag>
#中国源参数设置(不使用--debug参数,替换cn镜像源,pip在线安装,不安装pisugar)
curl -sSL https://gitee.com/xingguangk/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash -s -- --zh --cn --gitcn --version <tag>
```
```Bash
#默认源默认设置
#中国源参数设置(不使用--debug参数,替换cn镜像源,pip依赖离线安装,不安装pisugar)
curl -sSL https://gitee.com/xingguangk/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash -s -- --zh --cn --gitcn --pip-offline --version <tag>
```
```Bash
#默认源默认设置(不加参数)
curl -sSL https://github.com/kxgx/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash
```
```Bash
#默认源全参数设置(不使用--debug参数)
curl -sSL https://github.com/kxgx/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash -s -- --zh --cn --gitcn --pisugar-power-manager --pisugar-wifi-conf --version <tag>
#默认源默认设置(不使用--debug参数,替换cn镜像源,pip在线安装,不安装pisugar)
curl -sSL https://github.com/kxgx/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash -s -- --zh --cn --gitcn --version <tag>
```
```Bash
#默认源默认设置(不使用--debug参数,替换cn镜像源,pip依赖离线安装,不安装pisugar)
curl -sSL https://github.com/kxgx/2.13-Ink-screen-clock/raw/main/bin/install.sh | sudo bash -s -- --zh --cn --gitcn --pip-offline --version <tag>
```
## 需要安装的软件和依赖:
参考
微雪电子 https://www.waveshare.net/wiki/2.13inch_e-Paper_HAT+#Raspberry_Pi

View File

@@ -1,79 +0,0 @@
from flask import Flask, render_template, request, send_from_directory, redirect, url_for, jsonify
import os
import re
import subprocess
app = Flask(__name__, template_folder='webui', static_url_path='', static_folder='webui')
FONT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'pic') # 字体文件夹路径
MAIN_PY_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'bin', 'main.py')
def list_font_files(font_dir):
try:
return os.listdir(font_dir)
except Exception as e:
print(f"Error listing font files: {e}")
return [] # 返回空列表以避免迭代错误
@app.route('/execute-shell')
def execute_shell():
# 替换以下命令为您想要执行的Shell命令
command = "sudo reboot now"
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
return jsonify({'status': 'success', 'output': result.stdout})
else:
return jsonify({'status': 'error', 'output': result.stderr})
@app.route('/edit_main_py')
def edit_main_py():
try:
with open(MAIN_PY_PATH, 'r') as file:
content = file.read()
except Exception as e:
return f"Error reading main.py: {e}", 500
return render_template('edit_main_py.html', content=content)
@app.route('/save_main_py', methods=['POST'])
def save_main_py():
new_content = request.form.get('content')
if new_content is None:
return "No content provided", 400
try:
with open(MAIN_PY_PATH, 'w') as file:
file.write(new_content)
except Exception as e:
return f"Error saving main.py: {e}", 500
return redirect(url_for('index'))
@app.route('/')
def index():
font_files = list_font_files(FONT_DIR)
return render_template('index.html', font_files=font_files)
@app.route('/upload', methods=['POST'])
def upload():
if 'font_file' not in request.files:
return '没有文件部分'
file = request.files['font_file']
if file.filename == '':
return '没有选择文件'
if file:
filename = os.path.join(FONT_DIR, file.filename)
file.save(filename)
return '文件已上传成功'
@app.route('/update_font_names')
def update_font_names():
font_files = list_font_files(FONT_DIR)
return render_template('update_font_names.html', font_files=font_files)
@app.route('/fonts/<filename>')
def fonts(filename):
return send_from_directory(FONT_DIR, filename)
if not os.path.exists(FONT_DIR):
os.makedirs(FONT_DIR)
if __name__ == '__main__':
# 绑定到0.0.0.0,允许远程访问
app.run(host='0.0.0.0', port=80, debug=False)

View File

@@ -1,9 +0,0 @@
/*!
Theme: Default
Description: Original highlight.js style
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
Maintainer: @highlightjs/core-team
Website: https://highlightjs.org/
License: see project LICENSE
Touched: 2021
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@@ -1,59 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>编辑 main.py</title>
<!-- 引用本地的highlight.js CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='default.min.css') }}">
</head>
<body>
<h1>编辑 main.py</h1>
<!-- 添加一个表单来提交编辑后的代码 -->
<form method="post" action="{{ url_for('save_main_py') }}">
<pre style="background-color: black !important;"><code class="python" id="code">{{ content }}</code></pre>
<input type="hidden" name="content" id="hidden-content">
<input type="submit" value="保存">
</form>
<!-- 引用本地的highlight.js JavaScript -->
<script src="{{ url_for('static', filename='highlight.min.js') }}"></script>
<script>
// 初始化highlight.js
document.addEventListener('DOMContentLoaded', (event) => {
hljs.highlightAll();
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var preElements = document.querySelectorAll('pre.code-editor');
preElements.forEach(function(pre) {
pre.style.backgroundColor = 'black';
});
});
</script>
<h2>重启</h2>
</head>
<body>
<button id="executeShellBtn">重启</button>
<script>
document.getElementById('executeShellBtn').addEventListener('click', function() {
fetch('/execute-shell')
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('Command executed successfully:\n' + data.output);
} else {
alert('Error executing command:\n' + data.output);
}
})
.catch(error => {
alert('Error: ' + error);
});
});
</script>
</body>
</html>
 

File diff suppressed because one or more lines are too long

View File

@@ -1,27 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字体上传和管理</title>
</head>
<body>
<h1>上传字体文件</h1>
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="font_file">
<input type="submit" value="上传">
</form>
<h2>已上传的字体文件:</h2>
<ul>
{% for font_file in font_files %}
<li>{{ font_file }}</li>
{% else %}
<li>没有找到字体文件。</li>
{% endfor %}
</ul>
<!-- 添加跳转到edit_main_py的按钮 -->
<a href="/edit_main_py"><button>编辑main.py</button></a>
</body>
</html>

View File

@@ -1,26 +0,0 @@
<!-- update_font_names.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新字体文件名</title>
</head>
<body>
<h1>更新字体文件名</h1>
<form method="post" action="/save_font_names">
<label for="font01">字体文件名 01:</label>
<input type="text" id="font01" name="font01"><br>
<label for="font02">字体文件名 02:</label>
<input type="text" id="font02" name="font02"><br>
<label for="font03">字体文件名 03:</label>
<input type="text" id="font03" name="font03"><br>
<label for="font04">字体文件名 04:</label>
<input type="text" id="font04" name="font04"><br>
<label for="font05">字体文件名 05:</label>
<input type="text" id="font05" name="font05"><br>
<label for="font06">字体文件名 06:</label>
<input type="text" id="font06" name="font06"><br>
<input type="submit" value="保存">
</form>
</body>
</html>

View File

@@ -4,8 +4,7 @@ f_name="clean.py"
dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
logdir="${dir%/*}/log"
service_name="raspi_e-Paper.service"
hold_time_seconds=5 # 设置维持时间例如300秒5分钟
# 停止raspi_e-Paper服务
echo "正在停止$service_name服务..."
sudo systemctl stop "$service_name"
@@ -19,9 +18,6 @@ if [ -n "$pids" ]; then
kill -9 $pids
fi
# 使用nohup在后台运行Python脚本并将输出重定向到日志文件
nohup /usr/bin/python3 -u "$dir/$f_name" > "${logdir}/info-clean.log" 2>&1 &
# 等待维持时间
echo "正在运行脚本,将在 ${hold_time_seconds} 秒后停止服务..."
sleep $hold_time_seconds
echo "正在清除屏幕内容"
# 运行Python脚本并将输出重定向到日志文件
/usr/bin/python3 -u "$dir/$f_name" > "${logdir}/info-clean.log" 2>&1

View File

@@ -20,7 +20,7 @@ if os.path.exists(libdir):
logging.debug("Loading Fonts")
font01 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 20) #字体文件
font02 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 15) #字体文件
font03 = ImageFont.truetype(os.path.join(picdir, 'DSEG7Modern-Bold.ttf'), 38) #字体文件
font03 = ImageFont.truetype(os.path.join(picdir, 'DSEG7Modern-Bold.ttf'), 66) #字体文件
font04 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 10) #字体文件
font05 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 12) #字体文件
font06 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 13) #字体文件
@@ -35,11 +35,77 @@ def get_date():#返回当前年月日及星期几
today=LunarDate.today()
week_day_dict = {0: '星期一',1: '星期二',2: '星期三',3: '星期四',4: '星期五',5: '星期六',6: '星期日',}
day = date.weekday()
return time.strftime('%Y年%m月%d')+' '+week_day_dict[day]+' '+today.strftime(' %M月%D')
def get_time():#返回当前时间,不到秒,大写
return time.strftime('%H:%M')
def Get_address():#获取当前的IP地址
return (subprocess.check_output(u"hostname -I | cut -d\' \' -f1 | head --bytes -1", shell = True ).decode('gbk'))
return time.strftime('%Y年%m月%d')+''+week_day_dict[day]+''+today.strftime('%M月%D')
# 定义一个全局标志变量,用于检查是否已经设置了系统时间或尝试过设置
has_set_system_time = False
def set_system_time_from_hwclock(utc=True):
"""Set the system time from the hardware clock.
Args:
utc (bool): Whether the RTC is in UTC. Default is True.
"""
try:
# 记录调用 hwclock 前的时间
before_time = datetime.datetime.now()
logging.debug(f"System time before hwclock call: {before_time}")
# 构造 hwclock 命令及其参数
hwclock_args = ['sudo', 'hwclock', '--hctosys']
if not utc:
hwclock_args.append('--localtime')
logging.debug(f"Executing hwclock command: {' '.join(hwclock_args)}")
# 使用 subprocess.run 执行 hwclock --hctosys 并捕获输出
result = subprocess.run(hwclock_args,
check=True, # 如果命令失败,则抛出 CalledProcessError 异常
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
# 等待一小段时间以确保时间更新完成
time.sleep(0.1) # 根据需要调整
# 记录调用 hwclock 后的时间
after_time = datetime.datetime.now()
logging.debug("System time successfully set from hardware clock.")
logging.debug(f"System time after hwclock call: {after_time}")
# 检查时间是否发生了倒退
if after_time < before_time:
logging.warning(f"Time went backwards after hwclock call: before={before_time}, after={after_time}")
return True
except subprocess.CalledProcessError as e:
logging.error(f"Failed to set system time from hardware clock: {e.stderr}")
return False
except Exception as e:
logging.error(f"An unexpected error occurred: {str(e)}")
return False
def get_time():
global has_set_system_time
if not has_set_system_time:
# 尝试从硬件时钟设置系统时间,默认假设 RTC 是 UTC
success = set_system_time_from_hwclock(utc=True)
# 无论成功与否,都更新标志变量以避免重复尝试
has_set_system_time = True
# 获取并返回当前时间,格式为 HH:MM 大写
current_time = time.strftime('%H:%M').upper()
logging.debug(f"Returning current time: {current_time}")
return current_time
def Get_ipv4_address(): # 获取当前的IP地址
try:
ip_output = subprocess.check_output(
"hostname -I | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}'", shell=True).decode('utf-8').strip()
ip_list = ip_output.split()
filtered_ips = [ip for ip in ip_list if not ip.startswith("172.")]
return filtered_ips[0] if filtered_ips else "地址获取失败"
except subprocess.CalledProcessError:
return "获取失败"
def CPU_temperature():#CPU温度获取
temperatura = os.popen('vcgencmd measure_temp').readline()
temperatura = temperatura.replace('temp=','').strip()
@@ -70,7 +136,7 @@ def Bottom_edge(): #在图片中添加底边内容
draw.line((199,109,199,114),fill=255, width=1)
draw.line((200,114,204,114),fill=255, width=1)
global local_addr #获取当前IP地址
local_addr= Get_address() #获取当前IP地址
local_addr= Get_ipv4_address() #获取当前IP地址
draw.text((10,107),"IP:"+local_addr,font = font05,fill =255)#显示当前IP地址
def Basic_refresh(): #全刷函数
logging.info("Refresh and prepare the basic content before starting the canvas")#开始画布前刷新准备基础内容
@@ -94,8 +160,8 @@ def Partial_refresh():#局刷函数
global local_time
local_time1=get_time()
if (local_time1==local_time) ==False:
draw.rectangle((5, 40, 133, 82), fill = 255) #时间局刷区域
draw.text((5,40),local_time1,font = font03,fill =0)#刷新当前时间
draw.rectangle((5, 17, 133, 95), fill = 255) #时间局刷区域
draw.text((5,17),local_time1,font = font03,fill =0)#刷新当前时间
local_time=local_time1
Local_strong_brush() #局部强刷
get_date_var1=get_date() #局刷判断,如果时间与前一次不一致说明内容变化,需要刷新显示
@@ -107,7 +173,7 @@ def Partial_refresh():#局刷函数
logging.debug("头部日期部位发生刷新变化.")
Local_strong_brush() #局部强刷
global local_addr #当前IP地址
local_addr1 = Get_address()
local_addr1 = Get_ipv4_address()
if (local_addr1==local_addr) ==False:
draw.rectangle((1, 107, 94, 120), fill = 0) #设置头部刷新区域
draw.text((10,107),"IP:"+local_addr1,font = font05,fill =255)#显示当前IP地址
@@ -116,40 +182,37 @@ def Partial_refresh():#局刷函数
#显示当前电量百分比
power_str=power_str1
Local_strong_brush() #局部强刷
logging.info("电源电量局部刷新")
try:
##################屏幕初始化#########################
getWeath()#天气获取函数开始运行
epd = epd2in13_V4.EPD() #初始化
epd.init()#设定屏幕刷新模式
#epd.Clear(0xFF) #清除屏幕内容
##################屏幕初始化#########################
logging.info("Width = %s, Height = %s", format(epd.width), format(epd.height)) #打印屏幕高度及宽度
logging.info("Initialize and clear the display")#屏幕开始准备相关展示
info_image = Image.new('1', (epd.height, epd.width), 255) #画布创建准备
draw = ImageDraw.Draw(info_image)
Basic_refresh() #全局刷新
Partial_refresh() #局部刷新
epd.init()
epd.Clear(0xFF)
epd.sleep()
except IOError as e:
logging.info(e)
except KeyboardInterrupt:
logging.info("Keyboard interrupt detected, exiting gracefully.")
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
logging.debug("电源电量局部刷新")
except Exception as e:
logging.error("An unexpected error occurred: %s", e)
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
retry_interval = 180 # 设置重试间隔时间(秒)
while True:
try:
##################屏幕初始化#########################
epd = epd2in13_V4.EPD() #初始化
epd.init()#设定屏幕刷新模式
#epd.Clear(0xFF) #清除屏幕内容
##################屏幕初始化#########################
logging.info("Width = %s, Height = %s", format(epd.width), format(epd.height)) #打印屏幕高度及宽度
logging.info("初始化并清空显示屏")#屏幕开始准备相关展示
info_image = Image.new('1', (epd.height, epd.width), 255) #画布创建准备
draw = ImageDraw.Draw(info_image)
Basic_refresh() #全局刷新
Partial_refresh() #局部刷新
epd.init()
epd.Clear(0xFF)
epd.sleep()
time.sleep(300)
break # 如果脚本执行成功,则退出循环
except (OSError, Exception) as e: # 捕获你提到的异常
logging.error("发生了错误: %s", e)
time.sleep(retry_interval) # 等待一段时间后重试
except KeyboardInterrupt:
logging.info("检测到键盘中断,正在清理并退出")
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
# 脚本正常结束后的清理操作
epd.init()

View File

@@ -18,6 +18,8 @@ USE_CN_GIT=false
USE_PISUGAR_WIFI_CONF=false
# 检查是否安装pisugar-power-manager
USE_PISUGAR_POWER_MANAGER=false
# 检查是否使用离线安装pip依赖
USE_OFFLINE_PIP=false
# 解析命令行参数
while [ "$#" -gt 0 ]; do
@@ -31,6 +33,9 @@ while [ "$#" -gt 0 ]; do
--gitcn)
USE_CN_GIT=true
;;
--pip-offline)
USE_OFFLINE_PIP=true
;;
--pisugar-wifi-conf)
USE_PISUGAR_WIFI_CONF=true
;;
@@ -121,25 +126,14 @@ is_raspberry_pi() {
fi
}
# 定义链接变量
DEBIAN_MIRROR="http://deb.debian.org/debian/"
DEBIAN_SECURITY_MIRROR="http://security.debian.org/"
PISUGAR_WIFI_CONF_URL="https://cdn.pisugar.com/PiSugar-wificonfig/script/install.sh"
PISUGAR_POWER_MANAGER_URL="https://cdn.pisugar.com/release/pisugar-power-manager.sh"
PIPY_MIRROR="https://pypi.org/simple"
# 修改 Raspberry Pi 特定源链接
RASPBERRY_PI_SOURCE_DEBIAN11="https://archive.raspberrypi.org/debian/"
RASPBERRY_PI_SOURCE_DEBIAN12="https://archive.raspberrypi.com/debian/"
# 如果使用中国镜像源,则更新链接变量
if [ "$USE_CN_MIRROR" = true ]; then
DEBIAN_MIRROR="https://mirrors.tuna.tsinghua.edu.cn/debian/"
# 使用中国镜像源
DEBIAN_MIRROR="https://mirrors.tuna.tsinghua.edu.cn/debian"
DEBIAN_SECURITY_MIRROR="https://mirrors.tuna.tsinghua.edu.cn/debian-security"
PIPY_MIRROR="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
# 使用中国镜像源时Raspberry Pi 特定源链接保持不变
RASPBERRY_PI_SOURCE_DEBIAN11="https://mirrors.tuna.tsinghua.edu.cn/raspberrypi/"
RASPBERRY_PI_SOURCE_DEBIAN12="https://mirrors.tuna.tsinghua.edu.cn/raspberrypi/"
fi
RASPBERRY_PI_SOURCE_DEBIAN11="https://mirrors.tuna.tsinghua.edu.cn/raspberrypi"
RASPBERRY_PI_SOURCE_DEBIAN12="https://mirrors.tuna.tsinghua.edu.cn/raspberrypi"
# 定义仓库链接变量
INK_SCREEN_CLOCK_REPO_URL="https://github.com/kxgx/2.13-Ink-screen-clock"
@@ -155,6 +149,7 @@ update_sources_list() {
local raspberry_pi_source_in_use=$(grep -oP 'deb\s+\K.+' /etc/apt/sources.list.d/raspi.list | head -1)
# 检查并替换 Debian 源
if [ "$USE_CN_MIRROR" = true ]; then
if [ "$debian_mirror_in_use" != "$DEBIAN_MIRROR" ]; then
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
{
@@ -196,12 +191,13 @@ update_sources_list() {
else
echo "Raspberry Pi 源链接已更新,跳过替换" >&2
fi
fi
}
# 安装包函数
install_packages() {
echo "正在更新源列表"
if ! sudo apt-get -q -y update; then
if ! sudo apt-get -q update; then
echo "更新源列表失败" >&2
exit 1
fi
@@ -212,23 +208,7 @@ install_packages() {
fi
}
# 安装pip包函数
install_pip_packages() {
echo "正在安装pip软件包"
if ! sudo pip3 install -i "$PIPY_MIRROR" spidev borax pillow requests Flask; then
echo "pip软件包安装失败如果是最新版系统或是非lite系统" >&2
echo "请手动运行sudo pip3 install -i "$PIPY_MIRROR" spidev borax pillow requests --break-system-packages" >&2
exit 1
fi
}
# 复制服务文件并设置为开机启动
setup_service() {
local service_path="raspi_e-Paper.service"
local service1_path="e-Paper_clean.service"
local service_file_path="$HOME/2.13-Ink-screen-clock/bin/$service_path"
local service1_file_path="$HOME/2.13-Ink-screen-clock/bin/$service1_path"
install_Ink-screen-clock() {
# 检查墨水屏时钟仓库是否存在
if [ ! -d "$HOME/2.13-Ink-screen-clock" ]; then
echo "正在克隆仓库"
@@ -243,6 +223,41 @@ setup_service() {
else
echo "仓库文件夹已存在,跳过克隆"
fi
}
# 安装pip包函数
install_oline_pip_packages() {
echo "正在安装pip软件包"
if ! sudo pip3 install -i "$PIPY_MIRROR" -r "$HOME/2.13-Ink-screen-clock/bin/requirements.txt"; then
echo "pip软件包安装失败如果是最新版系统或是非lite系统" >&2
echo "请手动运行sudo pip3 install -i "$PIPY_MIRROR" -r "$HOME/2.13-Ink-screen-clock/bin/requirements.txt" --break-system-packages" >&2
exit 1
fi
}
install_offline_pip_packages() {
echo "正在安装pip软件包"
if ! sudo pip3 install --no-index --find-links="$HOME/2.13-Ink-screen-clock/bin/vendor" -r "$HOME/2.13-Ink-screen-clock/bin/requirements.txt"; then
echo "pip软件包安装失败如果是最新版系统或是非lite系统" >&2
echo "请手动运行sudo pip3 install --no-index --find-links="$HOME/2.13-Ink-screen-clock/bin/vendor" -r "$HOME/2.13-Ink-screen-clock/bin/requirements.txt" --break-system-packages" >&2
exit 1
fi
}
install_pip_packages() {
if [ "$USE_OFFLINE_PIP" = true ]; then
install_offline_pip_packages
else
install_oline_pip_packages
fi
}
# 复制服务文件并设置为开机启动
setup_service() {
local service_path="raspi_e-Paper.service"
local service1_path="e-Paper_clean.service"
local service_file_path="$HOME/2.13-Ink-screen-clock/bin/$service_path"
local service1_file_path="$HOME/2.13-Ink-screen-clock/bin/$service1_path"
# 检查服务文件是否存在
if [ -f "$service_file_path" ] && [ -f "$service1_file_path" ]; then
@@ -315,8 +330,10 @@ if [ -f /etc/debian_version ]; then
echo "执行Debian 11 (Bullseye) 相关操作"
update_sources_list "bullseye"
install_packages
install_Ink-screen-clock
install_pip_packages
setup_service
#install_webui
install_pisugar-wifi-conf
install_pisugar-power-manager
;;
@@ -324,8 +341,10 @@ if [ -f /etc/debian_version ]; then
echo "执行Debian 12 (Bookworm) 相关操作"
update_sources_list "bookworm"
install_packages
install_Ink-screen-clock
install_pip_packages
setup_service
#install_webui
install_pisugar-wifi-conf
install_pisugar-power-manager
;;

View File

@@ -21,48 +21,124 @@ if os.path.exists(libdir):
from waveshare_epd import epd2in13_V4 #引入墨水屏驱动文件
logging.debug("Loading Fonts")
font01 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 20) #字体文件
font02 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 15) #字体文件
font03 = ImageFont.truetype(os.path.join(picdir, 'DSEG7Modern-Bold.ttf'), 38) #字体文件
font02 = ImageFont.truetype(os.path.join(picdir, 'GB2312.ttf'), 15) #字体文件
font03 = ImageFont.truetype(os.path.join(picdir, 'Fonttt.ttf'), 48) #字体文件
font04 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 10) #字体文件
font05 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 12) #字体文件
font06 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 13) #字体文件
font06 = ImageFont.truetype(os.path.join(picdir, '原神cn.ttf'), 13) #字体文件
################################引入配置文件结束################################################
def Local_strong_brush(): #局部强制刷新显示
i = 0
while i < 5:
epd.displayPartial(epd.getbuffer(info_image.rotate(180)))#局刷开始
i = i + 1
def get_date():#返回当前年月日及星期几
def get_date(): # 返回当前年月日及星期几
date = datetime.datetime.now()
today=LunarDate.today()
week_day_dict = {0: '星期一',1: '星期二',2: '星期三',3: '星期四',4: '星期五',5: '星期六',6: '星期日',}
today = LunarDate.today()
week_day_dict = {0: '星期一', 1: '星期二', 2: '星期三', 3: '星期四', 4: '星期五', 5: '星期六', 6: '星期日'}
day = date.weekday()
return time.strftime('%Y年%m月%d')+' '+week_day_dict[day]+' '+today.strftime(' %M月%D')
def get_time():#返回当前时间,不到秒,大写
return time.strftime('%H:%M')
def Get_address():#获取当前的IP地址
return (subprocess.check_output(u"hostname -I | cut -d\' \' -f1 | head --bytes -1", shell = True ).decode('gbk'))
return f"{date.strftime('%Y年%m月%d')}{week_day_dict[day]}{today.strftime('%M月%D')}"
# 定义一个全局标志变量,用于检查是否已经设置了系统时间或尝试过设置
has_set_system_time = False
def set_system_time_from_hwclock(utc=True):
"""Set the system time from the hardware clock.
Args:
utc (bool): Whether the RTC is in UTC. Default is True.
"""
try:
# 记录调用 hwclock 前的时间
before_time = datetime.datetime.now()
logging.debug(f"System time before hwclock call: {before_time}")
# 构造 hwclock 命令及其参数
hwclock_args = ['sudo', 'hwclock', '--hctosys']
if not utc:
hwclock_args.append('--localtime')
logging.debug(f"Executing hwclock command: {' '.join(hwclock_args)}")
# 使用 subprocess.run 执行 hwclock --hctosys 并捕获输出
result = subprocess.run(hwclock_args,
check=True, # 如果命令失败,则抛出 CalledProcessError 异常
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
# 等待一小段时间以确保时间更新完成
time.sleep(0.1) # 根据需要调整
# 记录调用 hwclock 后的时间
after_time = datetime.datetime.now()
logging.debug("System time successfully set from hardware clock.")
logging.debug(f"System time after hwclock call: {after_time}")
# 检查时间是否发生了倒退
if after_time < before_time:
logging.warning(f"Time went backwards after hwclock call: before={before_time}, after={after_time}")
return True
except subprocess.CalledProcessError as e:
logging.error(f"Failed to set system time from hardware clock: {e.stderr}")
return False
except Exception as e:
logging.error(f"An unexpected error occurred: {str(e)}")
return False
def get_time():
global has_set_system_time
if not has_set_system_time:
# 尝试从硬件时钟设置系统时间,默认假设 RTC 是 UTC
success = set_system_time_from_hwclock(utc=True)
# 无论成功与否,都更新标志变量以避免重复尝试
has_set_system_time = True
# 获取并返回当前时间,格式为 HH:MM 大写
current_time = time.strftime('%H:%M').upper()
logging.debug(f"Returning current time: {current_time}")
return current_time
def Get_ipv4_address(): # 获取当前的IP地址
try:
# 执行命令获取IP地址并处理输出以仅返回IPv4地址
ip_output = subprocess.check_output("hostname -I | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}'", shell=True).decode('utf-8').strip()
# 检查是否为IPv4地址
if ip_output and not ip_output.startswith("127."):
return ip_output
else:
return "IPv4获取失败"
except subprocess.CalledProcessError as e:
return f"获取失败"
def CPU_temperature():#CPU温度获取
temperatura = os.popen('vcgencmd measure_temp').readline()
temperatura = temperatura.replace('temp=','').strip()
return str(temperatura)
def Memory_footprint():#显示内存占用百分比
return(subprocess.check_output(u"free -m | awk -F '[ :]+' 'NR==2{printf \"%d\", ($3)/$2*100}'", shell = True ).decode('gbk'))
def CPU_usage(): #显示CPU占用百分比
return(str(int(float(os.popen("top -b -n1 | awk '/Cpu\(s\):/ {print $2}'").readline().strip()))))
def power_battery():#获取当前电池电量
return(str(int(subprocess.check_output(u"echo \"get battery\" | nc -q 0 127.0.0.1 8423|awk -F':' '{print int($2)}'", shell = True ).decode('gbk')))+u'%')
ip_output = subprocess.check_output(
"hostname -I | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}'", shell=True).decode('utf-8').strip()
ip_list = ip_output.split()
filtered_ips = [ip for ip in ip_list if not ip.startswith("172.")]
return filtered_ips[0] if filtered_ips else "地址获取失败"
except subprocess.CalledProcessError:
return "获取失败"
# 全局变量声明
last_power = None # 用于存储上一次获取的电量值
last_power_time = 0 # 用于存储上一次获取电量的时间戳
def power_battery(): # 获取当前电池电量
global last_power, last_power_time
current_time = time.time() # 获取当前时间戳
# 每3分钟或首次获取时更新
if (current_time - last_power_time >= 180) or (last_power is None):
try:
# 执行命令获取电池电量
result = subprocess.check_output(
u"echo \"get battery\" | nc -q 0 127.0.0.1 8423 | awk -F':' '{print int($2)}'",
shell=True, stderr=subprocess.STDOUT
).decode('gbk').strip()
new_power = f"{int(result)}%" # 格式化电量值
# 仅在电量变化或首次时更新显示
if new_power != last_power or last_power is None:
logging.info(f"电池电量更新: {new_power}")
last_power = new_power # 更新缓存值
last_power_time = current_time # 更新获取时间
except Exception as e:
logging.error(f"电量获取失败: {str(e)}")
new_power = last_power if last_power else "0%" # 失败时使用缓存值或默认值
else:
new_power = last_power # 未到更新时间,使用缓存值
return new_power
# 打印电量信息
print(power_battery())
def Bottom_edge(): #在图片中添加底边内容
@@ -85,35 +161,39 @@ def Bottom_edge(): #在图片中添加底边内容
global local_addr #获取当前IP地址
local_addr= Get_ipv4_address() #获取当前IP地址
draw.text((10,107),"IP:"+local_addr,font = font05,fill =255)#显示当前IP地址
def Weather(): #在图片中添加天气内容
Weather_json = open('/root/2.13-Ink-screen-clock/bin/weather.json','r')
Weather_data = Weather_json.read()
Weather_json.close()
Weather_text=json.loads(Weather_data)
global Weather_position
global temperature
global weather
global wind_direction
global weather_update
global weather_date
global humidity
Weather_position = Weather_text['cityname'] #定位位置
temperature=Weather_text['temp']+u'°C' #温度
weather = Weather_text['weather'] #天气情况
wind_direction = Weather_text['WD'] #风向
weather_update = Weather_text['time'] #天气更新时间
weather_date = Weather_text['date'] #日期
humidity = Weather_text['SD'] #湿度
draw.text((150,25),"天气:",font = font06,fill =0)#显示当前天气前缀
draw.text((150,45),"度:",font = font06,fill =0)#显示当前度前缀
draw.text((150,65),"湿度:",font = font06,fill =0)#显示当前湿度前缀
draw.text((150,85),"城市:",font = font06,fill =0)#显示当前城市前缀
draw.text((191,25),weather,font = font06,fill =0)
draw.text((191,45),temperature,font = font06,fill =0)
draw.text((191,65),humidity,font = font06,fill =0)
draw.text((191,85),Weather_position,font = font06,fill =0)
draw.text((211,107),weather_update,font = font05,fill =255) #显示天气更新时间
def Weather():
try:
with open('/root/2.13-Ink-screen-clock/bin/weather.json', 'r') as Weather_json:
Weather_data = Weather_json.read()
if not Weather_data.strip(): # 检查文件是否为空
logging.error("天气数据文件为空")
return
Weather_text = json.loads(Weather_data)
global Weather_position, temperature, weather, wind_direction, weather_update, weather_date, humidity
Weather_position = Weather_text.get('cityname', '未知') # 使用get方法提供默认值
temperature = f"{Weather_text.get('temp', '--')}°C"
weather = Weather_text.get('weather', '未知')
wind_direction = Weather_text.get('WD', '未知')
weather_update = Weather_text.get('time', '未知')
weather_date = Weather_text.get('date', '未知')
humidity = Weather_text.get('SD', '未知')
draw.text((150,25),"天气:",font = font06,fill =0)#显示当前天气前缀
draw.text((150,45),"温度:",font = font06,fill =0)#显示当前温度前缀
draw.text((150,65),"湿度:",font = font06,fill =0)#显示当前湿度前缀
draw.text((150,85),"城市:",font = font06,fill =0)#显示当前城市前缀
draw.text((191,25),weather,font = font06,fill =0)
draw.text((191,45),temperature,font = font06,fill =0)
draw.text((191,65),humidity,font = font06,fill =0)
draw.text((191,85),Weather_position,font = font06,fill =0)
draw.text((211,107),weather_update,font = font05,fill =255) #显示天气更新时间
except FileNotFoundError:
logging.error("天气数据文件未找到")
except json.JSONDecodeError as e:
logging.error(f"天气数据解析失败: {str(e)}")
except Exception as e:
logging.error(f"获取天气信息时发生错误: {str(e)}")
def Basic_refresh(): #全刷函数
logging.info("在启动画布之前,刷新并准备基本内容")#开始画布前刷新准备基础内容
global get_date_var
@@ -121,7 +201,7 @@ def Basic_refresh(): #全刷函数
draw.text((2,2),get_date_var,font = font02,fill =0)#将日期及星期几显示到屏幕
global local_time
local_time=get_time()
draw.text((5,40),local_time,font = font03,fill =0)#显示当前时间
draw.text((5,28),local_time,font = font03,fill =0)#显示当前时间
Bottom_edge() #添加底边内容
Weather() #天气内容
epd.display(epd.getbuffer(info_image.rotate(180)))
@@ -137,8 +217,8 @@ def Partial_refresh():#局刷函数
global local_time
local_time1=get_time()
if (local_time1==local_time) ==False:
draw.rectangle((5, 40, 133, 82), fill = 255) #时间局刷区域
draw.text((5,40),local_time1,font = font03,fill =0)#刷新当前时间
draw.rectangle((5, 28, 149, 82), fill = 255) #时间局刷区域
draw.text((5,28),local_time1,font = font03,fill =0)#刷新当前时间
local_time=local_time1
Local_strong_brush() #局部强刷
get_date_var1=get_date() #局刷判断,如果时间与前一次不一致说明内容变化,需要刷新显示
@@ -213,43 +293,41 @@ def Partial_refresh():#局刷函数
draw.text((129,108),power_battery(),font = font04,fill =255) #显示当前电量百分比
power_str=power_str1
Local_strong_brush() #局部强刷
logging.info("电源电量局部刷新")
try:
##################屏幕初始化#########################
epd = epd2in13_V4.EPD() #初始化
epd.init()#设定屏幕刷新模式
#epd.Clear(0xFF) #清除屏幕内容
##################屏幕初始化#########################
logging.info("Width = %s, Height = %s", format(epd.width), format(epd.height)) #打印屏幕高度及宽度
logging.info("初始化并清空显示屏")#屏幕开始准备相关展示
info_image = Image.new('1', (epd.height, epd.width), 255) #画布创建准备
draw = ImageDraw.Draw(info_image)
Basic_refresh() #全局刷新
Partial_refresh() #局部刷新
epd.init()
epd.Clear(0xFF)
epd.sleep()
except OSError as e:
logging.info(e)
except KeyboardInterrupt:
logging.info("检测到键盘中断,正在清理并退出")
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
# logging.info("电池电量局部刷新")
except Exception as e:
logging.error("发生了意外的错误: %s", e)
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
retry_interval = 180 # 设置重试间隔时间(秒)
while True:
try:
##################屏幕初始化#########################
epd = epd2in13_V4.EPD() #初始化
epd.init()#设定屏幕刷新模式
#epd.Clear(0xFF) #清除屏幕内容
##################屏幕初始化#########################
logging.info("Width = %s, Height = %s", format(epd.width), format(epd.height)) #打印屏幕高度及宽度
logging.info("初始化并清空显示屏")#屏幕开始准备相关展示
info_image = Image.new('1', (epd.height, epd.width), 255) #画布创建准备
draw = ImageDraw.Draw(info_image)
Basic_refresh() #全局刷新
Partial_refresh() #局部刷新
epd.init()
epd.Clear(0xFF)
epd.sleep()
time.sleep(300)
break # 如果脚本执行成功,则退出循环
except (OSError, Exception) as e: # 捕获你提到的异常
logging.error("发生了错误: %s", e)
time.sleep(retry_interval) # 等待一段时间后重试
except KeyboardInterrupt:
logging.info("检测到键盘中断,正在清理并退出")
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
# 脚本正常结束后的清理操作
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
exit()

258
bin/main.py1 Normal file
View File

@@ -0,0 +1,258 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from PIL import Image,ImageDraw,ImageFont #引入图片处理库
import os,sys,re,json,time,datetime #引入系统相关库
from borax.calendars.lunardate import LunarDate #农历日期以及天干地支纪年法的 Python 库
import logging #日志库
import subprocess
import os
from threading import Timer
import requests
import socket
white = 255 #颜色
black = 0
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
################################引入配置文件开始################################################
picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic')
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
sys.path.append(libdir)#将引入文件添加到环境变量
from waveshare_epd import epd2in13_V4 #引入墨水屏驱动文件
logging.debug("Loading Fonts")
font01 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 20) #字体文件
font02 = ImageFont.truetype(os.path.join(picdir, 'GB2312.ttf'), 15) #字体文件
font03 = ImageFont.truetype(os.path.join(picdir, 'Fonttt.ttf'), 48) #字体文件
font04 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 10) #字体文件
font05 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 12) #字体文件
font06 = ImageFont.truetype(os.path.join(picdir, '原神cn.ttf'), 13) #字体文件
################################引入配置文件结束################################################
def Local_strong_brush(): #局部强制刷新显示
i = 0
while i < 5:
epd.displayPartial(epd.getbuffer(info_image.rotate(180)))#局刷开始
i = i + 1
def get_date():#返回当前年月日及星期几
date = datetime.datetime.now()
today=LunarDate.today()
week_day_dict = {0: '星期一',1: '星期二',2: '星期三',3: '星期四',4: '星期五',5: '星期六',6: '星期日',}
day = date.weekday()
return time.strftime('%Y年%m月%d日')+''+week_day_dict[day]+''+today.strftime('农历%M月%D')
def get_time():#返回当前时间,不到秒,大写
return time.strftime('%H:%M')
def Get_address():#获取当前的IP地址
return (subprocess.check_output(u"hostname -I | cut -d\' \' -f1 | head --bytes -1", shell = True ).decode('gbk'))
def Get_ipv4_address(): # 获取当前的IP地址
try:
# 执行命令获取IP地址并处理输出以仅返回IPv4地址
ip_output = subprocess.check_output("hostname -I | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}'", shell=True).decode('utf-8').strip()
# 分割输出以获取单个IP地址列表
ip_list = ip_output.split()
# 过滤掉以172开头的IP地址
filtered_ips = [ip for ip in ip_list if not ip.startswith("172.")]
# 如果有有效的IP地址返回第一个否则返回获取失败
if filtered_ips:
return filtered_ips[0]
else:
return "地址获取失败"
except subprocess.CalledProcessError as e:
#logging.error("获取IPv4地址失败: %s", e)
return "获取失败"
def CPU_temperature():#CPU温度获取
temperatura = os.popen('vcgencmd measure_temp').readline()
temperatura = temperatura.replace('temp=','').strip()
return str(temperatura)
def Memory_footprint():#显示内存占用百分比
return(subprocess.check_output(u"free -m | awk -F '[ :]+' 'NR==2{printf \"%d\", ($3)/$2*100}'", shell = True ).decode('gbk'))
def CPU_usage(): #显示CPU占用百分比
return(str(int(float(os.popen("top -b -n1 | awk '/Cpu\(s\):/ {print $2}'").readline().strip()))))
def power_battery():#获取当前电池电量
return(str(int(subprocess.check_output(u"echo \"get battery\" | nc -q 0 127.0.0.1 8423|awk -F':' '{print int($2)}'", shell = True ).decode('gbk')))+u'%')
# 打印电量信息
print(power_battery())
def Bottom_edge(): #在图片中添加底边内容
draw.rectangle((0, 105, 250, 122), 'black', 'black')
'''电池图标画图'''
draw.line((126,109,154,109),fill=255, width=1) #电池顶边
draw.line((126,110,126,119),fill=255, width=1) #电池左边
draw.line((127,119,154,119),fill=255, width=1) #电池下边
draw.line((154,110,154,118),fill=255, width=1)
draw.line((155,112,157,112),fill=255, width=1)
draw.line((155,116,157,116),fill=255, width=1)
draw.line((157,113,157,115),fill=255, width=1)
global power_str
power_str=power_battery()
draw.text((129,108),power_str,font = font04,fill =255) #显示当前电量百分比
'''电池图标画图'''
draw.ellipse((192, 107, 207, 120), 0, 255)# 时钟图标
draw.line((199,109,199,114),fill=255, width=1)
draw.line((200,114,204,114),fill=255, width=1)
global local_addr #获取当前IP地址
local_addr= Get_ipv4_address() #获取当前IP地址
draw.text((10,107),"IP:"+local_addr,font = font05,fill =255)#显示当前IP地址
def Weather(): #在图片中添加天气内容
Weather_json = open('/root/2.13-Ink-screen-clock/bin/weather.json','r')
Weather_data = Weather_json.read()
Weather_json.close()
Weather_text=json.loads(Weather_data)
global Weather_position
global temperature
global weather
global wind_direction
global weather_update
global weather_date
global humidity
Weather_position = Weather_text['cityname'] #定位位置
temperature=Weather_text['temp']+u'°C' #温度
weather = Weather_text['weather'] #天气情况
wind_direction = Weather_text['WD'] #风向
weather_update = Weather_text['time'] #天气更新时间
weather_date = Weather_text['date'] #日期
humidity = Weather_text['SD'] #湿度
draw.text((150,25),"天气:",font = font06,fill =0)#显示当前天气前缀
draw.text((150,45),"温度:",font = font06,fill =0)#显示当前温度前缀
draw.text((150,65),"湿度:",font = font06,fill =0)#显示当前湿度前缀
draw.text((150,85),"城市:",font = font06,fill =0)#显示当前城市前缀
draw.text((191,25),weather,font = font06,fill =0)
draw.text((191,45),temperature,font = font06,fill =0)
draw.text((191,65),humidity,font = font06,fill =0)
draw.text((191,85),Weather_position,font = font06,fill =0)
draw.text((211,107),weather_update,font = font05,fill =255) #显示天气更新时间
def Basic_refresh(): #全刷函数
logging.info("在启动画布之前,刷新并准备基本内容")#开始画布前刷新准备基础内容
global get_date_var
get_date_var=get_date() #记录开始数据
draw.text((2,2),get_date_var,font = font02,fill =0)#将日期及星期几显示到屏幕
global local_time
local_time=get_time()
draw.text((5,28),local_time,font = font03,fill =0)#显示当前时间
Bottom_edge() #添加底边内容
Weather() #天气内容
epd.display(epd.getbuffer(info_image.rotate(180)))
def Partial_full_brush(): #局部定时全刷函数
Basic_refresh() #全局刷新
logging.debug("局部定时全局刷新")
epd.init()
def Partial_refresh():#局刷函数
logging.info("部分内容更新,此更新建议与分钟同步,以节省墨水屏的使用寿命")#局部内容更新,此更新建议与分钟同步,以节省墨水屏寿命
epd.displayPartBaseImage(epd.getbuffer(info_image.rotate(180)))
epd.init()
while (True):
global local_time
local_time1=get_time()
if (local_time1==local_time) ==False:
draw.rectangle((5, 28, 149, 82), fill = 255) #时间局刷区域
draw.text((5,28),local_time1,font = font03,fill =0)#刷新当前时间
local_time=local_time1
Local_strong_brush() #局部强刷
get_date_var1=get_date() #局刷判断,如果时间与前一次不一致说明内容变化,需要刷新显示
global get_date_var #再次声明这个是全局变量
if(get_date_var1==get_date_var) ==False:
draw.rectangle((2, 2, 250, 16), fill = 255) #设置头部刷新区域
draw.text((2,2),get_date_var1,font = font02,fill =0)#将日期及星期几刷新显示到屏幕
get_date_var=get_date_var1 #将更新的值保存到初始变量,直到下一次变化时执行该刷新操作
logging.debug("头部日期部位发生刷新变化.")
Local_strong_brush() #局部强刷
global local_addr #当前IP地址
local_addr1 = Get_ipv4_address()
if (local_addr1==local_addr) ==False:
draw.rectangle((1, 107, 123, 120), fill = 0) #设置头部刷新区域
draw.text((10,107),"IP:"+local_addr1,font = font05,fill =255)#显示当前IP地址
local_addr=local_addr1
Local_strong_brush() #局部强刷
'''天气局部更新函数'''
Weather_json = open('/root/2.13-Ink-screen-clock/bin/weather.json','r')
Weather_data = Weather_json.read()
Weather_json.close()
Weather_text=json.loads(Weather_data)
global Weather_position
global temperature
global weather
global wind_direction
global weather_update
global weather_date
global humidity
Weather_position1 = Weather_text['cityname'] #定位位置
temperature1=Weather_text['temp']+u'°C' #温度
weather11 = Weather_text['weather'] #天气情况
wind_direction1 = Weather_text['WD'] #风向
weather_update1 = Weather_text['time'] #天气更新时间
weather_date1 = Weather_text['date'] #日期
humidity1 = Weather_text['SD'] #湿度
if (weather11==weather) ==False:
draw.rectangle((191, 25, 249, 38), fill = 255) #天气局刷区域
draw.text((191,25),weather11,font = font06,fill =0)
weather=weather11
logging.info("天气局部刷新")
Local_strong_brush() #局部强刷
if (temperature1==temperature) ==False:
draw.rectangle((191, 45, 249, 57), fill = 255) #局刷区域
draw.text((191,45),temperature1,font = font06,fill =0)
temperature=temperature1
logging.info("温度局部刷新")
Local_strong_brush() #局部强刷
if (humidity1==humidity) ==False:
draw.rectangle((191, 65, 249, 77), fill = 255) #局刷区域
draw.text((191,65),humidity1,font = font06,fill =0)
humidity = humidity1
logging.info("湿度局部刷新")
Local_strong_brush() #局部强刷
if (Weather_position1==Weather_position) ==False:
draw.rectangle((191, 85, 249, 98), fill = 255) #局刷区域
draw.text((191,85),Weather_position1,font = font06,fill =0)
Weather_position = Weather_position1
logging.info("城市局部刷新")
Local_strong_brush() #局部强刷
if (weather_update1==weather_update) ==False:
draw.rectangle((211, 107, 248, 118), fill = 0) #设置更新时间刷新区域
draw.text((211,107),weather_update1,font = font05,fill =255) #显示天气更新时间
weather_update=weather_update1
logging.info("天气更新时间局部刷新")
Local_strong_brush() #局部强刷
'''天气局部更新函数'''
global power_str
power_str1 =power_battery()
if (power_str1==power_str) ==False:
draw.rectangle((128, 110, 153, 117), fill = 0) #设置更新时间刷新区域
draw.text((129,108),power_battery(),font = font04,fill =255) #显示当前电量百分比
power_str=power_str1
Local_strong_brush() #局部强刷
# logging.info("电池电量局部刷新")
retry_interval = 180 # 设置重试间隔时间(秒)
while True:
try:
##################屏幕初始化#########################
epd = epd2in13_V4.EPD() #初始化
epd.init()#设定屏幕刷新模式
#epd.Clear(0xFF) #清除屏幕内容
##################屏幕初始化#########################
logging.info("Width = %s, Height = %s", format(epd.width), format(epd.height)) #打印屏幕高度及宽度
logging.info("初始化并清空显示屏")#屏幕开始准备相关展示
info_image = Image.new('1', (epd.height, epd.width), 255) #画布创建准备
draw = ImageDraw.Draw(info_image)
Basic_refresh() #全局刷新
Partial_refresh() #局部刷新
epd.init()
epd.Clear(0xFF)
epd.sleep()
time.sleep(300)
break # 如果脚本执行成功,则退出循环
except (OSError, Exception) as e: # 捕获你提到的异常
logging.error("发生了错误: %s", e)
time.sleep(retry_interval) # 等待一段时间后重试
except KeyboardInterrupt:
logging.info("检测到键盘中断,正在清理并退出")
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()
# 脚本正常结束后的清理操作
epd.init()
epd.Clear(0xFF) # 清除屏幕内容
epd.sleep() # 使屏幕进入休眠状态
epd2in13_V4.epdconfig.module_exit() # 清理资源
exit()

View File

@@ -4,12 +4,11 @@ After=network.target
[Service]
Type=forking
# 等待网络完全启动
ExecStartPre=/bin/bash -c 'until ping -c1 baidu.com &>/dev/null; do sleep 1; done'
ExecStart=/root/2.13-Ink-screen-clock/bin/start.sh
ExecStop=/usr/bin/screen -S main_screen -X quit
Restart=on-failure
ExecStop=/bin/bash -c "pkill -f /root/2.13-Ink-screen-clock/bin/main.py; pkill -f /root/2.13-Ink-screen-clock/bin/weather.py"
WorkingDirectory=/root/2.13-Ink-screen-clock/bin
Restart=always
User=root
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target

8
bin/requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
borax==4.1.2
certifi==2025.1.31
charset-normalizer==3.4.1
idna==3.10
pillow==11.1.0
requests==2.32.3
spidev==3.6
urllib3==2.3.0

View File

@@ -1,10 +1,8 @@
#!/bin/bash
f_name=main.py
f1_name=weather.py
f2_name=app.py
dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
logdir="${dir%/*}/log"
appdir="${dir%/*}/app"
# 确保日志目录存在
mkdir -p "${logdir}"
pid=`ps -ef |grep $dir/$f_name | grep -v grep |awk '{print $2}'`
@@ -13,5 +11,4 @@ do
kill -9 $id
done
nohup /usr/bin/python3 -u $dir/$f_name > $logdir/info.log 2>&1 &
nohup /usr/bin/python3 -u $dir/$f1_name > $logdir/info-wenter.log 2>&1 &
nohup /usr/bin/python3 -u $appdir/$f2_name > $logdir/info-app.log 2>&1 &
nohup /usr/bin/python3 -u $dir/$f1_name > $logdir/info-weather.log 2>&1 &

BIN
bin/vendor/borax-4.1.2-py3-none-any.whl vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
bin/vendor/idna-3.10-py3-none-any.whl vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bin/vendor/spidev-3.6.tar.gz vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,99 +1,192 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os, sys, re, json, time, datetime
import logging
import subprocess
import os
from threading import Timer
import json
import time
import logging
import requests
import random
import socket
from functools import wraps
from threading import Timer
white = 255 # 颜色
black = 0
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s') # 设置日志级别
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic')
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
sys.path.append(libdir) # 将引入文件添加到环境变量
def check_network_connection():
"""检查网络连接状态"""
try:
# 尝试连接一个可靠的公共DNS服务器
socket.create_connection(("223.5.5.5", 53), timeout=5)
return True
except OSError:
return False
def get_ip():
"""改进的IP获取函数"""
if not check_network_connection():
logging.warning("网络连接不可用")
return None
services = [
{"url": "https://api.ipify.org?format=json", "field": "ip"},
{"url": "https://ipinfo.io/json", "field": "ip"},
{"url": "https://ifconfig.me/all.json", "field": "ip_addr"}
]
for service in services:
try:
resp = requests.get(service["url"], timeout=10)
data = resp.json()
return data.get(service["field"])
except Exception:
continue
logging.error("所有IP服务尝试失败")
return None
def get_ip():
"""从ip.cn获取当前IP地址"""
url = "https://ip.cn/api/index?ip=&type=0"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
try:
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
data = resp.json()
if data.get('code') == 'Success':
return data.get('ip', '')
logging.error("获取IP失败: %s", data.get('msg', '未知错误'))
except Exception as e:
logging.error("获取IP异常: %s", str(e))
return None
def get_current_city():
"""通过IP地址获取当前定位城市并去除''后缀"""
ip = get_ip()
if not ip:
return None
url = f"http://ip-api.com/json/{ip}?fields=city&lang=zh-CN"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
while True:
try:
resp = requests.get(url, headers=headers, timeout=10)
data = resp.json()
if data.get('status') == 'success':
return data.get('city', '').replace('', '')
logging.error("定位失败: %s", data.get('message', '未知错误'))
except Exception as e:
logging.error("定位异常: %s", str(e))
time.sleep(180)
def schedule_getWeath():
"""改进的定时任务调度"""
retry_count = 0
max_retries = 3
while retry_count < max_retries:
try:
getWeath()
break
except Exception as e:
retry_count += 1
wait_time = min(300, retry_count * 60) # 指数退避
logging.error(f"天气更新失败({retry_count}/{max_retries}): {str(e)}")
time.sleep(wait_time)
# 无论成功与否,都安排下一次执行
Timer(1800, schedule_getWeath).start() # 30分钟间隔
def getWeath(default_city='101060101'):
"""改进的天气获取函数"""
# 1. 检查网络连接
if not check_network_connection():
logging.error("网络不可用,跳过天气更新")
return
# 2. 获取城市信息
city_name = None
try:
city_name = get_current_city()
except Exception as e:
logging.error(f"获取城市失败: {str(e)}")
# 3. 确定区域ID
area_id = default_city
if city_name:
try:
found_id = get_area_id(city_name)
if found_id:
area_id = found_id
logging.info(f"使用城市区域ID: {area_id} ({city_name})")
except Exception as e:
logging.error(f"获取区域ID失败: {str(e)}")
# 4. 获取天气数据
weather_apis = [
f'https://d1.weather.com.cn/sk_2d/{area_id}.html',
f'https://www.weather.com.cn/weather1d/{area_id}.shtml'
]
for api_url in weather_apis:
try:
resp = requests.get(
api_url,
headers={'User-Agent': 'Mozilla/5.0'},
timeout=15
)
resp.raise_for_status()
# 处理不同API的响应格式
if 'sk_2d' in api_url:
weather_data = resp.content[11:].decode('utf-8')
else:
weather_data = parse_html_weather(resp.text)
# 验证数据有效性
if validate_weather_data(weather_data):
with open('/root/2.13-Ink-screen-clock/bin/weather.json', 'w') as f:
json.dump(weather_data, f)
logging.info("天气数据更新成功")
return
except Exception as e:
logging.warning(f"天气API {api_url} 失败: {str(e)}")
continue
logging.error("所有天气API尝试失败")
def get_area_id(city_name):
"""从city.js中检索AREAID无限重试直到成功"""
url = "https://j.i8tq.com/weather2020/search/city.js"
while True:
try:
response = requests.get(url)
response.raise_for_status() # 检查请求是否成功
# 直接打印原始数据
logging.debug("Raw data from city.js: %s", response.text)
# 预处理返回的数据去除非JSON部分
city_data_text = response.text.strip()
city_data_text = city_data_text.split('var city_data = ')[-1].rstrip(';')
if city_data_text:
city_data = json.loads(city_data_text)
# 遍历数据结构,查找城市名称
for province, cities in city_data.items():
for city, districts in cities.items():
for district, info in districts.items():
if info['NAMECN'] == city_name:
return info['AREAID']
logging.error("城市名称 '%s' 在城市数据中未找到,将使用默认城市信息", city_name)
else:
logging.error("从city.js接收到的数据为空")
except (requests.RequestException, json.JSONDecodeError) as e:
logging.error("检索或解析城市数据时发生错误: %s", e)
time.sleep(5) # 重试前等待5秒
def get_current_city():
"""获取当前城市名称,无限重试直到成功"""
url = "http://ip-api.com/json/?lang=zh-CN"
while True:
try:
response = requests.get(url)
response.raise_for_status() # 检查请求是否成功
data = response.json()
if data['status'] == 'success':
return data['city']
else:
logging.error("获取当前城市失败: %s", data['message'])
except (requests.RequestException, json.JSONDecodeError) as e:
logging.error("检索或解析当前城市时出现错误: %s", e)
time.sleep(5) # 重试前等待5秒
# 注意:无限重试可能会在特定情况下导致程序无法终止,请确保在实际使用中考虑适当的退出条件或限制重试次数。
def getWeath(city='101060101'):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Referer': 'http://www.weather.com.cn/'
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
current_city = get_current_city()
if current_city:
area_id = get_area_id(current_city)
if area_id:
city = area_id
while True:
try:
resp = requests.get(url, headers=headers, timeout=10)
resp.encoding = 'utf-8'
city_data = json.loads(resp.text.split('=', 1)[1].rstrip(';'))
for province in city_data.values():
for city in province.values():
for district, info in city.items():
if info['NAMECN'] == city_name:
return info['AREAID']
logging.error("未找到城市: %s", city_name)
except Exception as e:
logging.error("获取城市ID失败: %s", str(e))
time.sleep(180)
if __name__ == "__main__":
try:
response = requests.get('http://d1.weather.com.cn/sk_2d/'+city+'.html',headers=headers)
response.raise_for_status() # 检查请求是否成功
response.encoding = 'utf-8'
Weath = response.text[11:]
fileHandle = open('/root/2.13-Ink-screen-clock/bin/weather.json', 'w')
fileHandle.write(str(Weath))
fileHandle.close()
Timer(180, getWeath).start() # 定时器函数,间隔三分钟下载文件至本地
print("天气文件更新")
except requests.RequestException as e:
logging.error("获取天气数据时出现网络错误: %s", e)
except Exception as e:
logging.error("发生了意外错误: %s", e)
try:
getWeath() # 天气获取函数开始运行
except IOError as e:
logging.info(e)
except KeyboardInterrupt:
logging.info("检测到键盘中断,正在退出")
except Exception as e:
logging.error("发生了意外错误: %s", e)
exit()
# 脚本正常结束后的清理操作
exit()
schedule_getWeath()
while True:
time.sleep(1)
except KeyboardInterrupt:
logging.info("程序已终止")

BIN
pic/Fonttt.ttf Normal file

Binary file not shown.

BIN
pic/GB2312.ttf Normal file

Binary file not shown.

BIN
pic/SmileySans-Oblique.otf Normal file

Binary file not shown.

Binary file not shown.

BIN
pic/SmileySans-Oblique.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
pic/原神cn.ttf Normal file

Binary file not shown.

BIN
pic/星穹铁道cn.ttf Normal file

Binary file not shown.