空调最高峰,空调数据库挂载再优化
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) await send_ac_image(evt, ac)
return return
await ac.update_ac(temperature_delta=temp) await ac.update_ac(temperature_delta=temp)
if ac.temperature > 40: if ac.burnt:
# 根据温度随机出是否爆炸40度开始呈指数增长 # 打开爆炸图片
possibility = -math.e ** ((40-ac.temperature) / 50) + 1 with open(ASSETS_PATH / "img" / "other" / "boom.jpg", "rb") as f:
if random.random() < possibility: output = BytesIO()
# 打开爆炸图片 # 爆炸抖动
with open(ASSETS_PATH / "img" / "other" / "boom.jpg", "rb") as f: frames = wiggle_transform(np.array(Image.open(f)), intensity=5)
output = BytesIO() 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)
frames = wiggle_transform(np.array(Image.open(f)), intensity=5) output.seek(0)
pil_frames = [Image.fromarray(frame) for frame in frames] await evt.send(await UniMessage().image(raw=output).export())
pil_frames[0].save(output, format="GIF", save_all=True, append_images=pil_frames[1:], loop=0, duration=35, disposal=2) await evt.send("太热啦,空调炸了!")
output.seek(0) return
await evt.send(await UniMessage().image(raw=output).export())
await ac.broke_ac(CrashType.BURNT)
await evt.send("太热啦,空调炸了!")
return
await send_ac_image(evt, ac) await send_ac_image(evt, ac)
evt = on_alconna(Alconna( evt = on_alconna(Alconna(
@ -152,11 +148,6 @@ async def _(target: DepLongTaskTarget, temp: Optional[Union[int, float]] = 1):
await send_ac_image(evt, ac) await send_ac_image(evt, ac)
return return
await ac.update_ac(temperature_delta=-temp) 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) await send_ac_image(evt, ac)
evt = on_alconna(Alconna( evt = on_alconna(Alconna(
@ -202,3 +193,36 @@ async def _(target: DepLongTaskTarget):
params=params params=params
) )
await evt.send(await UniMessage().image(raw=image).export()) 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 import asyncio
from enum import Enum from enum import Enum
from io import BytesIO from io import BytesIO
import math
from pathlib import Path from pathlib import Path
import random
import signal import signal
import time import time
@ -28,13 +30,8 @@ class CrashType(Enum):
driver = nonebot.get_driver() driver = nonebot.get_driver()
ac_manager: 'AirConditionerManager' = None
@driver.on_startup @driver.on_startup
async def register_startup_hook(): async def register_startup_hook():
global ac_manager
# 启动自动保存任务
ac_manager = AirConditionerManager(save_interval=300) # 5分钟
await ac_manager.start_auto_save() await ac_manager.start_auto_save()
@driver.on_shutdown @driver.on_shutdown
@ -89,10 +86,10 @@ class AirConditionerManager:
try: try:
await db_manager.execute_by_sql_file( await db_manager.execute_by_sql_file(
ROOT_PATH / "sql" / "update_ac.sql", ROOT_PATH / "sql" / "update_ac.sql",
(ac_instance.on, ac_instance.temperature, [(ac_instance.on, ac_instance.temperature,
ac_instance.burnt, ac_instance.frozen, ac_id) 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) to_remove.append(ac_id)
except Exception as e: except Exception as e:
logger.error(f"保存空调 {ac_id} 失败: {e}") logger.error(f"保存空调 {ac_id} 失败: {e}")
@ -105,6 +102,8 @@ class AirConditionerManager:
logger.info(f"清理长期不活跃的空调实例完成,目前池内共有 {len(AirConditioner.InstancesPool)} 个实例") logger.info(f"清理长期不活跃的空调实例完成,目前池内共有 {len(AirConditioner.InstancesPool)} 个实例")
ac_manager = AirConditionerManager(save_interval=300) # 5分钟
class AirConditioner: class AirConditioner:
InstancesPool: dict[str, 'AirConditioner'] = {} InstancesPool: dict[str, 'AirConditioner'] = {}
@ -119,7 +118,7 @@ class AirConditioner:
@classmethod @classmethod
async def get_ac(cls, id: str) -> 'AirConditioner': async def get_ac(cls, id: str) -> 'AirConditioner':
if(id in cls.InstancesPool): if(id in cls.InstancesPool):
cls.refresh_ac(id) await cls.refresh_ac(id)
return cls.InstancesPool[id] return cls.InstancesPool[id]
# 如果没有,那么从数据库重新实例化一个 AC 出来 # 如果没有,那么从数据库重新实例化一个 AC 出来
result = await db_manager.query_by_sql_file(ROOT_PATH / "sql" / "query_ac.sql", (id,)) result = await db_manager.query_by_sql_file(ROOT_PATH / "sql" / "query_ac.sql", (id,))
@ -140,16 +139,35 @@ class AirConditioner:
ac = AirConditioner(id) ac = AirConditioner(id)
await db_manager.execute_by_sql_file( await db_manager.execute_by_sql_file(
ROOT_PATH / "sql" / "insert_ac.sql", 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) await cls.storage_ac(id, ac)
return 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': async def update_ac(self, state: bool = None, temperature_delta: float = None, burnt: bool = None, frozen: bool = None) -> 'AirConditioner':
if state is not None: if state is not None:
self.on = state self.on = state
if temperature_delta is not None: if temperature_delta is not None:
self.temperature += temperature_delta await self.change_ac_temp(temperature_delta)
if burnt is not None: if burnt is not None:
self.burnt = burnt self.burnt = burnt
if frozen is not None: if frozen is not None:

View File

@ -7,6 +7,17 @@ CREATE TABLE IF NOT EXISTS air_conditioner (
frozen BOOLEAN NOT NULL 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 ( CREATE TABLE IF NOT EXISTS air_conditioner_crash_log (
id VARCHAR(128) NOT NULL, id VARCHAR(128) NOT NULL,
crash_type INT NOT NULL, crash_type INT NOT NULL,

View File

@ -1,3 +1,8 @@
-- 插入一台新空调 -- 插入一台新空调
INSERT INTO air_conditioner (id, "on", temperature, burnt, frozen) 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 * SELECT *
FROM air_conditioner FROM air_conditioner
WHERE id = ?; 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

@ -2,3 +2,9 @@
UPDATE air_conditioner UPDATE air_conditioner
SET "on" = ?, temperature = ?, burnt = ?, frozen = ? SET "on" = ?, temperature = ?, burnt = ?, frozen = ?
WHERE id = ?; WHERE id = ?;
-- 插入日志记录(从更新后的数据获取)
INSERT INTO air_conditioner_log (id, "on", temperature, burnt, frozen)
SELECT id, "on", temperature, burnt, frozen
FROM air_conditioner
WHERE id = ?;