纪念住了两年的出租屋

因为五月末要搬去上海(Actually 苏州昆山),要和这个住了两年的小屋子告别了,虽然说出租屋只有十一平但是生活还是一直在认真过,所以写点文字记录一下我和这个空间共处的两年时光。整个房间发生巨大变化是从去年双十一开始的,从那时起我陆续购买了各式各样的智能家电和收纳工具,也在 NAS 上搭了一整套的智能家居配套的管理工具,我开始思考自己应该如何和这个空间共处,如何让这个空间给我带来更多的安全感和亲切感。

在这篇文章中,我会介绍一些让我自豪的室内布置,也会详细的介绍每一个智能家居的配置方式,希望能给你带来一些启发。

收纳的部分

我完全放弃了把衣服挂在衣柜里的幻想,自如带的衣柜被塞满了塑料抽屉,所有的衣服洗干净之后直接卷一卷塞进抽屉里节省了非常多的空间。阳台也是类似的思路,几个(很贵的)塑料抽屉堆叠成了一个小的收纳角落,主要用来装一些反季的衣物、线缆、清洁用品之类的东西。

室内的抽屉柜室内的抽屉柜

抽屉上面的木板是单独买的,原本是给摄影棚做背景的,不过拿来做收纳箱的装饰也非常不错,尺寸恰到好处分毫不差,剩下的缝隙刚好可以塞进两个显示器的盒子,满足了强迫症对空间规划的全部幻想。

阳台的抽屉柜阳台的抽屉柜

值得一提的是因为有一个隔音还凑合的阳台,我把 NAS 转移到了阳台这边,极大的解决了硬盘半夜疯狂炒豆子给我带来的痛苦。

桌子上和床底下也是塞满了各式各样的抽屉,小杂物都可以塞进去,整个空间马上就会变得整洁很多(其实也没有——)。

桌子上的抽屉桌子上的抽屉

如果你想在出租屋的墙上墙上固定东西

兰丁胶、易拉胶和 Nano Leaf 的胶是唯三不会把墙皮扯掉的胶,但是兰丁胶粘不牢只能用来贴画,而且会留下油印;易拉胶可以用来粘遥控器啥的,但是不适合粘特别重的东西,东西会掉,墙皮也会跟着被扯掉;Nano Leaf 的胶虽然结实一些,但是一样不能粘太重的东西,以及取下来的时候要小心一些,不然还是会损伤墙面。

总的来说,出租屋你还是放弃在墙上粘东西的想法吧(

电器的部分

灯光

OK,这部分是主菜,我是一个非常喜欢电灯的人,淘宝购物车里面塞满了各式各样的电灯(Which made me be myself),同时我很讨厌阳光(吸血鬼属性),这就造成了一种蛮矛盾的情形:我的房间经常是拉上窗帘然后开几盏相当昏暗的灯。

为了达成这个扭曲的喜好,我买了四颗飞利浦的变色灯泡,有的直接放在书桌上,有的粘在了墙上(Sorry 房东,搞烂了你的墙 <( _ _ )>)。一般晚上到家都不会开太亮,调成很暗淡的橙色灯光可以让我处于一种比较放松的状态。

颜色昏暗的室内颜色昏暗的室内

唯一的遗憾是自如的合同不让我粉刷墙面,不然我可能会把整个屋子刷成水泥色的,这样无论是灯光呈现出来的效果还是灯座的颜色和墙面颜色的搭配都会更好看一些。

后来还搞了一组 Nanoleaf 贴在了墙上,室友跟我讲这灯晚上亮起来特别像一面窗户。这让我想起了之前蛮有争议的新闻:查理捐赠了 UCSB 的一栋学生宿舍并且要求宿舍的设计是 90% 以上的宿舍没有窗户,转而使用电字窗来模拟日照。其实我还蛮喜欢这个点子的,如果以后我能有幸自己买一栋房子的话,可能也会在厕所等封闭的空间装一些类似的电字窗,让封闭的空间看起来更通透一些。

「撕裂」形状的 Nano Leaf「撕裂」形状的 Nano Leaf

Nano Leaf 有一点比 Hue 好很多,它的固件原生支持播放动画。像是 Hue 如果要支持动画得单独装软件来控制灯的变色,跟 Node Red 之类的系统做集成的时候会有很麻烦的时序问题。所以睡前屋子里面的催眠灯光现在都是用 Nano Leaf 做的,非常棒。

桌子上我摆了一个 Awtrix,这东西很神,可以显示像素画之外还能播放声音,用它自带的 API 做了一些比较可爱的交互,每当我按下一个开关的时候都会有声音和动效的反馈,比较鸡掰的营造出了一种我和整个房间「互动」的感受。

Awtrix 像素灯Awtrix 像素灯

像素灯边上的纸雕灯是室友送我的圣诞礼物,直到二月末才雕完,可以说是做的很痛苦了(温馨提示,送朋友的圣诞礼物不要是没做完的手作品,真的很折磨人 _(:3 」∠ )_)。

夜晚的桌面夜晚的桌面

影音系统

影音系统就比较简单了,一个 Dell 的显示器后面配了一个绿联的 HDMI 转接器。显示器上有一个 3.5mm 耳机孔转蓝牙的转换器,可以直接把声音串到两边的音响上(我知道音质会变得很烂,但是因为我的听力非常烂所以听不出来差别)。绿联的这个转接器比较好的一点是可以用红外遥控器远程切设备,这样就可以相对容易的在 Chromecast、Switch 和电脑之间来回切换。但是这玩意的 HDCP 实现有问题,Chromecast 不能直接插,所以中间还串了一个 HDCP 的破解器。

智能化控制

现在家里的智能化控制有几个:

  • 据说早上看红光对眼睛好,所以设定了早上七点半室内打开亮度拉满的红色灯光叫我起床,戏剧效果拉满,根本睡不着灯亮立刻就清醒;
  • 每天晚上七点以后到后半夜三点之间回家,开门自动开灯并;
  • 离家自动关灯;
  • Zigbee 开关能够将灯光调整至不同的颜色和亮度;
  • 媒体开关能够在不同设备之间切换,切换设备时室内灯光会发生变化。

主要还是玩灯 ¯\(ツ)/¯,不愧是我。

除了智能灯泡之外我家里还有一些小东西:

  • 智能开关:用来控制愚蠢电器开关的,比如 Chromecast、电蚊香和纸雕灯;
  • 运动传感器:用来检测进门和出门的;
  • 红外发射器:博联的,方便用智能开关切换显示在屏幕上的设备,也可以控制空调但是考虑到我在这住了两年空调开机时间一共不超过六小时,所以没折腾它。

一个经验:绿米的设备稳定性相对好一些;涂鸦的便宜,但是因为是公开的协议各家各自生产设备,所以很容易踩到坑,我买的开关两个月就挂了。门磁传感器是例外,绿米的门磁传感器买回来根本配对不上,涂鸦的倒是一下子就配上了。

整体上,不推荐买任何蓝牙协议的设备,如果想要和 Home Assistant 打配合,优先买 Zigbee 协议的设备,其次买 HTTP 通信的设备(比如 Nano Leaf 和 Awtrix),至少可以用 Node Red 发 HTTP 请求做系统整合。

这里补一个小知识,Zigbee 是一种通信协议,类似 TCP 协议,但是通信协议里面传的东西可以是不一样的,比如 HTTP,HTTPS,SFTP,SMTP。各家的网关做的事情就是翻译自家的协议内容然后控制设备。Zigbee2MQTT 可以充当一个通用的固件,它的生态里面包含了大多数主流设备的 Zigbee 通信「方言」,因此可以充当各家产品的网关(你可以理解为路由器的控制面板)。而 CC2531 接收器则是一个可以刷 Zigbee2MQTT 固件的接收器,可以接收任何设备发出的 Zigbee 信号并且传输给 Zigbee2MQTT(你可以理解为一个收发 Zigbee 信号的「路由器」的路由器),具体后面会讲。

控制系统

整个控制系统的构成比较简单,一个 Hue 的网关、一个 Home Assistant 上面跑一个 Node Red 来连控制逻辑,整套系统都跑在 NAS 上。

Home Assistant 安装

需要的东西比较简单:

  • 一台 NAS (QNAP 为佳,虚拟化支持比较好);
  • Home Assistant 虚拟机镜像;
  • 刷了 Zigbee2MQTT 的 CC2531。

Home Assistant 是用来做设备管理的,虽然说是开源拖拉机但是还挺好用,只炸过一次,修了一晚上就修好了。除了 Hue 的网关之外不需要买任何其他的智能网关,Home Assistant + CC2531 就已经是一个通用型的网关了。

NAS 上面装好 Home Assistant 的虚拟机镜像(比 Docker 版本的省心,有条件的话推荐直接装这个),把 CC2531 透进设备开机就行了。

该装的 Integration 装一装,像是 Nano Leaf、Hue、Chromecast、Broadlink。

该装的插件装一装,比如:

  • Visual Studio Code:用来改系统配置的;
  • Node Red:用来做复杂自动化流水线的;
  • Zigbee2MQTT:翻译 Zigbee 信号的;
  • Mosquitto broker:Zigbee2MQTT 把 Zigbee 信号翻译成 MQTT 消息之后需要这个插件把 MQTT 传到 Home Assistant 里。

室内场景配置

Home Assistant 内置的自动化系统包含了两种配置的方式:

  • 场景:场景是一种室内家电「状态」配置的组合,在启动某个场景时,所有的设备都会被设置到某个预定的状态,在关闭某个场景时,这些设置会被还原;
  • 脚本:设备的状态不会被记录,系统会按照预定的流程一项一项的执行任务或调整系统状态。

这两个东西我们都会用得到。

【场景】昏暗模式

提供最基本的光照能够辨认环境中的物体但不提供更多照明,以保持精神放松便于入睡(Actually 毕业之后睡眠开始变得容易出问题了 ˊ_>ˋ)。

  • 四盏 Hue 亮起暗橙色;
  • Nano Leaf 播放暗色版本的 Pumpkin Soup 动画;
  • 控制智能插座关闭纸雕灯。

一个典型的灯光场景配置一个典型的灯光场景配置

【脚本】Switch 娱乐模式

  • 发送红外信号控制 HDMI 转接器切换到 Switch 输入;
  • 启动游戏模式场景:
  • 四盏 Hue 亮起高饱和蓝色和黄色;
  • Nano Leaf 播放「星昼」动画;
  • 开启剪纸雕灯。

只是提供两个思路,可玩的还有很多。

羞羞的灯效羞羞的灯效

自动化流水线编写

Home Assistant 自带的自动化只能满足最基本的,把一些动作打包,如果要做复杂的自动化流水线还是得用 Node Red 来写。反过来,用 Node Red 做动作打包很麻烦,所以这边推荐的策略是:用 HA 自带的面板打包任务,送进 Node Red 做复杂逻辑执行。

Node Red 是一个低代码平台,用户通过拖拽节点、连接节点来绘制有向无环图,最后完成一些简单的自动化任务。

接入 Home Assistant

Node Red 只是跑在 Home Assistant 的 Host 上,本质他们两个还是两套分开的系统,要通过一些方法才能整合在一起,找到 events: all,这是一个 Home Assistant 的节点,所有受 Home Assistant 控制的设备状态发生变化都会让这个节点流出数据,有了这个数据源我们就可以做一些好♂玩的事情了。

智能开关控制基本灯光

小贴士:现在市面上的大多数智能开关最多只能叫「智能按钮」,它不能保留一个「开」或者「关」的状态。只能按下去,发出去一个信号,这是非常反直觉的。为了处理这个问题我做了这么一个流水线设计:

  • 按下按钮会开启一种灯光场景的模式;
  • 如果灯开着,按下了另一个按钮,那么切换到另外一个模式;
  • 如果灯开着,按下了相同的按钮,那么关灯。

为了做到这点我们需要:

  1. 清洗 Home Assistant 信号,只保留触发开关设备的信号:
    1. 筛选设备类型:参考表格中的 1.1 填写,设备名称在 Home Assistant 的 Entries 列表里面都能找到;
    2. 去除无用数据:一般你按下一个开关会同时产生若干个信号,对应状态的变化,但是我们只需要其中的一个信号,辨认出按下的是哪个按键就可以了,其他的信号都需要筛掉避免重复触发动作(参考表格中的 1.2 填写);
    3. 建立一个路由,将不同的设备派发给不同的流水线:跟 1.1 一样,直接复制粘贴就行。
    4. 建立一个路由,将不同的按钮派发给不同的流水线:参考表格中的 1.4 填写,比较值写你的按钮名称,按钮名称比较 Tricky 一点,不同的设备按钮名是不一样的,比如绿米的按钮有这几个:left, right, left_double, right_double, left_long, right_long, both,按钮名可以用 debug node 抓出来,具体怎么用上 Google 搜一下有很多,教程很多不赘述了。
步骤 Node Property 操作符 比较值
1.1 switch node payload.entity_id == 设备名称
1.2 switch node payload.event.old_state.attributes.state_class != 留空
1.2 switch node payload.event.old_state.state == 留空
1.2 switch node payload.event.new_state.state != 留空
1.3 switch node payload.entity_id == 设备名称(同 1.1)
1.4 switch node payload.event.new_state.state == 按钮名称

具体连接起来的效果和数据填写的方法可以参考这张图:

数据怎么填数据怎么填 Node Red 网络Node Red 网络

  1. 根据不同的按钮来操作灯光:
  2. function node 判断当前的灯光模式,并将即将启动的灯光模式写入全局变量(低代码不是不写代码,基本的逻辑控制还是得写写的,好在只是写 JS 而已):
1
2
3
4
5
6
7
8
9
10
// 有多少种灯光模式就写多少个脚本,不同的灯光模式 LIGHT_MODE_NAME 不一样。
const LIGHT_MODE_NAME = `dimLight`

if (global.get('currentLightMode') === LIGHT_MODE_NAME) {
global.set('currentLightMode', undefined);
return { payload: false };
} else {
global.set('currentLightMode', LIGHT_MODE_NAME);
return { payload: true };
}
  1. 新建一个 switch node 判断需要开灯还是关灯:
步骤 Node Property 操作符 比较值 比较值类型
2.2 switch node payload == false JSON
!= false JSON
  1. 这个 switch node 有两个输出,分别对应的是开灯操作和关灯操作:
    • 开灯:相对简单,只是告诉 Home Assistant 启动一个场景而已;
    • 关灯:我比较推荐写一个叫做关灯的场景,内容是把所有的灯和电源全都断掉,然后像开灯一样,启动一个叫关灯的场景就行了。
步骤 Node Domain Service Entity
2.3 call service node sense turn_on 你的场景名称

连完的效果可以看下图:

数据怎么填,false 的数据格式注意选 expression数据怎么填,false 的数据格式注意选 expression Node Red 网络,涂红的是用来控制 Awtrix 的,后面会讲Node Red 网络,涂红的是用来控制 Awtrix 的,后面会讲

早晨的灯光闹铃

这个相对简单一些,在 Node Red 里面装一个叫做 cronplus 的插件,然后把 cronplus 节点拖进去,schedule0 0 7 ? * MON-FRI *,意思是周一到周五早上七点执行任务,格式基本就是 cron,网上生成器挺多的,不愿意研究在界面里面直接生成也可以。

恐怖的清晨恐怖的清晨

cron 节点跟上一小节的灯光控制脚本连在一起,再借一个启动 sense 的节点就可以了,相对来讲比较简单,具体操作可以看下面这张图:

Cron 任务填写Cron 任务填写

进门自动开灯

这个可就刺激了,我试了很多种方案,最后找到了一个经济误判率又低的方法,你需要:

  • 一个人体传感器(绿米的就行),放在门外面,朝下对着门口;
  • 一个门磁传感器(涂鸦的就行)。

我们要连这么一个网络:

  • 如果门前有人,并且开门了,代表有人在门外开门,他想要进门;
  • 如果下午五点到后半夜三点之间有人进门,就开灯;
  • 如果门前有人,门关了,代表有人从屋里开门,他想要出门。

因为绿米的这个设备时间分辨率非常烂,一分钟才发出一个信号,所以只能以门磁传感器为触发事件的标准。另外,这种判断逻辑还有可能有些错误触发,比如你出门洗个手没关门,再回来把门关上他会以为你离开家了把灯全都关了,这很烦人,所以还得对事件触发做一个 Throttle 避免误触。我的判断标准是如果门连续开了一分钟没关上,那就不响应下一个事件。

逻辑理顺了就可以开始连线了:

  1. 一个起始节点 events: all,接收所有的 Home Assistant 信号;
  2. 一个 switch node 筛选门磁传感器和人体传感器;
步骤 Node Property 操作符 比较值
2 switch node payload.entity_id == 运动传感器 ID
2 == 门磁传感器 ID
  1. 接下要给运动传感器和门磁传感器做数据记录,方便后面进行比较,用 function node 就可以了:
  • 对于运动传感器,我们只需要记录它的状态:
1
2
3
4
5
6
7
8
9
10
11
12
13
// 读取一下之前的状态
const previousState = global.get('motionState');
// 读取现在的状态
const nextState = msg.payload.event.new_state.state;

// 如果状态没变化,跳过
if (previousState === nextState) return { payload: null };

// 如果状态变了,记一下状态
global.set('motionState', nextState);

// 吐出来给后面的节点
return { payload: nextState };
  • 对于门磁传感器,要做类似的事情:
1
2
3
4
5
6
7
8
9
10
11
12
13
const previousState = global.get('doorState');
const nextState = msg.payload.event.new_state.state;

if (previousState === nextState) return { payload: null };

global.set('doorState', nextState);
// 这里需要额外记录一下开门的时间,一会用来做「如果门开了一分钟就跳过关门检测」的功能
if (nextState === 'on') {
global.set('doorOpenTime', Date.now());
}

// 返回值可能是 on 或者 off
return { payload: nextState };
  1. 因为们磁传感器是触发开门检测的 Trigger,所以我们给门磁传感器再接一个 function node
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 一个常量,当门开了 60 秒后跳过下一次出门检测
// 六十秒内只做一次出门或进门检测,避免室内灯光出现鬼畜
const TIME_THRESHOLD = 60 * 1000;

// 读取我们要用的变量
const lastMotionState = global.get('motionState');
const lastDoorState = global.get('doorState');
const lastDoorOpenTime = global.get('doorOpenTime');
const lastInOutActionTriggerTime = global.get('lastInOutActionTriggerTime');

// 获得当前时间
const now = Date.now();

// 计算上次开门劲过了多长时间
const DOOR_OPEN_DIFF = now - lastDoorOpenTime;
// 计算上次进出门检测劲过了多长时间
const IN_OUT_DIFF = now - lastInOutActionTriggerTime;

// 如果进出门检测频率高于一分钟,跳过
if (IN_OUT_DIFF < TIME_THRESHOLD) return { payload: null };

// 如果门关了,人出现在门口,代表要出门了
if (
lastDoorState === 'off'
&& lastMotionState === 'on'
// 如果门开了超过一分钟,说明他其实不想离开家,不关灯
&& DOOR_OPEN_DIFF < TIME_THRESHOLD
) {
global.set('lastInOutActionTriggerTime', Date.now());
return { payload: 'out' };
}

// 如果门前有人,门开了,代表有人要进屋了
if (
lastDoorState === 'on'
&& lastMotionState === 'on'
) {
global.set('lastInOutActionTriggerTime', Date.now());
return { payload: 'in' };
}
  1. 出门接关灯,进门接开灯,如果希望太阳下山之后(假设是五点之后)才执行开灯的话,接一个 time range node 就可以了。

Node Red 怎么连Node Red 怎么连

整合媒体娱乐系统

跟灯光系统一样,都是调 sensescript,唯一不一样的是,如果你要用 Chromecast 播音乐的话填写的方式可能会比较 Tricky,比如我的设计是让 Chromecast 和 Plex 整合在一起,直接从 NAS 串流音乐,那么要建一个 script,并且添加一个 Action type 为 Call service 的任务,Service 是 media_player.play_media,Target 写你的 Chromecast,Content Id 写 plex://{ "playlist_name": "Workout", "shuffle": "1" },Content Type 写 PLAYLIST,这样就能随机播放我 Plex 播放列表里面的曲子了。

Plex 的配置方法Plex 的配置方法

整合 Awtrix

Awtrix 和 Node red / Home Assistant 都没有整合,得先用 Docker 装一个它的服务器,然后发 HTTP 请求过去。

  • 建一个 change node,下面的设置填两个,变量类型都是 JSON:
    • action 选 Set,target 选 msg.payload,to the value 写你的 awtrix 指令,Awtrix 面板上能调试,照着文档改一改就行了,比如:
1
2
3
4
5
6
7
8
9
10
{
"name":"Good Night",
"force":true,
"icon":343,
"moveIcon":false,
"repeat":2,
"text":"Good Night",
"soundfile":1,
"color":[117,66,0]
}
  • action 选 Set,target 选 msg.headers,to the value 写 {"content-type":"application/json"},告诉他你要发的是 JSON。

节点配置,标黄的地方记得选 JSON节点配置,标黄的地方记得选 JSON

  • 把一个 HTTP request node 和这个节点接在一起,URL 填你的 awtrix 地址,请求发出去你的钟就会发声播动画了。

下面是我家完整的网络结构配置,还挺……恶心的……

完整的网络结构完整的网络结构

结语

这个是我从学校出来所居住的第一个小地方,颇有年代感的小区里面住满了很有活力的中老年人,从他们的眼中能看到那种平静而幸福的神色。我每次去楼下小卖店买冰棍老奶奶都会多给我塞两个;另一家小超市的大叔在屋子里面一边吞云吐雾一遍对着直播里的大姐姐嘿嘿笑,这根本是我不曾见过的景象,煞是有趣。

这个房间我也特别、特别的喜欢。作为吸血鬼属性的一名男子,住在一个背阴的房间,本身就是一大幸事。对面就是一排很高的树木,也是一个很享受的事情。夏天躺在床上听着虫鸣、树叶沙沙的声音、楼下小孩子打闹的声音,非常的安逸和舒适。

但是人肯定要往前走,北京嘛,天子脚下不是人人都能立得住的地方。接下来究竟是在苏州定居,还是出国留学再刷一个硕士学位,这两条路我还很犹豫要怎么走,不过慢慢走一定能看到未来的吧。

一篇文章写了一天,纪念一下这两年的生活,可能也帮不到什么人,不过,That’s Life。

以上,莉莉爱你 ♥。

Comments

No comments here,

Why not write something?