找回密码
 立即注册

微信扫码登录

搜索
查看: 97|回复: 2

[流程系列] 【阿木制作】一个通过人体传感器描述活动规迹的流

[复制链接]

64

主题

235

回帖

2315

积分

论坛技术达人

积分
2315
金钱
2011
HASS币
20
发表于 昨天 11:07 | 显示全部楼层 |阅读模式
室内一般不安装监控,风险太大,只安装了人体传感器。所以制作了一个记录活动轨迹的流。
1、卡片展示,展示内容自动生成,在track_text属性中:
屏幕截图_11-12-2025_11447_home.lamnas.top.jpeg

2、流程图:
屏幕截图_11-12-2025_11050_home.lamnas.top.jpeg
3、函数代码:
// ========== 1. 传感器与房间映射关系 =============
const SENSOR_MAP = {
//    szMotion: "书桌",     // 书桌传感器
    ktMotion: "客厅",     // 客厅传感器
    ctMotion: "餐厅",     // 餐厅传感器
    cfMotion: "厨房",     // 厨房传感器
    shuifMotion: "水房",  // 水房传感器
    zwMotion: "主卧",     // 主卧传感器
    zweiMotion: "主卫",   // 主卫传感器
    cwMotion: "次卧",     // 次卧传感器
    gweiMotion: "公卫",   // 公卫传感器
    sfMotion: "书房",     // 书房传感器
    ytMotion: "阳台"      // 阳台传感器
};

// 最大历史记录数量,保存最近10次活动记录
const MAX_HISTORY = 5;

// ========== 2. 读取上一次的运行缓存 ===============
// 从系统中读取上一次保存的活动信息,包含历史记录数组,如果没有缓存,则创建一个空对象
let lastActiveInfo = flow.get("last_active_info") || {};

// 从缓存中获取历史记录,如果没有则为空数组,slice() 用于创建数组副本,避免修改原数组
let historyCache = Array.isArray(lastActiveInfo.historyCache) ?
    lastActiveInfo.historyCache.slice() : [];

// ========== 3. 处理所有传感器状态 ===============
// 创建一个空数组,用于存放当前活动的传感器信息
let active = [];

// 遍历所有传感器(SENSOR_MAP中的每个键)
for (let key in SENSOR_MAP) {
    const state = msg[key];      // 获取传感器状态(例如:true/false, "on"/"off")
    const data = msg[key + "_data"];      // 获取传感器的详细数据,包含最后变化时间
    if (!data || !data.last_changed) continue;      // 如果传感器没有数据或没有最后变化时间,跳过这个传感器
    // 检查传感器是否处于活动状态(开状态)
    if (isOn(state)) {
        const parsed = new Date(data.last_changed);         // 将最后变化时间字符串转换为日期对象
        if (isNaN(parsed.getTime())) continue;               // 检查时间是否有效,无效则跳过
        // 将活动传感器信息存入active数组
        active.push({
            sensor: key,                        // 传感器ID
            room: SENSOR_MAP[key],              // 对应房间名称
            time_ms: parsed.getTime(),          // 时间戳(毫秒数)
            time: formatTime(parsed.getTime()), // 格式化的时间字符串
            raw: data.last_changed              // 原始时间字符串
        });
    }
}

// 将活动传感器按时间从新到旧排序(最新的在前面)
active.sort((a, b) => b.time_ms - a.time_ms);

// ========== 4. 初始化输出变量 ===============
let motionRoom = "无活动";     // 当前活动房间,默认"无活动"
let motionSensor = null;       // 当前活动传感器ID
let motionTime = null;         // 当前活动时间
let motionSinceTime = null;    // 当前活动距离现在的时间(如"5分钟前")

let prevRoom = null;           // 前一个活动房间
let prevSensor = null;         // 前一个活动传感器ID
let prevTime = null;           // 前一个活动时间
let prevSinceTime = null;      // 前一个活动距离现在的时间

let track_history = [];        // 完整历史轨迹数组
let track_detail = "";         // 历史轨迹文字描述

// ========== 5. 情况1:有传感器活动时的处理 ===============
if (active.length > 0) {
    // 获取最新的活动传感器(数组第一个元素)
    const newest = active[0];

    // 设置当前活动信息
    motionRoom = newest.room;
    motionSensor = newest.sensor;
    motionTime = newest.time;
    motionSinceTime = formatDuration(Date.now() - newest.time_ms);

    // 如果有多个活动传感器,设置前一个活动信息为第二个活动传感器
    if (active.length > 1) {
        prevRoom = active[1].room;
        prevSensor = active[1].sensor;
        prevTime = active[1].time;
        prevSinceTime = formatDuration(Date.now() - active[1].time_ms);
    }

    // ======= 关键步骤:将当前活动合并到历史缓存 =======
    // 遍历当前所有活动传感器(从最新到最旧)
    for (let a of active) {
        // 移除历史缓存中相同传感器的旧记录(避免重复)
        historyCache = historyCache.filter(h => h.sensor !== a.sensor);
        // 将新记录添加到历史缓存中
        historyCache.push({
            sensor: a.sensor,
            room: a.room,
            time_ms: a.time_ms,
            time: a.time
        });
    }

    // 对historyCache按时间从新到旧排序(最新的在最前面)
    historyCache.sort((a, b) => b.time_ms - a.time_ms);

    // 限制历史缓存长度不超过MAX_HISTORY
    if (historyCache.length > MAX_HISTORY) {
        historyCache = historyCache.slice(0, MAX_HISTORY);
    }

    // 用更新后的历史缓存生成轨迹历史(此时historyCache已按时间从新到旧排序)
    track_history = historyCache.map(item => ({
        room: item.room,
        sensor: item.sensor,
        time: item.time,
        time_ms: item.time_ms,
        sincetime: formatDuration(Date.now() - item.time_ms) // 计算距离现在的时间
    }));

    // 如果没有从active中获取到前一个活动,尝试从历史缓存中获取第二个记录
    if (!prevRoom && historyCache.length > 1) {
        prevRoom = historyCache[1].room;
        prevSensor = historyCache[1].sensor;
        prevTime = historyCache[1].time;
        prevSinceTime = formatDuration(Date.now() - historyCache[1].time_ms);
    }

    // 生成轨迹文字描述
    track_detail = generateTrackText(track_history);

    // 更新并保存缓存
    lastActiveInfo = lastActiveInfo || {};
    lastActiveInfo.historyCache = historyCache.slice(); // 保存历史记录
    lastActiveInfo.track_detail = track_detail;         // 保存轨迹文字
    lastActiveInfo.last_on_time_ms = newest.time_ms;    // 保存最后活动时间戳
    lastActiveInfo.last_on_time = newest.time;          // 保存最后活动时间
    flow.set("last_active_info", lastActiveInfo);       // 写入系统缓存

    // ========== 6. 情况2:没有传感器活动时的处理 ===============
} else {
    // 如果历史缓存中有记录
    if (historyCache && historyCache.length > 0) {
        // 确保historyCache按时间从新到旧排序(最新的在前面)
        historyCache.sort((a, b) => b.time_ms - a.time_ms);
        
        // 用历史缓存生成轨迹历史,动态更新"距离现在的时间"
        track_history = historyCache.map(item => {
            const tms = (typeof item.time_ms === "number") ?
                item.time_ms :
                (new Date(item.time).getTime() || Date.now());
            return {
                room: item.room,
                sensor: item.sensor,
                time: item.time,
                time_ms: item.time_ms,
                sincetime: formatDuration(Date.now() - tms)
            };
        });

        // 从历史缓存中获取前一个活动信息
        if (historyCache.length > 1) {
            prevRoom = historyCache[0].room;
            prevSensor = historyCache[0].sensor;
            prevTime = historyCache[0].time;
            prevSinceTime = formatDuration(Date.now() - historyCache[0].time_ms);
        }

        // 重新生成轨迹文字描述
        track_detail = generateTrackText(track_history);

        // 保持缓存不变(可选更新)
        lastActiveInfo = lastActiveInfo || {};
        lastActiveInfo.historyCache = historyCache.slice();
        lastActiveInfo.track_detail = track_detail;
        flow.set("last_active_info", lastActiveInfo);
    } else {
        // 没有历史缓存:保持空记录
        track_history = [];
        track_detail = "";
    }

    // 当前显示无活动
    motionRoom = "无活动";
    motionTime = null;
    motionSinceTime = null;
}

// ============ 7. 数据输出 =====================
// 设置输出消息的内容
msg.payload = motionRoom;                     // 主输出:当前活动房间
msg.motion_room = motionRoom;                 // 当前活动房间
msg.motion_time = motionTime === null ? null : motionTime;  // 当前活动时间
msg.motion_sincetime = motionSinceTime === null ? null : motionSinceTime; // 多久前活动
msg.prev_room = prevRoom;                     // 前一个活动房间
msg.prev_time = prevTime;                     // 前一个活动时间
msg.prev_sincetime = prevSinceTime;           // 前一个活动多久前
msg.track_history = track_history;            // 完整历史轨迹数组
msg.track_detail = track_detail;              // 轨迹文字描述
msg.update_time = formatTime(new Date());     // 本次更新时间

// 返回处理后的消息
return msg;

// ============ 8. 工具函数 =====================

// 判断传感器是否为开启状态
// 支持多种格式:true, 1, "on", "true", "1"
function isOn(val) {
    if (val === true) return true;
    if (val === 1) return true;
    const s = String(val).toLowerCase();
    return s === "on" || s === "true" || s === "1";
}

// 将时间戳格式化为"年-月-日 时:分:秒"格式
function formatTime(ts) {
    const d = new Date(Number(ts));
    const pad = n => (n < 10 ? "0" + n : n);  // 补零函数
    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} `
        + `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}

// 将毫秒数转换为易读的时间描述
// 例如:65000毫秒 → "1分钟5秒"
function formatDuration(ms) {
    if (typeof ms !== "number" || ms < 0) return "0秒";
    const seconds = Math.floor(ms / 1000);      // 转换为秒
    const days = Math.floor(seconds / 86400);   // 计算天数
    const hours = Math.floor((seconds % 86400) / 3600);  // 计算小时
    const minutes = Math.floor((seconds % 3600) / 60);   // 计算分钟
    const secs = Math.floor(seconds % 60);               // 计算秒数

    // 按优先级返回:天 > 小时 > 分钟 > 秒
    if (days > 0) return `${days}天`;
    if (hours > 0) return `${hours}小时`;
    if (minutes > 0) return `${minutes}分钟`;
    return `${secs}秒`;
}


// 生成活动轨迹的文字描述(从近到远)
function generateTrackText(history, options = {}) {
    const {
        maxMiddleRooms = 3,  // 中间部分最多显示几个房间
        showAllMiddle = false // 是否显示所有中间房间
    } = options;
    
    if (!history || history.length === 0) return "";
    
    const len = history.length;
    const latest = history[0];
    let result = `最近${latest.sincetime}前在${latest.room}`;
    
    // 只有一条记录
    if (len === 1) return result + "。";
    
    // 处理中间部分
    if (len > 2) {
        const middleRooms = [];
        for (let i = 1; i < len - 1; i++) {
            middleRooms.push(history[i].room);
        }
        
        let middleText = "";
        if (showAllMiddle || middleRooms.length <= maxMiddleRooms) {
            // 显示所有中间房间
            middleText = middleRooms.join(",");
        } else {
            // 限制显示数量
            const displayRooms = middleRooms.slice(0, maxMiddleRooms);
            middleText = `${displayRooms.join(",")}等${middleRooms.length - maxMiddleRooms}处`;
        }
        
        result += `,之前${history[1].sincetime}在${middleText}`;
    }
    
    // 添加最后一条记录(倒数第一条)
    const earliest = history[len-1];
    result += `,更早${earliest.sincetime}前在${earliest.room}。`;
    
    return result;
}



4、下载地址:
游客,如果您要查看本帖隐藏内容请回复







回复

使用道具 举报

2

主题

43

回帖

177

积分

注册会员

积分
177
金钱
132
HASS币
0
发表于 昨天 12:39 | 显示全部楼层
不错不过不能区分吧所以记录不正确
回复

使用道具 举报

2

主题

60

回帖

536

积分

高级会员

积分
536
金钱
474
HASS币
0
发表于 昨天 13:03 | 显示全部楼层
学习学习
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian ( 晋ICP备17001384号-1 )

GMT+8, 2025-12-12 07:52 , Processed in 0.048358 second(s), 5 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表