补充设备告警与休眠设置后端能力
This commit is contained in:
@@ -367,6 +367,20 @@ CREATE TABLE IF NOT EXISTS `device_settings` (
|
||||
REFERENCES `device_auth` (`device_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Device alarm events are reported by the device side through MQTT msg_id=010.
|
||||
CREATE TABLE IF NOT EXISTS `device_alarm_events` (
|
||||
`alarm_id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`device_id` VARCHAR(64) NOT NULL,
|
||||
`owner_user_id` BIGINT NULL,
|
||||
`child_id` BIGINT NULL,
|
||||
`source_msg_id` VARCHAR(8) NOT NULL DEFAULT '010',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`alarm_id`),
|
||||
KEY `idx_device_alarm_device_created` (`device_id`, `created_at`),
|
||||
KEY `idx_device_alarm_owner_created` (`owner_user_id`, `created_at`),
|
||||
KEY `idx_device_alarm_child_created` (`child_id`, `created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `im_conversations` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`conversation_type` TINYINT UNSIGNED NOT NULL,
|
||||
|
||||
@@ -77,6 +77,10 @@ class DeviceDAO(BaseDAO):
|
||||
db.device_id,
|
||||
db.child_id,
|
||||
c.child_name,
|
||||
ds.sleep_mode,
|
||||
ds.disable_time_start,
|
||||
ds.disable_time_end,
|
||||
ds.timezone,
|
||||
ds.power,
|
||||
ds.volume,
|
||||
ds.`signal` AS signal_strength,
|
||||
|
||||
86
talkingq-url/banban/dao/device_alarm.py
Normal file
86
talkingq-url/banban/dao/device_alarm.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from banban.dao import BaseDAO
|
||||
|
||||
|
||||
class DeviceAlarmDAO(BaseDAO):
|
||||
async def create(
|
||||
self,
|
||||
*,
|
||||
device_id: str,
|
||||
owner_user_id: int | None,
|
||||
child_id: int | None,
|
||||
source_msg_id: str = "010",
|
||||
) -> int:
|
||||
result = await self.execute(
|
||||
"""
|
||||
INSERT INTO device_alarm_events (
|
||||
device_id, owner_user_id, child_id, source_msg_id, created_at
|
||||
)
|
||||
VALUES (
|
||||
:device_id, :owner_user_id, :child_id, :source_msg_id, CURRENT_TIMESTAMP
|
||||
)
|
||||
""",
|
||||
{
|
||||
"device_id": device_id,
|
||||
"owner_user_id": owner_user_id,
|
||||
"child_id": child_id,
|
||||
"source_msg_id": source_msg_id,
|
||||
},
|
||||
)
|
||||
await self.commit()
|
||||
return int(result.lastrowid)
|
||||
|
||||
async def get_active_binding_context(self, *, device_id: str) -> Mapping[str, Any] | None:
|
||||
result = await self.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT owner_user_id, child_id
|
||||
FROM device_bindings
|
||||
WHERE device_id = :device_id
|
||||
AND status = 1
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"device_id": device_id},
|
||||
)
|
||||
return result.mappings().first()
|
||||
|
||||
async def list_by_device(
|
||||
self,
|
||||
*,
|
||||
device_id: str,
|
||||
owner_user_id: int,
|
||||
limit: int,
|
||||
) -> list[Mapping[str, Any]]:
|
||||
result = await self.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
dae.alarm_id,
|
||||
dae.device_id,
|
||||
dae.owner_user_id,
|
||||
dae.child_id,
|
||||
dae.source_msg_id,
|
||||
dae.created_at,
|
||||
c.child_name
|
||||
FROM device_alarm_events AS dae
|
||||
LEFT JOIN children AS c
|
||||
ON c.child_id = dae.child_id
|
||||
AND c.status = 1
|
||||
WHERE dae.device_id = :device_id
|
||||
AND dae.owner_user_id = :owner_user_id
|
||||
ORDER BY dae.created_at DESC, dae.alarm_id DESC
|
||||
LIMIT :limit
|
||||
"""
|
||||
),
|
||||
{
|
||||
"device_id": device_id,
|
||||
"owner_user_id": owner_user_id,
|
||||
"limit": limit,
|
||||
},
|
||||
)
|
||||
return list(result.mappings().all())
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from collections.abc import Mapping
|
||||
from datetime import datetime
|
||||
from datetime import datetime, time
|
||||
from services.connection_manager import connection_manager
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -15,6 +15,7 @@ try:
|
||||
DeviceLocationTrajectoryItem,
|
||||
DeviceLocationTrajectoryResponse,
|
||||
)
|
||||
from banban.service.device_alarm import device_alarm_service
|
||||
from banban.service.location import location_service
|
||||
from banban.service.device import device_service
|
||||
except ModuleNotFoundError:
|
||||
@@ -24,6 +25,7 @@ except ModuleNotFoundError:
|
||||
DeviceLocationTrajectoryItem,
|
||||
DeviceLocationTrajectoryResponse,
|
||||
)
|
||||
from banban.service.device_alarm import device_alarm_service
|
||||
from banban.service.location import location_service
|
||||
from banban.service.device import device_service
|
||||
|
||||
@@ -53,6 +55,10 @@ class DeviceStatusResponse(BaseModel):
|
||||
device_id: str
|
||||
child_id: int | None = None
|
||||
child_name: str | None = None
|
||||
sleep_mode: int | None = None
|
||||
disable_time_start: str | None = None
|
||||
disable_time_end: str | None = None
|
||||
timezone: str | None = None
|
||||
power: int | None = None
|
||||
volume: int | None = None
|
||||
signal: int | None = None
|
||||
@@ -72,6 +78,20 @@ class DeviceStatusResponse(BaseModel):
|
||||
location_updated_at: datetime | None = None
|
||||
|
||||
|
||||
class DeviceAlarmItem(BaseModel):
|
||||
alarm_id: int
|
||||
device_id: str
|
||||
child_id: int | None = None
|
||||
child_name: str | None = None
|
||||
source_msg_id: str
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class DeviceAlarmListResponse(BaseModel):
|
||||
items: list[DeviceAlarmItem]
|
||||
total: int
|
||||
|
||||
|
||||
class DeviceVolumeUpdateRequest(BaseModel):
|
||||
level: int = Field(ge=0, le=100)
|
||||
|
||||
@@ -82,6 +102,31 @@ class DeviceVolumeUpdateResponse(BaseModel):
|
||||
msg_id: str
|
||||
|
||||
|
||||
class DeviceSleepScheduleUpdateRequest(BaseModel):
|
||||
start: str = Field(..., pattern=r"^\d{2}:\d{2}$")
|
||||
end: str = Field(..., pattern=r"^\d{2}:\d{2}$")
|
||||
timezone: str = Field(default="Asia/Shanghai", min_length=1, max_length=32)
|
||||
|
||||
|
||||
class DeviceSleepScheduleResponse(BaseModel):
|
||||
device_id: str
|
||||
sleep_mode: int
|
||||
start: str | None = None
|
||||
end: str | None = None
|
||||
timezone: str
|
||||
msg_id: str | None = None
|
||||
|
||||
|
||||
class DeviceRemoteSleepWakeRequest(BaseModel):
|
||||
switch: str = Field(..., pattern=r"^(on|off)$")
|
||||
|
||||
|
||||
class DeviceRemoteSleepWakeResponse(BaseModel):
|
||||
device_id: str
|
||||
switch: str
|
||||
msg_id: str
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,11 +185,31 @@ def _row_to_trajectory_item(row: Mapping, *, child_name: str | None) -> DeviceLo
|
||||
)
|
||||
|
||||
|
||||
def _format_time_value(value: object) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, time):
|
||||
return value.strftime("%H:%M")
|
||||
text_value = str(value)
|
||||
return text_value[:5] if len(text_value) >= 5 else text_value
|
||||
|
||||
|
||||
def _parse_time_value(value: str, field_name: str) -> time:
|
||||
try:
|
||||
return datetime.strptime(value, "%H:%M").time()
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=f"{field_name} must use HH:MM format") from exc
|
||||
|
||||
|
||||
def _row_to_device_status_response(row: Mapping) -> DeviceStatusResponse:
|
||||
return DeviceStatusResponse(
|
||||
device_id=str(row["device_id"]),
|
||||
child_id=int(row["child_id"]) if row["child_id"] is not None else None,
|
||||
child_name=row.get("child_name"),
|
||||
sleep_mode=row.get("sleep_mode"),
|
||||
disable_time_start=_format_time_value(row.get("disable_time_start")),
|
||||
disable_time_end=_format_time_value(row.get("disable_time_end")),
|
||||
timezone=row.get("timezone"),
|
||||
power=row["power"],
|
||||
volume=row["volume"],
|
||||
signal=row["signal_strength"],
|
||||
@@ -165,6 +230,17 @@ def _row_to_device_status_response(row: Mapping) -> DeviceStatusResponse:
|
||||
)
|
||||
|
||||
|
||||
def _row_to_alarm_item(row: Mapping) -> DeviceAlarmItem:
|
||||
return DeviceAlarmItem(
|
||||
alarm_id=int(row["alarm_id"]),
|
||||
device_id=str(row["device_id"]),
|
||||
child_id=int(row["child_id"]) if row["child_id"] is not None else None,
|
||||
child_name=row.get("child_name"),
|
||||
source_msg_id=str(row["source_msg_id"]),
|
||||
created_at=row["created_at"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{device_id}/messages", response_model=DeviceMessageListResponse)
|
||||
async def list_device_messages(
|
||||
device_id: str,
|
||||
@@ -226,6 +302,36 @@ async def get_device_status(
|
||||
return _row_to_device_status_response(row)
|
||||
|
||||
|
||||
@router.get("/{device_id}/alarms", response_model=DeviceAlarmListResponse)
|
||||
async def list_device_alarms(
|
||||
device_id: str,
|
||||
request: Request,
|
||||
limit: int = Query(default=20, ge=1, le=100),
|
||||
current_user_id: int = Depends(get_current_user_id),
|
||||
) -> DeviceAlarmListResponse:
|
||||
await device_service.ensure_device_access(device_id=device_id, user_id=current_user_id)
|
||||
rows = await device_alarm_service.list_device_alarms(
|
||||
device_id=device_id,
|
||||
owner_user_id=current_user_id,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"device alarms fetched",
|
||||
extra={
|
||||
"event": "device_alarm_list",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"user_id": current_user_id,
|
||||
"device_id": device_id,
|
||||
"returned_count": len(rows),
|
||||
},
|
||||
)
|
||||
return DeviceAlarmListResponse(
|
||||
items=[_row_to_alarm_item(row) for row in rows],
|
||||
total=len(rows),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{device_id}/volume", response_model=DeviceVolumeUpdateResponse)
|
||||
async def set_device_volume(
|
||||
device_id: str,
|
||||
@@ -253,6 +359,77 @@ async def set_device_volume(
|
||||
return DeviceVolumeUpdateResponse(device_id=device_id, level=payload.level, msg_id=msg_id)
|
||||
|
||||
|
||||
@router.post("/{device_id}/sleep-schedule", response_model=DeviceSleepScheduleResponse)
|
||||
async def set_device_sleep_schedule(
|
||||
device_id: str,
|
||||
payload: DeviceSleepScheduleUpdateRequest,
|
||||
request: Request,
|
||||
current_user_id: int = Depends(get_current_user_id),
|
||||
) -> DeviceSleepScheduleResponse:
|
||||
start_time = _parse_time_value(payload.start, "start")
|
||||
end_time = _parse_time_value(payload.end, "end")
|
||||
msg_id = await device_service.set_sleep_schedule(
|
||||
device_id=device_id,
|
||||
user_id=current_user_id,
|
||||
start=start_time,
|
||||
end=end_time,
|
||||
timezone=payload.timezone,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"device sleep schedule command sent",
|
||||
extra={
|
||||
"event": "device_sleep_schedule_set",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"user_id": current_user_id,
|
||||
"device_id": device_id,
|
||||
"start": payload.start,
|
||||
"end": payload.end,
|
||||
"timezone": payload.timezone,
|
||||
"msg_id": msg_id,
|
||||
},
|
||||
)
|
||||
return DeviceSleepScheduleResponse(
|
||||
device_id=device_id,
|
||||
sleep_mode=1,
|
||||
start=payload.start,
|
||||
end=payload.end,
|
||||
timezone=payload.timezone,
|
||||
msg_id=msg_id,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{device_id}/sleep-wake", response_model=DeviceRemoteSleepWakeResponse)
|
||||
async def set_device_remote_sleep_wake(
|
||||
device_id: str,
|
||||
payload: DeviceRemoteSleepWakeRequest,
|
||||
request: Request,
|
||||
current_user_id: int = Depends(get_current_user_id),
|
||||
) -> DeviceRemoteSleepWakeResponse:
|
||||
msg_id = await device_service.set_remote_sleep_wake(
|
||||
device_id=device_id,
|
||||
user_id=current_user_id,
|
||||
switch=payload.switch,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"device remote sleep wake command sent",
|
||||
extra={
|
||||
"event": "device_remote_sleep_wake_set",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"user_id": current_user_id,
|
||||
"device_id": device_id,
|
||||
"switch": payload.switch,
|
||||
"msg_id": msg_id,
|
||||
},
|
||||
)
|
||||
return DeviceRemoteSleepWakeResponse(
|
||||
device_id=device_id,
|
||||
switch=payload.switch,
|
||||
msg_id=msg_id,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@router.get("/{device_id}/location", response_model=DeviceLocationCurrentResponse)
|
||||
async def get_current_device_location(
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, List
|
||||
from datetime import time
|
||||
from typing import Any, List, Literal
|
||||
|
||||
from services.database_service_base import DatabaseServiceBase
|
||||
from fastapi import HTTPException
|
||||
|
||||
from banban.dao.device import DeviceDAO
|
||||
from banban.service.device_setting import device_setting_service
|
||||
|
||||
|
||||
class DeviceService(DatabaseServiceBase):
|
||||
@@ -68,6 +70,51 @@ class DeviceService(DatabaseServiceBase):
|
||||
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
|
||||
return await service.send_volume_command(device_id, level)
|
||||
|
||||
async def set_sleep_schedule(
|
||||
self,
|
||||
*,
|
||||
device_id: str,
|
||||
user_id: int,
|
||||
start: time,
|
||||
end: time,
|
||||
timezone: str,
|
||||
) -> str:
|
||||
await self.ensure_device_access(device_id=device_id, user_id=user_id)
|
||||
|
||||
from handlers.mqtt_handler import TalkingQMQTTService
|
||||
|
||||
service = await TalkingQMQTTService.get_instance()
|
||||
if service is None:
|
||||
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
|
||||
|
||||
start_text = start.strftime("%H:%M")
|
||||
end_text = end.strftime("%H:%M")
|
||||
msg_id = await service.send_sleep_schedule_command(device_id, start_text, end_text)
|
||||
await device_setting_service.upsert_sleep_schedule(
|
||||
device_id=device_id,
|
||||
sleep_mode=1,
|
||||
disable_time_start=start,
|
||||
disable_time_end=end,
|
||||
timezone=timezone,
|
||||
)
|
||||
return msg_id
|
||||
|
||||
async def set_remote_sleep_wake(
|
||||
self,
|
||||
*,
|
||||
device_id: str,
|
||||
user_id: int,
|
||||
switch: Literal["on", "off"],
|
||||
) -> str:
|
||||
await self.ensure_device_access(device_id=device_id, user_id=user_id)
|
||||
|
||||
from handlers.mqtt_handler import TalkingQMQTTService
|
||||
|
||||
service = await TalkingQMQTTService.get_instance()
|
||||
if service is None:
|
||||
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
|
||||
return await service.send_remote_sleep_wake_command(device_id, switch)
|
||||
|
||||
|
||||
# 创建全局 DeviceService 实例
|
||||
device_service = DeviceService()
|
||||
|
||||
47
talkingq-url/banban/service/device_alarm.py
Normal file
47
talkingq-url/banban/service/device_alarm.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from banban.dao.device_alarm import DeviceAlarmDAO
|
||||
from services.database_service_base import DatabaseServiceBase
|
||||
|
||||
|
||||
class DeviceAlarmService(DatabaseServiceBase):
|
||||
def __init__(self):
|
||||
super().__init__(service_name="device_alarm_service")
|
||||
|
||||
async def record_alarm_event(self, *, device_id: str, source_msg_id: str = "010") -> int | None:
|
||||
db_session = await self.get_session()
|
||||
try:
|
||||
dao = DeviceAlarmDAO(db_session)
|
||||
binding = await dao.get_active_binding_context(device_id=device_id)
|
||||
if not binding:
|
||||
return None
|
||||
return await dao.create(
|
||||
device_id=device_id,
|
||||
owner_user_id=int(binding["owner_user_id"]) if binding.get("owner_user_id") is not None else None,
|
||||
child_id=int(binding["child_id"]) if binding.get("child_id") is not None else None,
|
||||
source_msg_id=source_msg_id,
|
||||
)
|
||||
finally:
|
||||
await db_session.close()
|
||||
|
||||
async def list_device_alarms(
|
||||
self,
|
||||
*,
|
||||
device_id: str,
|
||||
owner_user_id: int,
|
||||
limit: int,
|
||||
) -> list[Mapping[str, Any]]:
|
||||
db_session = await self.get_session()
|
||||
try:
|
||||
dao = DeviceAlarmDAO(db_session)
|
||||
return await dao.list_by_device(
|
||||
device_id=device_id,
|
||||
owner_user_id=owner_user_id,
|
||||
limit=limit,
|
||||
)
|
||||
finally:
|
||||
await db_session.close()
|
||||
|
||||
|
||||
device_alarm_service = DeviceAlarmService()
|
||||
@@ -110,5 +110,33 @@ class DeviceSettingService(DatabaseServiceBase):
|
||||
finally:
|
||||
pass
|
||||
|
||||
async def upsert_sleep_schedule(
|
||||
self,
|
||||
*,
|
||||
device_id: str,
|
||||
sleep_mode: int,
|
||||
disable_time_start: time,
|
||||
disable_time_end: time,
|
||||
timezone: str,
|
||||
) -> None:
|
||||
current_row = await self.get_setting_by_device_id(device_id=device_id)
|
||||
if current_row:
|
||||
await self.update_setting(
|
||||
device_id=device_id,
|
||||
sleep_mode=sleep_mode,
|
||||
disable_time_start=disable_time_start,
|
||||
disable_time_end=disable_time_end,
|
||||
timezone=timezone,
|
||||
)
|
||||
return
|
||||
|
||||
await self.create_setting(
|
||||
device_id=device_id,
|
||||
sleep_mode=sleep_mode,
|
||||
disable_time_start=disable_time_start,
|
||||
disable_time_end=disable_time_end,
|
||||
timezone=timezone,
|
||||
)
|
||||
|
||||
# 创建全局 DeviceSettingService 实例
|
||||
device_setting_service = DeviceSettingService()
|
||||
|
||||
@@ -267,6 +267,22 @@ class DeviceSetting(Base):
|
||||
onupdate=datetime.utcnow,
|
||||
)
|
||||
|
||||
|
||||
class DeviceAlarmEvent(Base):
|
||||
__tablename__ = "device_alarm_events"
|
||||
__table_args__ = (
|
||||
Index("idx_device_alarm_device_created", "device_id", "created_at"),
|
||||
Index("idx_device_alarm_owner_created", "owner_user_id", "created_at"),
|
||||
Index("idx_device_alarm_child_created", "child_id", "created_at"),
|
||||
)
|
||||
|
||||
alarm_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
owner_user_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
child_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
source_msg_id: Mapped[str] = mapped_column(String(8), server_default=text("'010'"))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
|
||||
class IMConversation(Base):
|
||||
__tablename__ = "im_conversations"
|
||||
__table_args__ = (
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Awaitable, Callable, Dict, Optional
|
||||
import aiomqtt
|
||||
|
||||
from banban.service.binding import BindingService
|
||||
from banban.service.device_alarm import device_alarm_service
|
||||
from banban.service.device_setting import device_setting_service
|
||||
from banban.service.location import location_service
|
||||
from config import settings
|
||||
@@ -240,6 +241,25 @@ class TalkingQMQTTService:
|
||||
status = payload.get("status")
|
||||
if status == "success":
|
||||
logger.info(device_id, "", f"[定时休眠] 设备 {device_id} 休眠时间设置成功")
|
||||
data = payload.get("data", {}) or {}
|
||||
start = data.get("start")
|
||||
end = data.get("end")
|
||||
timezone = data.get("timezone") or "Asia/Shanghai"
|
||||
if start and end:
|
||||
try:
|
||||
await self._schedule_persistence(
|
||||
device_id,
|
||||
"sleep_schedule",
|
||||
device_setting_service.upsert_sleep_schedule(
|
||||
device_id=device_id,
|
||||
sleep_mode=1,
|
||||
disable_time_start=datetime.strptime(start, "%H:%M").time(),
|
||||
disable_time_end=datetime.strptime(end, "%H:%M").time(),
|
||||
timezone=timezone,
|
||||
),
|
||||
)
|
||||
except ValueError:
|
||||
logger.warning(device_id, "", f"[定时休眠] 无法解析设备返回的时间: {payload}")
|
||||
else:
|
||||
logger.warning(device_id, "", f"[定时休眠] 设备 {device_id} 休眠时间设置失败: {payload}")
|
||||
|
||||
@@ -252,6 +272,11 @@ class TalkingQMQTTService:
|
||||
|
||||
async def _handle_alarm_report(self, device_id: str, payload: dict):
|
||||
logger.info(device_id, "", f"[告警] 设备 {device_id} 发送紧急报警")
|
||||
await self._schedule_persistence(
|
||||
device_id,
|
||||
"alarm_event",
|
||||
device_alarm_service.record_alarm_event(device_id=device_id, source_msg_id="010"),
|
||||
)
|
||||
await self._publish(f"device/{device_id}/event_resp", {"msg_id": "010", "status": "success"})
|
||||
|
||||
async def _handle_short_press_message(self, device_id: str, payload: dict):
|
||||
|
||||
@@ -367,6 +367,20 @@ CREATE TABLE IF NOT EXISTS `device_settings` (
|
||||
REFERENCES `device_auth` (`device_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Device alarm events are reported by the device side through MQTT msg_id=010.
|
||||
CREATE TABLE IF NOT EXISTS `device_alarm_events` (
|
||||
`alarm_id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`device_id` VARCHAR(64) NOT NULL,
|
||||
`owner_user_id` BIGINT NULL,
|
||||
`child_id` BIGINT NULL,
|
||||
`source_msg_id` VARCHAR(8) NOT NULL DEFAULT '010',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`alarm_id`),
|
||||
KEY `idx_device_alarm_device_created` (`device_id`, `created_at`),
|
||||
KEY `idx_device_alarm_owner_created` (`owner_user_id`, `created_at`),
|
||||
KEY `idx_device_alarm_child_created` (`child_id`, `created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `im_conversations` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`conversation_type` TINYINT UNSIGNED NOT NULL,
|
||||
@@ -427,20 +441,20 @@ CREATE TABLE IF NOT EXISTS `im_messages` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `child_location_current` (
|
||||
`child_id` BIGINT NOT NULL COMMENT '孩子ID,当前表每个孩子仅保留一条最新定位',
|
||||
`device_id` VARCHAR(64) NOT NULL COMMENT '定位上报设备ID',
|
||||
`coord_type` VARCHAR(16) NOT NULL DEFAULT 'gcj02' COMMENT '坐标系类型,统一使用gcj02',
|
||||
`lat` DECIMAL(10, 7) NOT NULL COMMENT '纬度',
|
||||
`lng` DECIMAL(10, 7) NOT NULL COMMENT '经度',
|
||||
`accuracy_m` INT UNSIGNED NULL COMMENT '定位精度,单位米',
|
||||
`altitude_m` DECIMAL(8, 2) NULL COMMENT '海拔高度,单位米',
|
||||
`speed_mps` DECIMAL(8, 2) NULL COMMENT '速度,单位米每秒',
|
||||
`heading_deg` SMALLINT UNSIGNED NULL COMMENT '航向角,单位度,范围0到360',
|
||||
`source` TINYINT UNSIGNED NOT NULL COMMENT '定位来源编码,由设备侧约定',
|
||||
`battery_pct` TINYINT UNSIGNED NULL COMMENT '设备电量百分比,范围0到100',
|
||||
`device_time` DATETIME NOT NULL COMMENT '设备采集定位时间',
|
||||
`server_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '服务端接收并写入时间',
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '当前定位记录最后更新时间',
|
||||
`child_id` BIGINT NOT NULL,
|
||||
`device_id` VARCHAR(64) NOT NULL,
|
||||
`coord_type` VARCHAR(16) NOT NULL DEFAULT 'gcj02',
|
||||
`lat` DECIMAL(10, 7) NOT NULL,
|
||||
`lng` DECIMAL(10, 7) NOT NULL,
|
||||
`accuracy_m` INT UNSIGNED NULL,
|
||||
`altitude_m` DECIMAL(8, 2) NULL,
|
||||
`speed_mps` DECIMAL(8, 2) NULL,
|
||||
`heading_deg` SMALLINT UNSIGNED NULL,
|
||||
`source` TINYINT UNSIGNED NOT NULL,
|
||||
`battery_pct` TINYINT UNSIGNED NULL,
|
||||
`device_time` DATETIME NOT NULL,
|
||||
`server_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`child_id`),
|
||||
KEY `idx_child_loc_current_device` (`device_id`),
|
||||
KEY `idx_child_loc_current_server_time` (`server_time`),
|
||||
@@ -454,21 +468,21 @@ CREATE TABLE IF NOT EXISTS `child_location_current` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `child_location_history` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '历史定位记录ID',
|
||||
`child_id` BIGINT NOT NULL COMMENT '孩子ID',
|
||||
`device_id` VARCHAR(64) NOT NULL COMMENT '定位上报设备ID',
|
||||
`coord_type` VARCHAR(16) NOT NULL DEFAULT 'gcj02' COMMENT '坐标系类型,统一使用gcj02',
|
||||
`lat` DECIMAL(10, 7) NOT NULL COMMENT '纬度',
|
||||
`lng` DECIMAL(10, 7) NOT NULL COMMENT '经度',
|
||||
`accuracy_m` INT UNSIGNED NULL COMMENT '定位精度,单位米',
|
||||
`altitude_m` DECIMAL(8, 2) NULL COMMENT '海拔高度,单位米',
|
||||
`speed_mps` DECIMAL(8, 2) NULL COMMENT '速度,单位米每秒',
|
||||
`heading_deg` SMALLINT UNSIGNED NULL COMMENT '航向角,单位度,范围0到360',
|
||||
`source` TINYINT UNSIGNED NOT NULL COMMENT '定位来源编码,由设备侧约定',
|
||||
`battery_pct` TINYINT UNSIGNED NULL COMMENT '设备电量百分比,范围0到100',
|
||||
`device_time` DATETIME NOT NULL COMMENT '设备采集定位时间',
|
||||
`server_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '服务端接收并写入时间',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '历史定位记录创建时间',
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`child_id` BIGINT NOT NULL,
|
||||
`device_id` VARCHAR(64) NOT NULL,
|
||||
`coord_type` VARCHAR(16) NOT NULL DEFAULT 'gcj02',
|
||||
`lat` DECIMAL(10, 7) NOT NULL,
|
||||
`lng` DECIMAL(10, 7) NOT NULL,
|
||||
`accuracy_m` INT UNSIGNED NULL,
|
||||
`altitude_m` DECIMAL(8, 2) NULL,
|
||||
`speed_mps` DECIMAL(8, 2) NULL,
|
||||
`heading_deg` SMALLINT UNSIGNED NULL,
|
||||
`source` TINYINT UNSIGNED NOT NULL,
|
||||
`battery_pct` TINYINT UNSIGNED NULL,
|
||||
`device_time` DATETIME NOT NULL,
|
||||
`server_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_child_loc_hist_child` (`child_id`, `created_at`),
|
||||
KEY `idx_child_loc_hist_device` (`device_id`, `created_at`),
|
||||
|
||||
Reference in New Issue
Block a user