从某种程度上来说,我是一个懒人,智能音箱在很大程度上满足了本人“懒”的需求。无奈的事,依旧有很多电器无法接入智能音箱的生态,比如服务器。

关于服务器的远程开关,尝试了很多方案,演化大致如下:从开始的WOL,到后来的ipmi,以及本文的homebridge方案。

严格来说,homebridge是一个转接平台,能作为一个主控让无法直接支持homekit的设备接入apple的生态,大致如下图。

+--------+ +------------+ +---------+
| iphone |-----| Homebridge |-----| Homekit |
+--------+ +------------+ +---------+

本文并不涉及homekit API以及IPMI协议细节的分析,只分享方案。

插件配置

homebridge对接server的方案需要依赖一个插件homebridge-http-switch,当然读者要是有时间,完全可以自己定制。这个插件主要功能就是将power up、power down、power status这3个行为转换为Http请求,无非就是把服务器当做一个开关,相关homebridge的配置如下:

"accessories": [
{
"accessory": "HTTP-SWITCH",
"name": "服务器",
"switchType": "stateful",
"statusPattern": "{\"succuess\":true,\"data\":{\"status\":\"on\"}}",
"onUrl": "http://10.0.0.3:65123/v1/power/up",
"offUrl": "http://10.0.0.3:65123/v1/power/soft",
"statusUrl": "http://10.0.0.3:65123/v1/power/status"
}
]

如果你想要用siri进行控制,那么建议你把name设置为中文,否则siri的智障会让你崩溃。statusPattern主要就是用来判断status API返回的结果是否表示on这个状态。

ipmi API服务

读到这里,大家应该已经能够猜到还需要一个API服务控制服务器的开关,具体的思路是将请求转换为ipmi命令。关于API服务,我是用gin开发的,为了节省不必要的时间开销,直接使用了vmware的ipmitool封装——goipmi,这个没有文档,大概看下代码就行了,附上我使用的,大致如下:

func doPowerStatus() *IpmiResult {
cl, err := ipmi.NewClient(&conn)
if err != nil {
return newIpmiResult(err)
}
defer cl.Close()
res := &ipmi.ChassisStatusResponse{}
req := &ipmi.Request{
NetworkFunction: ipmi.NetworkFunctionChassis,
Command: ipmi.CommandChassisStatus,
Data: &ipmi.ChassisStatusRequest{},
}
err = cl.Send(req, res)
if err != nil {
return newIpmiResult(err)
}
power := IpmiPowerStatus {}
if res.IsSystemPowerOn() {
power.Status = "on"
} else {
power.Status = "off"
}
return newIpmiResult(&power)
}
func doPowerOperator(op string) *IpmiResult {
cl, err := ipmi.NewClient(&conn)
if err != nil {
return newIpmiResult(err)
}
defer cl.Close()
var cmd ipmi.ChassisControl
switch op {
case "up":
cmd = ipmi.ControlPowerUp
case "down":
cmd = ipmi.ControlPowerDown
case "reset":
cmd = ipmi.ControlPowerHardReset
case "cycle":
cmd = ipmi.ControlPowerCycle
case "soft":
cmd = ipmi.ControlPowerAcpiSoft
}
res := &ipmi.ChassisControlResponse{}
req := &ipmi.Request{
NetworkFunction: ipmi.NetworkFunctionChassis,
Command: ipmi.CommandChassisControl,
Data: &ipmi.ChassisControlRequest{cmd},
}
err = cl.Send(req, res)
if err != nil {
return newIpmiResult(err)
}
if res.CompletionCode != ipmi.CommandCompleted {
return newIpmiResult(
errors.New(res.CompletionCode.Error()))
}
return newIpmiResult(nil)

}点击开启会调用命令ipmitool power up,等同于按了开机按钮;

点击关闭会调用命令ipmitool power soft,发送acpi down event,让os进入关机流程;

获取状态会调用命令ipmitool power status。

ipmitool的使用方法请参考manual。

总结

可以看到,添加设备并重启homebridge后,在home app里面会出现一个开关的图案,由于提供了power status请求,因此这个服务是无状态的,每次打开app的时候,均会产生请求获取服务器的电源状态。

接入homekit之后的另一个好处就是,可以使用siri或者homepod进行控制了,真的是懒人必备。目前,goipmi没有对于sensor命令的封装,暂时无法实现更加完善的监控功能,先不折腾了。

对于没有bmc的主板,先别沮丧,我也有方案:power up - 使用WOL,网卡通常是支持的

power down - 直接remote ssh shutdown

power status - ping或者arping

这套方案的最大缺陷是,开发板是单点,要是挂了,就会无法控制homebridge下挂载的设备。

Enjoy coding!