RaspBoard计划
不知不觉家里的智能设备越来越多,而Home Assistant早期运行在树莓派里,设备多了响应也变得慢了起来。所以最近把Home Assistant从ARM架构迁移到NAS的Docker中后,Siri再也不会「请稍后,正在处理中」了,用户体验极好。🥳
这样树莓派也闲置了下来,在淘宝收了一个LCD显示屏正好可以放在桌上,不如改造一下做一个第三代的DashBoard:RaspBoard。

前几代DashBoard
命令行
第一个DashBoard是用Python写的一个简单的命令行,运行在iPad上,但是用了一段时间后就废弃掉了。
废弃的原因太多了:
- 文字太小看不清楚
- iPad长期开机导致了电池老化
- 用户界面(UX)非常不友好
Hub Center
第二个DashBoard使用了一台被Root的安卓机,通过Home Assistant提供的HADashboard服务,在手机上显示家里设备的状态和一些天气状态及公交状态。
因为可以通过WIFI使用ADB操作,所以可以完美的根据是否在家控制屏幕的开与关,再通过智能插座控制手机的充电状态。目前一直用到了现在。
但是必须依靠Home Assitant和有限的模块限制了其他信息的显示,所以有了RaspBoard的想法。
实现方式
同样的,RaspBoard通过网页显示所有的信息。Raspbian系统内置了一个Chromium,可以用来显示网页,而全屏打开Chromoum也非常的容易,只需要在终端执行:
chromium-browser --disable-popup-blocking --no-first-run --disable-desktop-notifications --kiosk "http://127.0.0.1"
这样,只需要写一个网页,然后定时请求不同的设备接口,RaspBoard就完工了。🎉
但是如何制作这个网页?
前端
前端还是使用了Vue,每一个模块/卡片为一个组件:
components
├── dashboard
│ ├── RdFansBoard.vue
│ ├── RdHassBoard.vue
│ ├── RdHeader.vue
│ ├── RdInfoBoard.vue
│ ├── RdPcBoard.vue
│ └── index.js
├── index.js
└── integrations
├── RdClick.vue
├── RdFans.vue
├── RdLight.vue
├── RdPc.vue
├── RdPi.vue
├── RdSwitch.vue
├── RdTemperature.vue
├── RdTime.vue
├── RdVpn.vue
├── RdWeather.vue
├── RdWeatherAqi.vue
├── index.js
└── mixins
└── hass.js
定时请求
定时请求使用了vue-cronoNPM包,当组件created
的时候创建定时器,定时器的生命周期同Vue,当Vue被destory
的时候删除定时器。
状态管理
状态管理使用了Vue的官方Vuex,通过store
集中管理所有的组件状态。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
每一个组件的状态大致为:
const state = {
status: false,
first: true,
next: 2,
data: {}
};
const mutations = {
SET_HASS(state, value) {
state.data = value
state.first = false
},
SET_NEXT(state, value) {
state.next = value
}
};
const actions = {
setHassData({commit}, data) {
commit('SET_HASS', data)
},
setHassNext({commit}, value) {
commit('SET_NEXT', value)
}
};
export default {
state,
mutations,
actions
}
每一个组件包含四个值:
- status: 判断当前数据是否有效
- first: 是否是第一次请求
- next: 轮询时间,当next为0时再次请求数据接口
- data: 接口数据

后端
每个组件根据数据的时效性来设置定时器请求数据接口,但是每隔1秒的HTTP请求显然很消耗资源。所以RashBoard没有直接请求数据接口,而是使用了tornado
来作为一层代理,前端和后端通过WebSocket连接用来转发所有的请求:
import tornado
import json
import requests
class EchoWebSocket(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
def open(self):
print("WebSocket opened")
def on_message(self, data):
···
def on_close(self):
print("WebSocket closed")
class Application(tornado.web.Application):
def __init__(self, handlers):
super(Application, self).__init__(handlers)
def main():
options.parse_command_line()
app = Application([('/', EchoWebSocket)])
app.listen(6901, address='0.0.0.0')
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
当Vue组件被创建后,RaspBoard和本地服务一直保持着长连接,通过WS交换数据:

作为服务
为了更好的控制,把代理作为一项systemctl服务:
vim /usr/lib/systemd/system/raspbpard-server.service
[Unit]
Description=raspbpard-server
After=network.target
[Service]
TimeoutStartSec=30
ExecStart=/usr/bin/python3 /home/hades/raspboard-server/ws.py
ExecStop=/bin/kill $MAINPID
[Install]
WantedBy=multi-user.target
生命周期
这样,RaspBoard的生命周期大致为:
- 创建Vue,开启WebSocket连接,定义定时器,保持
onMessage
监听 - 判断每个组件的状态和Next值,当组件定时器被清零后提交请求
- 服务端按照队列请求数据接口,请求成功后发送
message
到前端 - 前端判断返回的
message
类型,根据类型更新相应组件的状态,渲染组件数据
界面设计
目前RaspBoard有四个Dashboard组成,每一个Dashboard由不同的模块/组件组成:
- 基本:包含天气、时钟和树莓派的运行状态。
- 家庭:Home Assistant的设备信息。
- 主机:主机的运行状态。
- 粉丝:微博、BiliBili的粉丝数、订阅数。

为了能够自动显示,增加了一个Switch按钮,这样,激活按钮后每一个Tab将循环显示,实现方式也简单粗暴:
autoSwitchTabs() {
if (this.isSwitch && this.timer) {
clearInterval(this.timer);
} else {
this.timer = setInterval(() => {
if (this.active < 3) {
this.$store.dispatch('setActive', parseInt(this.active) + 1);
} else {
this.$store.dispatch('setActive', 0)
}
}, 5000);
}
this.$store.dispatch('setSwitch', !this.isSwitch);
},
信息获取
UI绘制完成,就该考虑如何获取这些数据了。天气和流量数据以及粉丝数都有接口,直接请求就可以得到这些数据:
# weibo fans
https://m.weibo.cn/api/container/getIndex?vmid=xxx
# bilibi fans
https://api.bilibili.com/x/relation/stat?containerid=xxx
Raspberry Pi
通过Python的os
模块,可以很方便的获取树莓派的硬件信息:
CPU
def getCPUtemperature():
return os.popen('/opt/vc/bin/vcgencmd measure_temp').read()[5:9]
def getCPUuse():
return(str(os.popen("top -n1 | awk '/Cpu\(s\):/ {print $2}'").readline().strip()))
RAM && Disk
def getDiskSpace():
return os.popen('df -h /|tail -n +2').readline().split()[1:5]
def getRAMinfo():
return os.popen('free|tail -n +2').readline().split()[1:4]
# RAM information
# Output is in kb, here I convert it in Mb for readability
RAM_stats = getRAMinfo()
RAM_total = round(int(RAM_stats[0]) / 1000, 1)
RAM_used = round(int(RAM_stats[1]) / 1000, 1)
RAM_free = round(int(RAM_stats[2]) / 1000, 1)
# Disk information
DISK_stats = getDiskSpace()
DISK_total = DISK_stats[0]
DISK_used = DISK_stats[1]
DISK_perc = DISK_stats[3]
System
def getUptime():
return os.popen("cat /proc/uptime| awk -F. '{run_days=$1 / 86400;run_hour=($1 % 86400)/3600;run_minute=($1 % 3600)/60;run_second=$1 % 60;printf(\"%dday%d:%d:%d\",run_days,run_hour,run_minute,run_second)}'").read()
def getIp():
return os.popen("ifconfig wlan0 | grep \"inet 192\" | awk '{ print $2}'").read()
PC
平时使用AIDA64来监听主机的设备信息,所以要获取主机的信息主要从AIDA64入手。
python_aida64库
AIDA64可以将设备信息写入到内存或者注册表中,通过读取内存或注册表就可以得到主机的设备信息,Github上找到了一份Python的代码,可以读取内存中的ADID64的数据。
那如何才能让其他设备访问主机中的数据呢?
为了可以在局域网的其他设备中请求数据,同时在主机使用tornado
开启了一个WEB服务用来暴露数据,RaspBoard请求这个接口就可以得到主机的状态信息。

开机自启
为了能保证做为一个应用程序在开机之后自动启动,使用pyinstaller
将这个WEB程序打包成了一个.exe文件,直接丢到启动项里,这样就方便了很多:
# -*- coding:utf-8 -*-
import tornado.web
import json
import os
import platform
import python_aida64
if platform.system() == "Windows":
import asyncio
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
class IndexHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header("Access-Control-Allow-Headers", "x-requested-with")
self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
def get(self):
data = python_aida64.getData()
self.write(json.dumps(data))
if __name__ == '__main__':
app = tornado.web.Application([
('/', IndexHandler),
])
app.listen(8080, address='0.0.0.0')
tornado.ioloop.IOLoop.current().start()
设备状态
当主机关机是RaspBoard应该暂时取消主机的设备请求,所以通过Home Assistant增加了判断主机是否在线的Sensor:
binary_sensor:
- platform: ping
name: hades_pc
host: 192.168.1.100
这样,一个桌面级的DashBoard就完工了,以至于还内心澎湃的拍了一个15s小视频:
🎬链接
配合上Home Assistant的自动化,就可以做到回到家自动打开RashBoard,离开家关闭RashBoard了!
最后
终于玩上了动森,开始了还房贷的生活,真的是太太太好玩了!任天堂真是_____!