|
|
<!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 '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 '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 '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> >> flask.<span class="hljs-built_in">log</span> <span class="hljs-number">2</span>>&<span class="hljs-number">1</span> &</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">() =></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> =></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>) =></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>) =></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> =></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>) =></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>) =></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> =></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> =></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> =></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> =></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> =></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> =></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> << <span class="hljs-number">24</span>) | (r << <span class="hljs-number">16</span>) | (g << <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 & <span class="hljs-number">0xff0000</span>) >> <span class="hljs-number">16</span>;
|
|
|
<span class="hljs-keyword">let</span> g = (value & <span class="hljs-number">0xff00</span>) >> <span class="hljs-number">8</span>;
|
|
|
<span class="hljs-keyword">let</span> b = (value & <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 - 2025 <a href="/">Hans362 '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 '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> |