使用siri控制服务器开关的方案

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

关于服务器的远程开关,尝试了很多方案,演化大致如下:从开始的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!

说两句: