空调最高峰,空调数据库挂载再优化
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-11-19 16:24:24 +08:00
parent 1233677eea
commit f6e7dfcd93
7 changed files with 111 additions and 34 deletions

View File

@ -117,22 +117,18 @@ async def _(target: DepLongTaskTarget, temp: Optional[Union[int, float]] = 1):
await send_ac_image(evt, ac)
return
await ac.update_ac(temperature_delta=temp)
if ac.temperature > 40:
# 根据温度随机出是否爆炸40度开始呈指数增长
possibility = -math.e ** ((40-ac.temperature) / 50) + 1
if random.random() < possibility:
# 打开爆炸图片
with open(ASSETS_PATH / "img" / "other" / "boom.jpg", "rb") as f:
output = BytesIO()
# 爆炸抖动
frames = wiggle_transform(np.array(Image.open(f)), intensity=5)
pil_frames = [Image.fromarray(frame) for frame in frames]
pil_frames[0].save(output, format="GIF", save_all=True, append_images=pil_frames[1:], loop=0, duration=35, disposal=2)
output.seek(0)
await evt.send(await UniMessage().image(raw=output).export())
await ac.broke_ac(CrashType.BURNT)
await evt.send("太热啦,空调炸了!")
return
if ac.burnt:
# 打开爆炸图片
with open(ASSETS_PATH / "img" / "other" / "boom.jpg", "rb") as f:
output = BytesIO()
# 爆炸抖动
frames = wiggle_transform(np.array(Image.open(f)), intensity=5)
pil_frames = [Image.fromarray(frame) for frame in frames]
pil_frames[0].save(output, format="GIF", save_all=True, append_images=pil_frames[1:], loop=0, duration=35, disposal=2)
output.seek(0)
await evt.send(await UniMessage().image(raw=output).export())
await evt.send("太热啦,空调炸了!")
return
await send_ac_image(evt, ac)
evt = on_alconna(Alconna(
@ -152,11 +148,6 @@ async def _(target: DepLongTaskTarget, temp: Optional[Union[int, float]] = 1):
await send_ac_image(evt, ac)
return
await ac.update_ac(temperature_delta=-temp)
if ac.temperature < 0:
# 根据温度随机出是否冻结0度开始呈指数增长
possibility = -math.e ** (ac.temperature / 50) + 1
if random.random() < possibility:
await ac.broke_ac(CrashType.FROZEN)
await send_ac_image(evt, ac)
evt = on_alconna(Alconna(
@ -202,3 +193,36 @@ async def _(target: DepLongTaskTarget):
params=params
)
await evt.send(await UniMessage().image(raw=image).export())
evt = on_alconna(Alconna(
"空调最高峰",
), use_cmd_start=True, use_cmd_sep=False, skip_for_unmatch=True)
@evt.handle()
async def _(target: DepLongTaskTarget):
result = await db_manager.query_by_sql_file(
ROOT_PATH / "sql" / "query_peak.sql"
)
if len(result) == 0:
await evt.send("没有空调记录!")
return
max_temp = result[0].get("max")
min_temp = result[0].get("min")
his_max = result[0].get("his_max")
his_min = result[0].get("his_min")
# 再从内存里的空调池中获取最高温度和最低温度
for ac in AirConditioner.InstancesPool.values():
if ac.on and not ac.burnt and not ac.frozen:
if max_temp is None or min_temp is None:
max_temp = ac.temperature
min_temp = ac.temperature
max_temp = max(max_temp, ac.temperature)
min_temp = min(min_temp, ac.temperature)
if max_temp is None or min_temp is None:
await evt.send(f"目前全部空调都被炸掉了!")
else:
await evt.send(f"全球在线空调最高温度为 {'%.1f' % max_temp}°C最低温度为 {'%.1f' % min_temp}°C")
if his_max is None or his_min is None:
pass
else:
await evt.send(f"历史最高温度为 {'%.1f' % his_max}°C最低温度为 {'%.1f' % his_min}°C\n(要进入历史记录,温度需至少保持 5 分钟)")

View File

@ -1,7 +1,9 @@
import asyncio
from enum import Enum
from io import BytesIO
import math
from pathlib import Path
import random
import signal
import time
@ -28,13 +30,8 @@ class CrashType(Enum):
driver = nonebot.get_driver()
ac_manager: 'AirConditionerManager' = None
@driver.on_startup
async def register_startup_hook():
global ac_manager
# 启动自动保存任务
ac_manager = AirConditionerManager(save_interval=300) # 5分钟
await ac_manager.start_auto_save()
@driver.on_shutdown
@ -89,10 +86,10 @@ class AirConditionerManager:
try:
await db_manager.execute_by_sql_file(
ROOT_PATH / "sql" / "update_ac.sql",
(ac_instance.on, ac_instance.temperature,
ac_instance.burnt, ac_instance.frozen, ac_id)
[(ac_instance.on, ac_instance.temperature,
ac_instance.burnt, ac_instance.frozen, ac_id),(ac_id,)]
)
if(save_time - ac_instance.instance_get_time >= 3600):
if(save_time - ac_instance.instance_get_time >= 300): # 5 分钟
to_remove.append(ac_id)
except Exception as e:
logger.error(f"保存空调 {ac_id} 失败: {e}")
@ -105,6 +102,8 @@ class AirConditionerManager:
logger.info(f"清理长期不活跃的空调实例完成,目前池内共有 {len(AirConditioner.InstancesPool)} 个实例")
ac_manager = AirConditionerManager(save_interval=300) # 5分钟
class AirConditioner:
InstancesPool: dict[str, 'AirConditioner'] = {}
@ -119,7 +118,7 @@ class AirConditioner:
@classmethod
async def get_ac(cls, id: str) -> 'AirConditioner':
if(id in cls.InstancesPool):
cls.refresh_ac(id)
await cls.refresh_ac(id)
return cls.InstancesPool[id]
# 如果没有,那么从数据库重新实例化一个 AC 出来
result = await db_manager.query_by_sql_file(ROOT_PATH / "sql" / "query_ac.sql", (id,))
@ -140,16 +139,35 @@ class AirConditioner:
ac = AirConditioner(id)
await db_manager.execute_by_sql_file(
ROOT_PATH / "sql" / "insert_ac.sql",
(id, ac.on, ac.temperature, ac.burnt, ac.frozen)
[(id, ac.on, ac.temperature, ac.burnt, ac.frozen),(id,)]
)
await cls.storage_ac(id, ac)
return ac
async def change_ac_temp(self, temperature_delta: float) -> None:
'''
改变空调的温度
:param temperature_delta: float 温度变化量
'''
changed_temp = self.temperature + temperature_delta
random_poss = random.random()
if temperature_delta < 0 and changed_temp < 0:
# 根据温度随机出是否冻结0度开始呈指数增长
possibility = -math.e ** (changed_temp / 50) + 1
if random_poss < possibility:
await self.broke_ac(CrashType.FROZEN)
elif temperature_delta > 0 and changed_temp > 40:
# 根据温度随机出是否烧坏40度开始呈指数增长
possibility = -math.e ** ((40-changed_temp) / 50) + 1
if random_poss < possibility:
await self.broke_ac(CrashType.BURNT)
self.temperature = changed_temp
async def update_ac(self, state: bool = None, temperature_delta: float = None, burnt: bool = None, frozen: bool = None) -> 'AirConditioner':
if state is not None:
self.on = state
if temperature_delta is not None:
self.temperature += temperature_delta
await self.change_ac_temp(temperature_delta)
if burnt is not None:
self.burnt = burnt
if frozen is not None:

View File

@ -7,6 +7,17 @@ CREATE TABLE IF NOT EXISTS air_conditioner (
frozen BOOLEAN NOT NULL
);
CREATE TABLE IF NOT EXISTS air_conditioner_log (
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
log_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
id VARCHAR(128),
"on" BOOLEAN NOT NULL,
temperature REAL NOT NULL,
burnt BOOLEAN NOT NULL,
frozen BOOLEAN NOT NULL,
FOREIGN KEY (id) REFERENCES air_conditioner(id)
);
CREATE TABLE IF NOT EXISTS air_conditioner_crash_log (
id VARCHAR(128) NOT NULL,
crash_type INT NOT NULL,

View File

@ -1,3 +1,8 @@
-- 插入一台新空调
INSERT INTO air_conditioner (id, "on", temperature, burnt, frozen)
VALUES (?, ?, ?, ?, ?);
VALUES (?, ?, ?, ?, ?);
-- 使用返回的数据插入日志
INSERT INTO air_conditioner_log (id, "on", temperature, burnt, frozen)
SELECT id, "on", temperature, burnt, frozen
FROM air_conditioner
WHERE id = ?;

View File

@ -1,4 +1,4 @@
-- 查询空调状态,如果没有就插入一条新的记录
-- 查询空调状态
SELECT *
FROM air_conditioner
WHERE id = ?;

View File

@ -0,0 +1,13 @@
-- 查询目前所有空调中的最高温度与最低温度与历史最高低温
SELECT
(SELECT MAX(temperature) FROM air_conditioner
WHERE "on" = TRUE AND NOT frozen AND NOT burnt) AS max,
(SELECT MIN(temperature) FROM air_conditioner
WHERE "on" = TRUE AND NOT frozen AND NOT burnt) AS min,
(SELECT MAX(temperature) FROM air_conditioner_log
WHERE "on" = TRUE AND NOT frozen AND NOT burnt) AS his_max,
(SELECT MIN(temperature) FROM air_conditioner_log
WHERE "on" = TRUE AND NOT frozen AND NOT burnt) AS his_min;

View File

@ -1,4 +1,10 @@
-- 更新空调状态
UPDATE air_conditioner
SET "on" = ?, temperature = ?, burnt = ?, frozen = ?
WHERE id = ?;
-- 插入日志记录(从更新后的数据获取)
INSERT INTO air_conditioner_log (id, "on", temperature, burnt, frozen)
SELECT id, "on", temperature, burnt, frozen
FROM air_conditioner
WHERE id = ?;