Matrix 首页推荐

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。

文章代表作者个人观点,少数派仅对标题和排版略作修改。


没有 Siri 的 iOS 不是一个好 OS。

之前提到关于 网络唤醒WoL 这块的内容,也列举了很多唤醒工具,目的是更好地对远端计算机进行有效管控。

这次更进一步,解放双手,尝试让「语音助手」执行唤醒操作。

关联阅读:不用开机键,你的 Windows 也能随时就绪

实现思路

原理亦不复杂,我手头有台 iPad Air,Siri 充当语音助手,调用「捷径」来发送开机指令。

「捷径」本身不支持 WoL 协议,但能发起 HTTP 请求,因此我们需要一个能让不同协议进行沟通/转换的装置/程序,我们简称「协议转换器」,处理 HTTP 请求并发送 WoL 唤醒信号。

我们知道有 Home Assistant 这类平台专门用于构建智能家居交互核心,但对于单一简单需求来说,未免显得过于笨重,所以我决定自己实现一个。如果你已经部署某些类似的物联网核心,可以尝试对其进行扩展或集成。

因此我的总实现流程如下:

  • 本地局域网已有一台树莓派 2,作为程序运行的载体
  • HTTP 与 WoL 同属网络协议,在此我们使用 Golang 来构建上图的两大功能模块
  • HTTP 请求先不设计得非常复杂,够用就行1http://192.168.1.4:40080/wakeup
  • 得益于 HTTP 协议的泛用性,亦可以通过各种浏览器方便地调用,唤醒远程计算机

配置过程

iOS

在「新建捷径 > 获取 URL 内容」中选择 HTTP GET 方法:

接下来开启「互联网、麦克风及语音识别」:

嘿 Siri,「快捷指令名称」:

捷径名称可任意发挥,喊得顺就行。

协议转换器

程序入口 main.go 的具体配置如下:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

func handler_status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "0")
}

func handler_wakeup(w http.ResponseWriter, r *http.Request) {
	sendwol()
	fmt.Fprintf(w, "0")
}

func main() {
	http.HandleFunc("/", handler)

	// Service Status Check
	http.HandleFunc("/status", handler_status)

	// WOL
	http.HandleFunc("/wakeup", handler_wakeup)

	// HTTP
	err := http.ListenAndServe(":8000", nil)
	log.Fatal(err)
}

GitHub 扒拉两个函数实现 WoL 协议包 wol.go:

package main

import (
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"net"
)

func sendwol() {
  // 目标MAC地址与指定网卡接口
	const hw  = "e0db55a893e6"
	const nic = "eth0"

	macHex, err := hex.DecodeString(hw)
	if err != nil {
		fmt.Printf("MAC: [%s] decode fail.\n", hw)
		return
	}

	// 广播MAC地址 FF:FF:FF:FF:FF:FF
	var bcast = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
	var buff bytes.Buffer
	buff.Write(bcast)
	for i := 0; i < 16; i++ {
		buff.Write(macHex)
	}

	// 获得唤醒魔包
	mp := buff.Bytes()
	if len(mp) != 102 {
		fmt.Printf("MAC: [%s] length too short.\n", hw)
		return
	}

	sendMagicPacket(mp, nic)
}

// 向指定网卡发送唤醒魔包
func sendMagicPacket(mp []byte, nic string) {
	sender := net.UDPAddr{}
	if len(nic) != 0 {
		ip, err := interfaceIPv4ByName(nic)
		if err != nil {
			fmt.Printf("网卡[%s]错误: %s", nic, err)
			return
		}

		sender.IP = ip
	}

	target := net.UDPAddr{
		IP: net.IPv4bcast,
	}
	conn, err := net.DialUDP("udp", &sender, &target)
	if err != nil {
		fmt.Printf("创建UDP错误:%v", err)
		return
	}
	defer func() {
		_ = conn.Close()
	}()

	_, err = conn.Write(mp)
	if err != nil {
		fmt.Printf("魔包发送失败[%s]", err)
	} else {
		fmt.Printf("魔包发送成功\n")
	}
}

// 通过网卡名称获取该网卡绑定的IPv4
func interfaceIPv4ByName(nic string) (net.IP, error) {
	inter, err := net.InterfaceByName(nic)
	if err != nil {
		return nil, err
	}

	// 检查网卡是否正在工作
	if (inter.Flags & net.FlagUp) == 0 {
		return nil, errors.New("网卡未工作")
	}

	addrs, err := inter.Addrs()
	if err != nil {
		return nil, err
	}

	for _, addr := range addrs {
		if ip, ok := addr.(*net.IPNet); ok {
			if ipv4 := ip.IP.To4(); ipv4 != nil {
				return ipv4, nil
			}
		}
	}

	return nil, errors.New("该网卡没有IPv4地址")
}

安装 Golang & 编译 & 试运行

apt-get install golang && go run main.go wol.go

正式运行

程序托管

为了保证程序长时间稳定运行,请个「保姆」,由 Systemd 对「协议转换器」进行托管

vim /etc/systemd/system/assist.service

[Unit]
Description = Assistant Service
[Service]
ExecStart = /usr/local/bin/assist
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

生成二进制文件 & 存放于合适位置

go build main.go wol.go & cp main /usr/local/bin/assist

启动

systemctl daemon-reload & systemctl start assist

后续还可在此基础上进行扩展,集成「miIO」协议,自由操控物联网设备。

最后来看看效果:

参考链接:

> 下载少数派 客户端 、关注 少数派公众号 ,了解更妙的数字生活 🍃

> 想申请成为少数派作者?冲!