6 Commits

Author SHA1 Message Date
7bc12a67ea v1.3.8, 去掉温度符号 2026-02-08 09:29:25 +08:00
978919de2a lcd: spi init with phase=1 2026-02-03 20:04:41 +08:00
212bd6b2e1 v1.3.6, update weather url 2026-02-03 16:10:52 +08:00
cda7c855fe add console config 2026-02-02 12:46:01 +08:00
ebf61d13d4 加入定时熄屏功能 2026-02-02 12:20:53 +08:00
a43690930b 加入百分比符号 2026-02-02 10:53:13 +08:00
4 changed files with 68 additions and 31 deletions

View File

@@ -188,6 +188,37 @@ async def eval_cmd(request):
finally: finally:
await json_response(request, json.dumps(ack)) await json_response(request, json.dumps(ack))
# 定时熄屏处理功能熄屏则返回True
def standby_control():
# 获取当前时间 (格式: HH:MM)
now = time.localtime()
current_time = "{:02d}:{:02d}".format(now[3], now[4])
# 从配置中获取熄屏和唤醒时间
standby_time = config.get("standby_time")
wakeup_time = config.get("wakeup_time")
_set = config.get('brightness', 10)
# 如果设置了熄屏和唤醒时间
if standby_time and wakeup_time:
# 如果当前时间在熄屏时间之后,唤醒时间之前,则关闭屏幕
if standby_time <= wakeup_time:
if current_time >= standby_time and current_time < wakeup_time:
_set = 0
else:
if current_time >= standby_time or current_time < wakeup_time:
_set = 0
else:
_set = None
if _set is not None:
if display.brightness():
if _set == 0:
display.brightness(0)
else:
if _set > 0:
display.brightness(config.get("brightness", 10)) # 恢复亮度
# ntp时钟同步 # ntp时钟同步
@@ -215,14 +246,15 @@ async def fetch_weather_data(city=None):
if not city: if not city:
city = config.get("cityid") or "北京" city = config.get("cityid") or "北京"
print(f"正在获取{city}天气数据...") # 读取API URL可附加其他参数
# 从配置获取API基础URL默认使用官方API # http://esp.foresh.com/api/ws2/?appid=xxx&appsecert=yyy
url = config.get("weather_api_url", "http://esp.tangofu.com/api/ws2/") url = config.get("weather_api_url", "http://esp.foresh.com/api/ws2/")
params = {"uuid": uuid(), "city": city} full_url = f"{url}{'&' if '?' in url else '?'}uuid={uuid()}&city={city}"
print(f"GET> {full_url}")
# 发送GET请求 # 发送GET请求
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as response: async with session.get(full_url) as response:
# 检查响应状态 # 检查响应状态
if response.status == 200: if response.status == 200:
# 解析JSON数据 # 解析JSON数据
@@ -240,7 +272,9 @@ async def fetch_weather_data(city=None):
advice = wdata.get("advice", []) advice = wdata.get("advice", [])
lunar = wdata.get("lunar", None) lunar = wdata.get("lunar", None)
ip = wifi_manager.get_ip() if city == "N/A":
advice.append("城市配置错误")
advice.append(wifi_manager.get_ip())
display.update_ui( display.update_ui(
city, city,
@@ -248,7 +282,6 @@ async def fetch_weather_data(city=None):
advice, advice,
aqi, aqi,
lunar, lunar,
ip,
envdat={"t": t, "rh": rh, "co2": co2, "pm": pm, "ap": ap}, envdat={"t": t, "rh": rh, "co2": co2, "pm": pm, "ap": ap},
) )
else: else:
@@ -269,7 +302,6 @@ async def sysinfo_update_task():
last_weather = last_ntptime = start_ticks last_weather = last_ntptime = start_ticks
while True: while True:
task_id = None # task执行失败后要快速重试
try: try:
current_ticks = time.ticks_ms() current_ticks = time.ticks_ms()
# 计算时间差,处理溢出 # 计算时间差,处理溢出
@@ -279,23 +311,17 @@ async def sysinfo_update_task():
if weather_diff >= weather_ts * 1000: if weather_diff >= weather_ts * 1000:
# 更新天气数据 # 更新天气数据
gc.collect() gc.collect()
task_id = "weather"
await fetch_weather_data() await fetch_weather_data()
last_weather = current_ticks last_weather = current_ticks
weather_ts = int(config.get("weather_ts", 600)) # 10min weather_ts = int(config.get("weather_ts", 600)) # 10min
elif ntp_diff >= ntptime_ts * 1000: elif ntp_diff >= ntptime_ts * 1000:
# 更新NTP时间 # 更新NTP时间
gc.collect() gc.collect()
task_id = "ntp"
sync_ntp_time() sync_ntp_time()
last_ntptime = current_ticks last_ntptime = current_ticks
ntptime_ts = int(config.get("ntptime_ts", 3600)) + 13 # 1hour ntptime_ts = int(config.get("ntptime_ts", 3600)) + 13 # 1hour
except Exception as e: except Exception as e:
print(f"定时任务更新错误: {e}") print(f"定时任务更新错误: {e}")
if task_id == "ntp":
ntptime_ts = 10
elif task_id == "weather":
weather_ts = 60
# 等待x秒再检查1~30 # 等待x秒再检查1~30
_x = min(30, 1 + min(weather_ts, ntptime_ts) // 10) _x = min(30, 1 + min(weather_ts, ntptime_ts) // 10)
@@ -327,7 +353,12 @@ async def ui_task():
# 每隔100帧更新一次UI显示 # 每隔100帧更新一次UI显示
F += 1 F += 1
if F % 100 == 0: if F % 100 == 0:
display.update_ui() standby_control() # 控制开关机
# 只在亮屏时更新显示
if display.brightness():
display.update_ui()
else:
gc.collect(); print(f'LCD.idle.mem: {gc.mem_free()}')
# 每轮清理一次内存 # 每轮清理一次内存
gc.collect() gc.collect()
@@ -361,6 +392,9 @@ def cb_progress(data):
def start(): def start():
# 禁用repl
if config.get('console') == "disable":
import os; os.dupterm(None, 1)
# 初始化液晶屏 # 初始化液晶屏
display.init_display(config.get("bl_mode") == "pwm", 7000) display.init_display(config.get("bl_mode") == "pwm", 7000)
display.brightness(int(config.get("brightness", 10))) display.brightness(int(config.get("brightness", 10)))

View File

@@ -49,7 +49,7 @@ class Display:
# 初始化显示屏 # 初始化显示屏
self.tft = ST7789( self.tft = ST7789(
SPI(1, 40_000_000, polarity=1), SPI(1, 40_000_000, polarity=1, phase=1),
240, 240,
240, 240,
dc=Pin(0, Pin.OUT), dc=Pin(0, Pin.OUT),
@@ -68,7 +68,7 @@ class Display:
self.tft.init() self.tft.init()
self.tft.fill(0) self.tft.fill(0)
self.show_jpg(self.bootimg, 80, 80) self.show_jpg(self.bootimg, 80, 80)
self.message("WS2 v1.3.5 (20260202)") self.message("WS2 v1.3.8 (20260208)")
_print_mem() _print_mem()
return True return True
@@ -146,7 +146,7 @@ class Display:
self.window("配置设备网络连接", tips, "portal ip: 192.168.4.1") self.window("配置设备网络连接", tips, "portal ip: 192.168.4.1")
# 更新ui数据 # 更新ui数据
def update_ui(self, city=None, weather=None, advice=None, aqi=None, lunar=None, ip=None, envdat=None): def update_ui(self, city=None, weather=None, advice=None, aqi=None, lunar=None, envdat=None):
self.ticks += 1 self.ticks += 1
if self.ui_type == 'default': if self.ui_type == 'default':
# 中文的城市名称 # 中文的城市名称
@@ -161,7 +161,6 @@ class Display:
self.ui_data['weather'] = weather self.ui_data['weather'] = weather
# 建议信息可能有很多条,需要轮换展示 # 建议信息可能有很多条,需要轮换展示
if advice is not None and advice != self.ui_data.get('advice'): if advice is not None and advice != self.ui_data.get('advice'):
if ip is not None: advice.append(ip)
self.ui_data['advice'] = advice self.ui_data['advice'] = advice
# AQI等级分成0-5级分别对应优、良、中、差、污、恶 # AQI等级分成0-5级分别对应优、良、中、差、污、恶
if aqi is not None and aqi != self.ui_data.get('aqi'): if aqi is not None and aqi != self.ui_data.get('aqi'):
@@ -182,11 +181,11 @@ class Display:
if t is not None and t != self.ui_data.get('t'): if t is not None and t != self.ui_data.get('t'):
self.ui_data['t'] = t self.ui_data['t'] = t
self.tft.fill_rect(35,179,40,16,0) self.tft.fill_rect(35,179,40,16,0)
self.tft.draw(self.vector_font, f"{str(t)}'C", 35,187,0xFFFF,0.5) self.tft.draw(self.vector_font, f"{str(t)}", 35,187,0xFFFF,0.5)
if rh is not None and rh != self.ui_data.get('rh'): if rh is not None and rh != self.ui_data.get('rh'):
self.ui_data['rh'] = rh self.ui_data['rh'] = rh
self.tft.fill_rect(110,179,40,16,0) self.tft.fill_rect(110,179,40,16,0)
self.tft.draw(self.vector_font, f' {str(rh)}%', 110,187,0xFFFF,0.5) self.tft.draw(self.vector_font, f' {str(rh)}', 110,187,0xFFFF,0.5)
if pm is not None and pm != self.ui_data.get('pm'): if pm is not None and pm != self.ui_data.get('pm'):
self.ui_data['pm'] = pm self.ui_data['pm'] = pm
self.tft.fill_rect(35,213,40,16,0) self.tft.fill_rect(35,213,40,16,0)

View File

@@ -82,12 +82,10 @@ target="_blank"
download download
>查看城市ID列表</a >查看城市ID列表</a
> >
</small></div><div class="form-group"><label class="form-label">自动熄屏时间</label </small></div><div class="form-group"><label class="form-label">自动熄屏时间</label><div style="display: flex;">
><input <input type="time" id="standby-time-input" class="form-control" placeholder="熄屏时间" style="margin-right: 10px; flex: 1;"/>
type="time" <input type="time" id="wakeup-time-input" class="form-control" placeholder="唤醒时间" style="flex: 1;"/></div>
id="standby-time-input" <small class="text-muted">分别设置熄屏和唤醒时间,留空则表示不自动熄屏</small></div>
class="form-control"
/><small class="text-muted">留空表示不自动熄屏</small></div>
<div class="form-group"><label class="form-label">自定义配置</label> <div class="form-group"><label class="form-label">自定义配置</label>
<div id="custom-config-container" class="custom-config-list"> <div id="custom-config-container" class="custom-config-list">
<div class="custom-config-row" style="display: flex; margin-bottom: 10px;"> <div class="custom-config-row" style="display: flex; margin-bottom: 10px;">
@@ -131,7 +129,7 @@ WS2是一款基于ESP8266的桌面气象站能够实时显示天气信息、
href="https://iot.foresh.com/git/kicer/ws2/releases" href="https://iot.foresh.com/git/kicer/ws2/releases"
target="_blank" target="_blank"
> >
ws2-firmware-v1.3.5-4M.bin </a ws2-firmware-xxx-4M.bin </a
><span class="badge badge-success">开源</span></div><div class="list-item"><strong>协议:</strong> HTTP REST API ><span class="badge badge-success">开源</span></div><div class="list-item"><strong>协议:</strong> HTTP REST API
</div><div class="list-item"><strong>更新频率:</strong> 每小时 </div><div class="list-item"><strong>更新频率:</strong> 每小时
</div></div></div></div><h3 class="mt-3">开放源码</h3><a </div></div></div></div><h3 class="mt-3">开放源码</h3><a
@@ -263,6 +261,7 @@ Math.round((memoryValue / maxMemory) * 100),
);// 使用micro.js的图表功能创建仪表盘 );// 使用micro.js的图表功能创建仪表盘
mw.chart.createGauge(mw.$("#memory-gauge"), percentage, 100, { mw.chart.createGauge(mw.$("#memory-gauge"), percentage, 100, {
label: "内存使用率", label: "内存使用率",
percent: true,
color: color:
percentage > 80 percentage > 80
? "#e74c3c" ? "#e74c3c"
@@ -313,7 +312,10 @@ mw.val(mw.$("#city-input"), data.city);
} }
if (data.standby_time) { if (data.standby_time) {
mw.val(mw.$("#standby-time-input"), data.standby_time); mw.val(mw.$("#standby-time-input"), data.standby_time);
}// 更新配置表 }
if (data.wakeup_time) {
mw.val(mw.$("#wakeup-time-input"), data.wakeup_time);
}
updateConfigTable(data); updateConfigTable(data);
} catch (error) { } catch (error) {
showMessage("获取配置失败: " + error.message, "error"); showMessage("获取配置失败: " + error.message, "error");
@@ -353,6 +355,7 @@ try {
const userCfgKey = mw.val(mw.$("#custom-config-key")); const userCfgKey = mw.val(mw.$("#custom-config-key"));
const userCfgVal = mw.val(mw.$("#custom-config-value")); const userCfgVal = mw.val(mw.$("#custom-config-value"));
const city = mw.val(mw.$("#city-input")); const city = mw.val(mw.$("#city-input"));
const wakeupTime = mw.val(mw.$("#wakeup-time-input"));
const standbyTime = mw.val(mw.$("#standby-time-input"));if (!city) { const standbyTime = mw.val(mw.$("#standby-time-input"));if (!city) {
showMessage("城市名称不能为空", "error"); showMessage("城市名称不能为空", "error");
return; return;
@@ -362,8 +365,9 @@ cityid: encodeURIComponent(city),
}; };
if (userCfgKey !== "") { if (userCfgKey !== "") {
configData[userCfgKey] = userCfgVal; configData[userCfgKey] = userCfgVal;
}if (standbyTime !== "") { }if (standbyTime !== "" && wakeupTime !== "") {
configData.standby_time = standbyTime; configData.standby_time = standbyTime;
configData.wakeup_time = wakeupTime;
}const response = await mw.ajax.post( }const response = await mw.ajax.post(
"/config/set", "/config/set",
configData, configData,

View File

@@ -59,7 +59,7 @@ const MicroWeb={$:t=>document.querySelector(t),$$:t=>document.querySelectorAll(t
top: ${o/2.5}px; top: ${o/2.5}px;
font-size: ${o/7}px; font-size: ${o/7}px;
font-weight: bold;"> font-weight: bold;">
${e} ${e}${i.percent?"%":""}
<div style="font-size: ${o/10}px; color: #666;"> <div style="font-size: ${o/10}px; color: #666;">
${i.label||""} ${i.label||""}
</div> </div>