博客搬遷至https://blog.wangjiegulu.comhtml
RSS訂閱:https://blog.wangjiegulu.com/feed.xmllinux
原文連接:https://blog.wangjiegulu.com/2018/09/26/private-smart-life-cloud-a--hack-tuya-smart-plug/android
本系列文章的目標是經過本身搭建的私有云
、IFTTT
、Slack
、Google Assistant
、塗鴉智能
、圖靈機器人
等等第三方服務,構建本身的「智能生活」基本的框架。ios
前段時間我在京東上購買了一個塗鴉智能的插座。塗鴉智能是一個把產品智能化的一個平臺,從軟件和硬件和雲端上面提供給廠商一個一站式人工智能物聯網的解決方案(確實不是塗鴉智能的廣告- -.)。因此嚴格來講應該是「鵲起」基於塗鴉解決方案所開發出來的一個產品,基於塗鴉提供的一切規範,全部軟硬雲端的開發規範均可以在官網無權限限制地查看到(https://docs.tuya.com/cn/)web
我首先下載了塗鴉的官方app:Smart Life,註冊登陸、智能配對插座,經過手機控制插座開關,一切順利,還支持設置定時開關和 Schedule,並且手機控制到插座的反應十分靈敏(看樣子應該使用了長連接)。算法
固然使用官方 app 顯然是沒法知足咱們需求,因此查看官方文檔,發現文檔中的 tuya.m.device.dp.publish 接口正好能夠知足咱們的需求。json
經過接入指南,咱們能夠知道接入方式提供了兩種:api
下面有列出通用的參數:session
參數名稱 | 參數類型 | 是否必須 | 是否簽名 | 參數描述 |
---|---|---|---|---|
a | String | 是 | 是 | API名稱 |
v | String | 是 | 是 | API接口版本 |
sid | String | 否 | 是 | 用戶登陸受權成功後,ATOP頒發給應用的用戶session |
time | String | 是 | 是 | 時間戳,格式爲數字,大小到秒非毫秒,時區爲標準時區,例如:1458010495。API服務端容許客戶端請求最大時間偏差爲540分鐘。 |
sign | String | 是 | 否 | API輸入參數簽名結果,簽名算法參照下面的介紹 |
clientId | String | 是 | 是 | 用戶的APPID(注各平臺不同如:ios、android、云云對接的id都不同) |
lang | String | 否 | 是 | APP的語言,如"en",「zh_cn」,錯誤信息根據語言自動翻譯 |
ttid | String | 否 | 是 | APP渠道或雲端渠道,如公司名,用於數據分析跟蹤 |
os | String | 是 | 是 | 手機操做系統,如"Android",「ios」,雲端能夠寫linux或寫公司名 |
上述咱們沒法拿到的是哪些呢?app
因此只要拿到 clientId,咱們就能
那怎麼去拿到 clientId 呢?反編譯試試(你們應該都知道怎麼作),class.dex 有點多,最後拿到了
8qp5cfk*******3mpmc3
(這個打碼了)g75ktcvsae8**********e95j738tawg
(一樣打碼)打開 Postman
,按照文檔 POST countryCode
和 mobile
能夠獲得 token:
{ "api":"tuya.m.user.mobile.token.get", "result":{ "exponent":"3", "pExponent":"...", "publicKey":"...", "token":"..." }, "status":"ok", "success":true }
{ "countryCode":"86", "mobile":"11745678923", "passwd":"根據獲取token接口返回的公鑰對 md5(明文密碼) 進行rsa加密", "token":"126bb7570dcae343980b0607e6b35084", "ifencrypt":1 }
根據登陸接口的文檔,密碼須要使用 gettoken 接口返回的公鑰對 md5以後的密碼進行 rsa 加密,apk 反編譯以後代碼能夠徹底看到,直接拷貝便可
登陸完以後,就能夠拿到具體的 sid (sessionId)了,有了 sessionId,就能夠去對插座進行下發指令。
一樣根據下發指令接口文檔:
{ "devId": "002yt001sf000000sfV3", "dps": { "1":1, "2":5 } }
能夠看到,POST 的數據除了 sid,還須要 devId 和 dps
dps 能夠參考這裏的文檔:https://fchelp.cloud.alipay.com/queryArticleContent.htm?tntInstId=WRNQWLCN&articleId=89429815&helpCode=SCE_00000019
咱們要控制插座開關的話,那就使用 {"1": true}
/ {"1": false}
便可
那 devId 呢?它表明我添加設備 id,那我怎麼知道我剛給在 Smart Life 上添加的插座 id 呢?抓包試試(你們應該都知道),經過抓包,咱們能夠很容易拿到全部你綁定的 devId。
至此,咱們能夠經過 http 來控制插座的開關了。
我爲本身的項目取名爲
Angelia
,安革利亞,古希臘神話人物之一,爲「消息女神」。
搭建 web 項目,新建 AngeliaController
:
@RestController @RequestMapping("/angelia") @Configuration class AngeliaController { // ... }
增長 Tuya 的配置文件:tuyaclient.properties
tuya.client.client_id=xxxxxx tuya.client.app_secret=xxxxxx tuya.client.ttid=xxxxxx tuya.client.v=1.0 tuya.client.base_url=https://a1.tuyacn.com/api.json # smart life app 登陸手機號 tuya.client.user_mobile=18511111111 tuya.client.user_country_code=86 tuya.client.user_rsa_encrypted_passwd=xxx # 設備信息 tuya.client.lang=en tuya.client.os=Android tuya.client.os_system=9 tuya.client.time_zone_id=Asia/Shanghai tuya.client.platform=Pixel tuya.client.sdk_version=2.6.4 tuya.client.app_version=3.4.3 tuya.client.app_rn_version=5.6 # 設備 id,模擬手機的 deviceId tuya.client.device_id=e507510a0288accd7****** tuya.client.imei=xxxxxx tuya.client.imsi=xxxxxx # 智能設備,能夠多個:別名|id|dpid,別名|id|dpid,別名|id|dpid # dpid: 1. 開關;4.rgb;5. 檔位;6. 溫度;15. 紅外數據 tuya.client.dev_ids=[\ {\ "aliasList": ["plug a", "plug 1", "插座a", "洗手間總電源"],\ "devId": "111222333",\ "dpId": "1"\ },\ {\ "aliasList": ["plug b", "plug 2", "插座b", "臥室總電源"],\ "devId": "12341234",\ "dpId": "1"\ },\ // ... ]
以上,除了剛剛的那些必要的參數之類,在這個配置文件裏面還增長了對全部設備的配置,每一個設備對應它的 dpId,devId,還有別名(控制的時候不可能直接說 「devId 爲 a1d2f32a1d2f32 的插座關掉」,而是說 「關掉插座1」 / 「關掉洗手間總電源」等等),每一個設備別名能夠有多個。
好了,回到 AngeliaController
,新增一個接口:
/** * 插座控制接口 */ @Autowired lateinit var tuyaClientService: TuyaClientService @Autowired lateinit var tuyaClientProperties: TuyaClientProperties @PostMapping("/control/plug") fun controlPlug(@RequestBody request: PlugRequestVo): JSONObject { val dev = tuyaClientProperties.findDev(request.alias) return try { if (null == dev) { JsonResult.error("Device named ${request.alias} is not found") } else { tuyaClientService.controlPlug(dev.devId, request.turnOn) JsonResult.success() } } catch (e: Exception) { JsonResult.error(e.message) } } data class PlugRequestVo( val alias: String, val turnOn: Boolean )
以上,該接口接收別名(alias)和開閉狀態(turnOn)兩個入參。首先,經過請求的別名去 properties 查找設備,若是找到,則經過 tuyaClientService
的 controlPlug
方法來下發指令,controlPlug
的實現就是剛剛上面說的 獲取登陸接口-登陸-下發指令
幾步。
構建、部署,經過 Postman 訪問 http://localhost:xxxxx/angelia/control/plug
, body 設置爲 {"alias": "臥室總電源", "turnOn": true}
,插座便可打開。