152 lines
43 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta http-equiv="x-dns-prefetch-control" content="on"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><meta name="renderer" content="webkit"><meta name="force-rendering" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="HandheldFriendly" content="True"><meta name="mobile-web-app-capable" content="yes"><link rel="shortcut icon" href="https://hans362-img.oss.0vv0.top/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="https://hans362-img.oss.0vv0.top/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="https://hans362-img.oss.0vv0.top/favicon-32x32.png"><link rel="apple-touch-icon" sizes="180x180" href="https://hans362-img.oss.0vv0.top/apple-touch-icon.png"><link rel="mask-icon" href="https://hans362-img.oss.0vv0.top/safari-pinned-tab.svg"><title>「一块钢板的重生」——7年前的小米4还能干什么 | Hans362 &#39;s Blog</title><meta name="keywords" content="小米4, 智能家庭, HASS, 小爱同学, 红外, Termux, Hans362"><meta name="description" content="随着家里接入米家生态链的智能家庭设备越来越多,我已经习惯于使唤房间里的小爱音箱帮我完成各种开关操作。就在前几天,我甚至都忘记了房间里的电风扇不是智能的,对着小爱同学张口就来,最后还得自己去开。那么对于这样的非智能家电,是否能够将其接入到智能家居的生态链中呢?答案是肯定的,比如我房间里的这台风扇支持红外遥控,只需要加钱买个米家生态链的红外万能遥控器就解决了。 然而目前市面上一台红外万能遥控器价格在几"><meta property="og:type" content="article"><meta property="og:title" content="「一块钢板的重生」——7年前的小米4还能干什么"><meta property="og:url" content="https://blog.hans362.cn/post/rebirth-of-xiaomi-4/"><meta property="og:site_name" content="Hans362 &#39;s Blog"><meta property="og:description" content="随着家里接入米家生态链的智能家庭设备越来越多,我已经习惯于使唤房间里的小爱音箱帮我完成各种开关操作。就在前几天,我甚至都忘记了房间里的电风扇不是智能的,对着小爱同学张口就来,最后还得自己去开。那么对于这样的非智能家电,是否能够将其接入到智能家居的生态链中呢?答案是肯定的,比如我房间里的这台风扇支持红外遥控,只需要加钱买个米家生态链的红外万能遥控器就解决了。 然而目前市面上一台红外万能遥控器价格在几"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://hans362-img.oss.0vv0.top/2021/07/31/30615583.jpg?width=1920"><meta property="article:published_time" content="2021-07-31T10:23:00.000Z"><meta property="article:modified_time" content="2025-04-11T10:35:15.358Z"><meta property="article:author" content="Hans362"><meta property="article:tag" content="小米4"><meta property="article:tag" content="智能家庭"><meta property="article:tag" content="HASS"><meta property="article:tag" content="小爱同学"><meta property="article:tag" content="红外"><meta property="article:tag" content="Termux"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="https://hans362-img.oss.0vv0.top/2021/07/31/30615583.jpg?width=1920"><link rel="stylesheet" href="/css/style/main.css"><link rel="stylesheet" id="hl-default-theme" href="https://blog.hans362.cn/npm/highlight.js@10.1.2/styles/atom-one-light.css" media="none"><link rel="stylesheet" id="hl-dark-theme" href="https://blog.hans362.cn/npm/highlight.js@10.1.2/styles/atom-one-dark.css" media="none"><script src="/js/darkmode.js"></script><link rel="dns-prefetch" href="https://analytics.0vv0.top"><link rel="preconnect" href="https://hans362-img.oss.0vv0.top"><meta name="generator" content="Hexo 7.1.1"><link rel="alternate" href="/atom.xml" title="Hans362 's Blog" type="application/atom+xml"></head><body><div class="app-shell-loader">加载中...</div><div class="container" tabindex="-1"><header><div class="header__left"><a href="/" class="button"><span class="logo__text">Hans362 &#39;s Blog</span></a></div><div class="header__right"><div class="navbar__menus"><a href="/" class="button"><div class="navbar-menu">首页</div></a><a href="/archives/" class="button"><div class="navbar-menu">归档</div></a><a href="/tags/" class="button"><div class="navbar-menu">标签</div></a><a href="/bangumi/" class="button"><div class="navbar-menu">追番</div></a><a href="/links/" class="button"><div class="navbar-menu">友链</div></a><a href="/about/" class="button"><div class="navbar-menu">关于</div></a><a href="/atom.xml" class="button"><div class="navbar-menu">RSS</div></a></div><a href="/search/" class="button"><div id="btn-search"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="24" height="24" fill="currentColor" stroke="currentColor" stroke-width="32"><path d="M192 448c0-141.152 114.848-256 256-256s256 114.848 256 256-114.848 256-256 256-256-114.848-256-256z m710.624 409.376l-206.88-206.88A318.784 318.784 0 0 0 768 448c0-176.736-143.264-320-320-320S128 271.264 128 448s143.264 320 320 320a318.784 318.784 0 0 0 202.496-72.256l206.88 206.88 45.248-45.248z"></path></svg></div></a><a href="javaScript:void(0);" rel="external nofollow noreferrer" class="button"><div id="btn-toggle-dark"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></div></a><a href="#" class="button" id="b2t" aria-label="回到顶部" title="回到顶部"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="32" height="32"><path d="M233.376 722.752L278.624 768 512 534.624 745.376 768l45.248-45.248L512 444.128zM192 352h640V288H192z" fill="currentColor"></path></svg> </a><a class="dropdown-icon button" tabindex="0"><div id="btn-dropdown"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="24" height="24" fill="none" stroke="currentColor" stroke-width="0.7" stroke-linecap="round" stroke-linejoin="round"><path fill="currentColor" d="M3.314,4.8h13.372c0.41,0,0.743-0.333,0.743-0.743c0-0.41-0.333-0.743-0.743-0.743H3.314c-0.41,0-0.743,0.333-0.743,0.743C2.571,4.467,2.904,4.8,3.314,4.8z M16.686,15.2H3.314c-0.41,0-0.743,0.333-0.743,0.743s0.333,0.743,0.743,0.743h13.372c0.41,0,0.743-0.333,0.743-0.743S17.096,15.2,16.686,15.2z M16.686,9.257H3.314c-0.41,0-0.743,0.333-0.743,0.743s0.333,0.743,0.743,0.743h13.372c0.41,0,0.743-0.333,0.743-0.743S17.096,9.257,16.686,9.257z"></path></svg></div></a><div class="dropdown-menus" id="dropdown-menus"><a href="/" class="dropdown-menu button">首页</a> <a href="/archives/" class="dropdown-menu button">归档</a> <a href="/tags/" class="dropdown-menu button">标签</a> <a href="/bangumi/" class="dropdown-menu button">追番</a> <a href="/links/" class="dropdown-menu button">友链</a> <a href="/about/" class="dropdown-menu button">关于</a> <a href="/atom.xml" class="dropdown-menu button">RSS</a></div></div></header><cover></cover><main><div class="post-content"><div class="post-title"><h1 class="post-title__text">「一块钢板的重生」——7年前的小米4还能干什么</h1><div class="post-title__meta"><a href="/archives/2021/07/" class="post-meta__date button">2021-07-31</a> <span class="separate-dot"></span> <a href="/categories/%E6%8A%80%E6%9C%AF%E5%90%91/" class="button"><span class="post-meta__cats">技术向</span></a><style>.post-meta__pv{color:var(--t-l);visibility:hidden;opacity:0;transition:.2s}</style><span class="separate-dot"></span> <span class="post-meta__pv"></span></div></div><aside class="post-side"><div class="post-side__toc"><div class="toc-title">文章目录</div><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C"><span class="toc-text">准备工作</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%98%E8%BA%AB%E7%BA%A2%E5%A4%96%E9%81%A5%E6%8E%A7"><span class="toc-text">变身红外遥控</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%8E%A5%E5%85%A5%E7%B1%B3%E5%AE%B6%E7%94%9F%E6%80%81%E9%93%BE"><span class="toc-text">接入米家生态链</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9B%B4%E5%A4%9A%E7%8E%A9%E6%B3%95"><span class="toc-text">更多玩法</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li></ol></div></aside><a class="btn-toc button" id="btn-toc" tabindex="0"><svg viewBox="0 0 1024 1024" width="32" height="32" xmlns="http://www.w3.org/2000/svg"><path d="M128 256h64V192H128zM320 256h576V192H320zM128 544h64v-64H128zM320 544h576v-64H320zM128 832h64v-64H128zM320 832h576v-64H320z" fill="currentColor"></path></svg></a><div class="toc-menus" id="toc-menus"><div class="toc-title">文章目录</div><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C"><span class="toc-text">准备工作</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%98%E8%BA%AB%E7%BA%A2%E5%A4%96%E9%81%A5%E6%8E%A7"><span class="toc-text">变身红外遥控</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%8E%A5%E5%85%A5%E7%B1%B3%E5%AE%B6%E7%94%9F%E6%80%81%E9%93%BE"><span class="toc-text">接入米家生态链</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9B%B4%E5%A4%9A%E7%8E%A9%E6%B3%95"><span class="toc-text">更多玩法</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li></ol></div><article class="post post__with-toc card"><div class="post__header"><img alt="Cover Image" class="lazy" src="https://hans362-img.oss.0vv0.top/2021/07/31/30615583.jpg?width=1920" srcset="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEXMzMyWlpYU2uzLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==" data-srcset="https://hans362-img.oss.0vv0.top/2021/07/31/30615583.jpg?width=1920"><div class="post__expire" id="post-expired-notify"><p><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style="fill:#f5a623;stroke:#f5a623"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg> 本文最后更新于 <span id="expire-date"></span> 天前,文中部分描述可能已经过时。</p></div><script>(()=>{var e=Date.parse("2021-07-31"),t=(new Date).getTime(),t=Math.floor((t-e)/864e5);120<=t&&(document.querySelectorAll("#expire-date")[0].innerHTML=t,document.querySelectorAll("#post-expired-notify")[0].style.display="block")})()</script></div><div class="post__content"><html><head><script>var meting_api="https://api-v2.hans362.cn/vip/?server=:server&type=:type&id=:id&r=:r"</script><script class="meting-secondary-script-marker" src="/js/Meting.min.js"></script></head><body><p>随着家里接入米家生态链的智能家庭设备越来越多,我已经习惯于使唤房间里的小爱音箱帮我完成各种开关操作。就在前几天,我甚至都忘记了房间里的电风扇不是智能的,对着小爱同学张口就来,最后还得自己去开。那么对于这样的非智能家电,是否能够将其接入到智能家居的生态链中呢?答案是肯定的,比如我房间里的这台风扇支持红外遥控,只需要加钱买个米家生态链的红外万能遥控器就解决了。</p><p>然而目前市面上一台红外万能遥控器价格在几十块到一百多块不等贫穷使我我不得不思索另一种替代方案。看到抽屉里的小米4以及机身顶部的红外发射器我突然有了灵感。这台小米4是我初中的时候用的上了高中换了手机以后就放在抽屉里没动过了算下来已经是7年前的老古董了。虽然这样一台手机在今天干啥都有那么点卡顿而且无论是官方的还是第三方的 ROM 都已经停止支持了但是当一个万能遥控绰绰有余。因此这篇文章就来谈谈我是如何让7年前的小米4重获新生的。</p><span id="more"></span><h2 id="准备工作"><a class="markdownIt-Anchor" href="#准备工作"></a> 准备工作</h2><p>首先既然要干这么有意思的事情,当然受不住 MIUI 条条框框的束缚,所以第一件事就是刷入第三方 Recovery我选择的是 TWRP以及第三方 ROM我选择的是 Lineage OS。由于年代久远Lineage OS 也停止了对于小米4的支持因此我 Google 了一下,在 XDA 论坛上找到了一个比较新的 Unofficial 版本,刷入后各项功能都正常。接着又刷入了 OpenGApps 以便于使用 Google Play 商店安装应用。</p><p>因为我并不会安卓开发,所以我选择了 Termux。在使用 Google Play 安装了 Termux 本体以及 Termux-API配置好 openssh-server 后,这台手机就变身为一块 ARM 开发版。通过 Termux-API 可以调用手机的各项功能,当然其中也就包括红外发射。</p><p>由于红外发射需要红外遥控码,不同品牌、同一品牌不同型号的设备的红外遥控码都大概率是不一样的,而厂家一般也不会公开这些信息,这时就需要一个维护好的红外码库,从中查询自己的电器遥控器上每个按钮对应的红外遥控码。虽然现在网上一搜就能看到很多这种码库,但基本上全是要收费的。如果不想要花钱就只能自己买红外接收器,逐个按键录入遥控码,这无疑又是一笔额外开销。</p><p>好在几年前有人维护过一个开源的红外码库叫做 IRext但是由于一些原因估计是动了谁的蛋糕这个码库的官网关闭了文档下线了GitHub 仓库也删除了,虽然有相关的 Fork 但是 Readme 里面的链接和文档都打不开了。好在 IRext 的 API 服务器其实还是偷偷开着的,只是因为没有文档不知道如何使用。我也是很懵地研究了很久,最后终于摸索清楚了。考虑到这个码库关闭的原因,我在本文中就不指明了,留下一点线索供需要的人参考。</p><ul><li>官网和 API 文档虽然已经无法访问,但是互联网档案馆的 Wayback Machine 有过 Snapshot借助它可以穿梭时空。</li><li>API 需要 Credentials 鉴权后才能使用,在 GitHub 上搜一搜使用了 IRext 的项目有惊喜。</li><li>注意仔细阅读 API 文档中有关「按键映射」的部分这里详细展示了按键编号与功能的对应关系不要理所当然地认为X号键一定有功能否则你就会像我一样试了半天发现不管用因为X号键不对应遥控器上的任何一个按键。</li><li>最后获得的红外遥控码应该是一串形如<code>[1250,340,340,1250,340,1250,1250,1250,340,1250,...]</code>的编码。</li></ul><h2 id="变身红外遥控"><a class="markdownIt-Anchor" href="#变身红外遥控"></a> 变身红外遥控</h2><p>在这一步我将展示如何利用这台小米4将我房间里的风扇接入 HomeAssistant。</p><p>确保手机连接了 WiFi安装好了 Termux 及 Termux-API通过 SSH 连接到手机上的 Termux。调用<code>termux-infrared-transmit</code>指令以了解红外发射功能的使用方法。</p><p>调用如下的命令以测试红外发射,<code>-f</code>指定了发射的频率一般的红外遥控器频率为38kHz因此指定频率为<code>38000</code>Hz。后面的第二个参数即红外遥控码。如果命令执行完毕被控设备正常响应则没有问题。</p><pre><code class="hljs apache"><span class="hljs-attribute">termux</span>-infrared-transmit -f <span class="hljs-number">38000</span> <span class="hljs-number">1250</span>,<span class="hljs-number">340</span>,<span class="hljs-number">340</span>,<span class="hljs-number">1250</span>,<span class="hljs-number">340</span>,<span class="hljs-number">1250</span>,<span class="hljs-number">1250</span>,<span class="hljs-number">1250</span>,<span class="hljs-number">340</span>,<span class="hljs-number">1250</span>,...</code></pre><p>接着为了让局域网里运行在软路由上的 HomeAssistant 能够控制该设备,我用 Python 和 Flask 写了一个 API 接口。先在 Termux 中安装好 Python3然后编写<code>api.py</code></p><pre><code class="hljs python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask,request
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> os
app=Flask(__name__)
<span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">"/airmate_fan_power"</span>,methods=[<span class="hljs-string">"GET"</span>]</span>)</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">airmate_fan_power</span>():
return_dict= {<span class="hljs-string">'return_code'</span>: <span class="hljs-string">'200'</span>, <span class="hljs-string">'return_info'</span>: <span class="hljs-string">'ok'</span>}
os.system(<span class="hljs-string">"termux-infrared-transmit -f 38000 1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,430,1250,430,1250,430,1250,430,1250,1250,7430,1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,430,1250,430,1250,430,1250,430,1250,1250,7430,1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,430,1250,430,1250,430,1250,430,1250,1250,7430"</span>)
<span class="hljs-keyword">return</span> json.dumps(return_dict, ensure_ascii=<span class="hljs-literal">False</span>)
<span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">"/airmate_fan_rotate"</span>,methods=[<span class="hljs-string">"GET"</span>]</span>)</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">airmate_fan_rotate</span>():
return_dict= {<span class="hljs-string">'return_code'</span>: <span class="hljs-string">'200'</span>, <span class="hljs-string">'return_info'</span>: <span class="hljs-string">'ok'</span>}
os.system(<span class="hljs-string">"termux-infrared-transmit -f 38000 1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,1250,430,430,1250,430,1250,430,1250,430,8250,1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,1250,430,430,1250,430,1250,430,1250,430,8250,1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,1250,430,430,1250,430,1250,430,1250,430,8250"</span>)
<span class="hljs-keyword">return</span> json.dumps(return_dict, ensure_ascii=<span class="hljs-literal">False</span>)
<span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">"/airmate_fan_wind"</span>,methods=[<span class="hljs-string">"GET"</span>]</span>)</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">airmate_fan_wind</span>():
return_dict= {<span class="hljs-string">'return_code'</span>: <span class="hljs-string">'200'</span>, <span class="hljs-string">'return_info'</span>: <span class="hljs-string">'ok'</span>}
os.system(<span class="hljs-string">"termux-infrared-transmit -f 38000 1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,430,1250,430,1250,430,1250,1250,430,430,8250,1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,430,1250,430,1250,430,1250,1250,430,430,8250,1250,430,1250,430,430,1250,1250,430,1250,430,430,1250,430,1250,430,1250,430,1250,430,1250,1250,430,430,8250"</span>)
<span class="hljs-keyword">return</span> json.dumps(return_dict, ensure_ascii=<span class="hljs-literal">False</span>)
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
app.run(debug=<span class="hljs-literal">True</span>, host=<span class="hljs-string">"0.0.0.0"</span>)</code></pre><p>(需要注意的是本段代码实现的 API 功能没有进行任何的鉴权,因为是开放在家里内网上的,只要家里内网不被打穿相对而言比较安全,切勿开放在公网上)</p><p>通过下面的指令使其在后台运行。</p><pre><code class="hljs vim">nohup <span class="hljs-keyword">python</span> api.<span class="hljs-keyword">py</span> &gt;&gt; flask.<span class="hljs-built_in">log</span> <span class="hljs-number">2</span>&gt;&amp;<span class="hljs-number">1</span> &amp;</code></pre><p>打开局域网内任何设备的浏览器,访问 <code>http://MI4-IP:5000/airmate_fan_power</code>,其中<code>MI4-IP</code>为手机的局域网 IP 地址,如果风扇开了/关了,说明成功。</p><p>最后通过给 HomeAssistant 的 <code>configuration.yaml</code> 添加配置以注册设备。</p><pre><code class="hljs yaml"><span class="hljs-attr">shell_command:</span>
<span class="hljs-attr">airmate_fan_power:</span> <span class="hljs-string">curl</span> <span class="hljs-string">http://192.168.1.225:5000/airmate_fan_power</span>
<span class="hljs-attr">airmate_fan_rotate:</span> <span class="hljs-string">curl</span> <span class="hljs-string">http://192.168.1.225:5000/airmate_fan_rotate</span>
<span class="hljs-attr">airmate_fan_wind:</span> <span class="hljs-string">curl</span> <span class="hljs-string">http://192.168.1.225:5000/airmate_fan_wind</span>
<span class="hljs-attr">script:</span>
<span class="hljs-attr">airmate_fan_power:</span>
<span class="hljs-attr">alias:</span> <span class="hljs-string">"卧室的艾美特风扇-电源"</span>
<span class="hljs-attr">icon:</span> <span class="hljs-string">"mdi:fan"</span>
<span class="hljs-attr">sequence:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">shell_command.airmate_fan_power</span>
<span class="hljs-attr">airmate_fan_rotate:</span>
<span class="hljs-attr">alias:</span> <span class="hljs-string">"卧室的艾美特风扇-摆风"</span>
<span class="hljs-attr">icon:</span> <span class="hljs-string">"mdi:fan"</span>
<span class="hljs-attr">sequence:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">shell_command.airmate_fan_rotate</span>
<span class="hljs-attr">airmate_fan_wind:</span>
<span class="hljs-attr">alias:</span> <span class="hljs-string">"卧室的艾美特风扇-风速"</span>
<span class="hljs-attr">icon:</span> <span class="hljs-string">"mdi:fan"</span>
<span class="hljs-attr">sequence:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">shell_command.airmate_fan_wind</span></code></pre><p>打开 HomeAssistant 可见设备,可以点击「运行」进行测试。如果配置了 HomeKit在 iOS 的「家庭」应用中也可以看到设备。</p><p><img src="https://hans362-img.oss.0vv0.top/2021/07/31/68197305.jpg?width=1920" class="lazy" data-srcset="https://hans362-img.oss.0vv0.top/2021/07/31/68197305.jpg?width=1920" srcset="/loading.gif" alt=""></p><p><img src="https://hans362-img.oss.0vv0.top/2021/07/31/38474986.jpg?width=1920" class="lazy" data-srcset="https://hans362-img.oss.0vv0.top/2021/07/31/38474986.jpg?width=1920" srcset="/loading.gif" alt=""></p><h2 id="接入米家生态链"><a class="markdownIt-Anchor" href="#接入米家生态链"></a> 接入米家生态链</h2><p>至此貌似还没有解决最初的问题,即能够通过小爱同学来控制风扇。由于注册小爱开放平台貌似需要把 HomeAssistant 暴露在公网,再加上各种实名认证、审核太过麻烦,我决定曲线救国。</p><p>众所周知「米家」APP 中有一个「其他平台设备」的功能,可以添加第三方平台的设备,其中「点灯科技」吸引了我的兴趣。在阅读了点灯科技 Blinker 的文档后,我发现这个办法可行。</p><p>由于 Blinker 推荐的 SDK 是 Typescript 的,在弱小的小米 4 上跑这玩意简直要命(试过,几个小时后 node 进程因为内存不足被杀了),好在有软路由,我选择丢到软路由上去跑。</p><p>参照 Blinker 的文档注册设备、配置环境、安装 SDK 的过程这里不赘述了,可以自行搜索,选择 Broker 的时候注意选择阿里云,选择点灯科技将不支持小爱控制。安装完后改写 SDK 目录下 <code>example/miot/example_miot_light.ts</code>,加入控制语句。</p><pre><code class="hljs typescript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">BlinkerDevice</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../lib/blinker'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Miot</span>, <span class="hljs-variable constant_">VA_TYPE</span>, <span class="hljs-variable constant_">MI_LIGHT_MODE</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../lib/voice-assistant'</span>;
<span class="hljs-keyword">const</span> { exec } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'child_process'</span>);
<span class="hljs-keyword">let</span> device = <span class="hljs-keyword">new</span> <span class="hljs-title class_">BlinkerDevice</span>(<span class="hljs-string">'/* Your Secret */'</span>);
<span class="hljs-keyword">let</span> miot = device.<span class="hljs-title function_">addVoiceAssistant</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Miot</span>(<span class="hljs-variable constant_">VA_TYPE</span>.<span class="hljs-property">LIGHT</span>));
device.<span class="hljs-title function_">ready</span>().<span class="hljs-title function_">then</span>(<span class="hljs-function">() =&gt;</span> {
miot.<span class="hljs-property">powerChange</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
<span class="hljs-keyword">switch</span> (message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">pState</span>) {
<span class="hljs-keyword">case</span> <span class="hljs-string">"true"</span>:
message.<span class="hljs-title function_">power</span>(<span class="hljs-string">"on"</span>).<span class="hljs-title function_">update</span>();
<span class="hljs-title function_">exec</span>(<span class="hljs-string">'curl http://192.168.1.225:5000/airmate_fan_power'</span>, <span class="hljs-function">(<span class="hljs-params">err, stdout, stderr</span>) =&gt;</span> {});
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-string">"false"</span>:
message.<span class="hljs-title function_">power</span>(<span class="hljs-string">"off"</span>).<span class="hljs-title function_">update</span>();
<span class="hljs-title function_">exec</span>(<span class="hljs-string">'curl http://192.168.1.225:5000/airmate_fan_power'</span>, <span class="hljs-function">(<span class="hljs-params">err, stdout, stderr</span>) =&gt;</span> {});
<span class="hljs-keyword">break</span>;
<span class="hljs-attr">default</span>:
<span class="hljs-keyword">break</span>;
}
})
miot.<span class="hljs-property">modeChange</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
<span class="hljs-keyword">switch</span> (message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">mode</span>) {
<span class="hljs-keyword">case</span> <span class="hljs-variable constant_">MI_LIGHT_MODE</span>.<span class="hljs-property">DAY</span>:
<span class="hljs-title function_">exec</span>(<span class="hljs-string">'curl http://192.168.1.225:5000/airmate_fan_rotate'</span>, <span class="hljs-function">(<span class="hljs-params">err, stdout, stderr</span>) =&gt;</span> {});
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-variable constant_">MI_LIGHT_MODE</span>.<span class="hljs-property">NIGHT</span>:
<span class="hljs-title function_">exec</span>(<span class="hljs-string">'curl http://192.168.1.225:5000/airmate_fan_wind'</span>, <span class="hljs-function">(<span class="hljs-params">err, stdout, stderr</span>) =&gt;</span> {});
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-variable constant_">MI_LIGHT_MODE</span>.<span class="hljs-property">COLOR</span>:
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-variable constant_">MI_LIGHT_MODE</span>.<span class="hljs-property">WARMTH</span>:
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-variable constant_">MI_LIGHT_MODE</span>.<span class="hljs-property">TV</span>:
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-variable constant_">MI_LIGHT_MODE</span>.<span class="hljs-property">READING</span>:
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-variable constant_">MI_LIGHT_MODE</span>.<span class="hljs-property">COMPUTER</span>:
<span class="hljs-keyword">break</span>;
<span class="hljs-attr">default</span>:
<span class="hljs-keyword">break</span>;
}
message.<span class="hljs-title function_">mode</span>(message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">mode</span>).<span class="hljs-title function_">update</span>();
})
miot.<span class="hljs-property">colorChange</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'RGB:'</span>, <span class="hljs-title function_">int2rgb</span>(<span class="hljs-title class_">Number</span>(message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">col</span>)));
message.<span class="hljs-title function_">color</span>(message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">col</span>).<span class="hljs-title function_">update</span>();
})
miot.<span class="hljs-property">colorTempChange</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(message);
message.<span class="hljs-title function_">colorTemp</span>(<span class="hljs-number">255</span>).<span class="hljs-title function_">update</span>();
})
<span class="hljs-keyword">let</span> brightness = <span class="hljs-number">50</span>;
miot.<span class="hljs-property">brightnessChange</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">bright</span> != <span class="hljs-string">'undefined'</span>) {
brightness = <span class="hljs-title class_">Number</span>(message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">bright</span>)
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">upBright</span> != <span class="hljs-string">'undefined'</span>) {
brightness = brightness + <span class="hljs-title class_">Number</span>(message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">upBright</span>)
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">downBright</span> != <span class="hljs-string">'undefined'</span>) {
brightness = brightness - <span class="hljs-title class_">Number</span>(message.<span class="hljs-property">data</span>.<span class="hljs-property">set</span>.<span class="hljs-property">downBright</span>)
}
message.<span class="hljs-title function_">brightness</span>(brightness).<span class="hljs-title function_">update</span>();
})
miot.<span class="hljs-property">stateQuery</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
message.<span class="hljs-title function_">power</span>(<span class="hljs-string">'on'</span>).<span class="hljs-title function_">update</span>()
})
device.<span class="hljs-property">dataRead</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'otherData:'</span>, message);
})
device.<span class="hljs-property">builtinSwitch</span>.<span class="hljs-property">change</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">message</span> =&gt;</span> {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'builtinSwitch:'</span>, message);
device.<span class="hljs-property">builtinSwitch</span>.<span class="hljs-title function_">setState</span>(<span class="hljs-title function_">turnSwitch</span>()).<span class="hljs-title function_">update</span>();
})
})
<span class="hljs-keyword">function</span> <span class="hljs-title function_">rgb2int</span>(<span class="hljs-params">r: <span class="hljs-built_in">number</span>, g: <span class="hljs-built_in">number</span>, b: <span class="hljs-built_in">number</span></span>) {
<span class="hljs-keyword">return</span> ((<span class="hljs-number">0xFF</span> &lt;&lt; <span class="hljs-number">24</span>) | (r &lt;&lt; <span class="hljs-number">16</span>) | (g &lt;&lt; <span class="hljs-number">8</span>) | b)
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">int2rgb</span>(<span class="hljs-params">value: <span class="hljs-built_in">number</span></span>) {
<span class="hljs-keyword">let</span> r = (value &amp; <span class="hljs-number">0xff0000</span>) &gt;&gt; <span class="hljs-number">16</span>;
<span class="hljs-keyword">let</span> g = (value &amp; <span class="hljs-number">0xff00</span>) &gt;&gt; <span class="hljs-number">8</span>;
<span class="hljs-keyword">let</span> b = (value &amp; <span class="hljs-number">0xff</span>);
<span class="hljs-keyword">return</span> [r, g, b]
}
<span class="hljs-keyword">let</span> switchState = <span class="hljs-literal">false</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">turnSwitch</span>(<span class="hljs-params"></span>) {
switchState = !switchState
device.<span class="hljs-title function_">log</span>(<span class="hljs-string">"切换设备状态为"</span> + (switchState ? <span class="hljs-string">'on'</span> : <span class="hljs-string">'off'</span>))
<span class="hljs-keyword">return</span> switchState ? <span class="hljs-string">'on'</span> : <span class="hljs-string">'off'</span>
}</code></pre><p>通过 <code>ts-node example/miot/example_miot_light.ts</code> 让其跑起来就可以了。</p><p>细心的你可能发现了,要接入的不是风扇吗,怎么用的是灯的 SDK要怪就怪 Blinker 提供的 SDK 根本就是个半成品,风扇 SDK 还没写呢,只能先拿灯凑合用。只要把设备名称修改成「风扇」,小爱同学就听得懂了。至于风速、摆风功能我把它们绑定在了日光模式和夜灯模式上,利用小爱训练计划可以重定向指令,比如当我说“让风扇摇头”的时候执行“设置风扇为日光模式”就可以了。</p><p>最后在米家中绑定点灯科技并同步设备,来一句“小爱同学,打开风扇”,正常的话风扇就开咯。</p><h2 id="更多玩法"><a class="markdownIt-Anchor" href="#更多玩法"></a> 更多玩法</h2><p>使用<code>termux-sensor -l</code>可以获得手机上都有哪些传感器我惊讶地发现小米4竟然有货真价实的气压传感器并不是通过 GPS 海拔数据倒推出来的于是搭建了一个气象监测站每5分钟提交一次数据至 Firebase 实时数据库,并使用 Apache Echarts 和 Jquery 制作了数据展示页面。</p><p><a target="_blank" rel="noopener" href="https://lab.hans362.cn/weather">https://lab.hans362.cn/weather</a></p><p>当然<code>termux-sensor</code>还告诉我有温度传感器,但这明显是 CPU 的温度传感器,测的并不是环境温度,就没有什么利用价值了。</p><p>另外有条件可以购入一张物联卡插入手机中,这样家里 WiFi 断开的时候也可以持续完成数据采集和上报。</p><h2 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h2><p>就这样小米4结束了它作为手机的使命却在2021年成为了智能家庭与传统家电沟通的桥梁。</p><p>本文所提及的思路和方法适用于多数带有遥控功能的安卓手机,遥控对象可以是任何家电(只要有遥控码),欢迎尝试呀。</p></body></html></div><div class="license"><div class="license-title">「一块钢板的重生」——7年前的小米4还能干什么</div><div class="license-link"><a href="https://blog.hans362.cn/post/rebirth-of-xiaomi-4/">https://blog.hans362.cn/post/rebirth-of-xiaomi-4/</a></div><div class="license-meta"><div class="license-meta-item"><div class="license-meta-title">本文作者</div><div class="license-meta-text">Hans362</div></div><div class="license-meta-item"><div class="license-meta-title">最后更新</div><div class="license-meta-text">2021-07-31</div></div><div class="license-meta-item"><div class="license-meta-title">许可协议</div><div class="license-meta-text"><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh" rel="nofollow noopener noreferrer" target="_blank">CC BY-NC-SA 4.0</a></div></div></div><div>转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!</div></div><div class="post-footer__cats"><a href="/categories/%E6%8A%80%E6%9C%AF%E5%90%91/" class="post-cats__link button">技术向</a><a href="/tags/%E5%B0%8F%E7%B1%B34/" class="post-tags__link button"># 小米4</a><a href="/tags/%E6%99%BA%E8%83%BD%E5%AE%B6%E5%BA%AD/" class="post-tags__link button"># 智能家庭</a><a href="/tags/HASS/" class="post-tags__link button"># HASS</a><a href="/tags/%E5%B0%8F%E7%88%B1%E5%90%8C%E5%AD%A6/" class="post-tags__link button"># 小爱同学</a><a href="/tags/%E7%BA%A2%E5%A4%96/" class="post-tags__link button"># 红外</a><a href="/tags/Termux/" class="post-tags__link button"># Termux</a></div></article><div class="nav"><div class="nav__prev"><a href="/post/2021-csp-s-1/" class="nav__link"><div><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M589.088 790.624L310.464 512l278.624-278.624 45.248 45.248L400.96 512l233.376 233.376z" fill="#808080"></path></svg></div><div><div class="nav__label">上一篇</div><div class="nav__title">2021 CSP-S 初赛游记</div></div></a></div><div class="nav__next"><a href="/post/weekly-23/" class="nav__link"><div><div class="nav__label">下一篇</div><div class="nav__title">周记#23 - 六七月那些事</div></div><div><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M434.944 790.624l-45.248-45.248L623.04 512l-233.376-233.376 45.248-45.248L713.568 512z" fill="#808080"></path></svg></div></a></div></div><div class="post__sponsers card"><div class="sponser-label">喜欢这篇文章吗?考虑支持一下作者吧~</div><a class="sponser-button button" href="https://afdian.net/@hans362" rel="external nofollow noreferrer" target="_blank" data-type="afdian">爱发电</a> <a class="sponser-button button" data-type="alipay">支付宝<img class="sponser-qrcode" src="https://hans362-img.oss.0vv0.top/2021/08/05/68281340.jpg"></a></div><div class="post__comments post__with-toc card" id="comment"><h4>评论</h4><div id="disqus_thread">您所在的地区可能无法访问 Disqus 评论系统,请切换网络环境再尝试。</div></div></div></main><footer><p class="footer-copyright">Copyright © 2017&nbsp;-&nbsp;2025 <a href="/">Hans362 &#39;s Blog</a></p><p>Powered by <a href="https://hexo.io" target="_blank">Hexo</a> | Theme - <a href="https://github.com/ChrAlpha/hexo-theme-cards" target="_blank">Cards</a></p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-8746554831230893" data-ad-slot="6356225601" data-ad-format="auto" data-full-width-responsive="true"></ins><script>(adsbygoogle=window.adsbygoogle||[]).push({})</script></footer></div><script defer src="https://blog.hans362.cn/npm/vanilla-lazyload@17.8.3/dist/lazyload.min.js"></script><script>window.lazyLoadOptions={elements_selector:".lazy"}</script><script async defer data-website-id="5d181692-8a81-4c20-a282-cee87a6b90ef" src="https://analytics.0vv0.top/vue.js"></script><script src="/js/pageviews.js"></script><link rel="stylesheet" href="https://blog.hans362.cn/npm/katex@0.16.0/dist/katex.min.css" crossorigin="anonymous"><script>function loadComment(){let n,e;(n=document.createElement("script")).src="https://blog.hans362.cn/js/disqus.js",document.body.appendChild(n),n.onload=()=>{new DisqusJS({shortname:"hans362-s-blog",siteName:"Hans362 &#39;s Blog",api:"https://api-v3.hans362.cn/",apikey:"8Z1UVT4UOk22yNyk9MhpqQ0FLb27Hb1bpV066b4v9zOFie0GQ6VCoJ9TJwoGlCVF",admin:"hans362",identifier:"post/rebirth-of-xiaomi-4/",url:"https://blog.hans362.cn/post/rebirth-of-xiaomi-4/",nesting:"4"})},(e=document.createElement("link")).rel="stylesheet",e.href="https://blog.hans362.cn/css/disqusjs.css",document.head.appendChild(e)}var runningOnBrowser="undefined"!=typeof window,isBot=runningOnBrowser&&!("onscroll"in window)||"undefined"!=typeof navigator&&/(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent),supportsIntersectionObserver=runningOnBrowser&&"IntersectionObserver"in window;setTimeout(function(){var e;!isBot&&supportsIntersectionObserver?(e=new IntersectionObserver(function(n){n[0].isIntersecting&&(loadComment(),e.disconnect())},{threshold:[0]})).observe(document.getElementById("comment")):loadComment()},1)</script></body></html>