【Java】Dysonの家電をプログラム経由で操作したり室温を取得したりする
今回はDysonの空調家電、Pure Hot + Cool LinkをJavaから操作してみましょう。
テストに使う型番はHP03ですが他のDyson扇風機や加湿器などでも同じように操作できると予想されますので参考にしてみてください。
プログラムを実行する前にDyson家電をセットアップしておきましょう。
セットアップは前回の記事で紹介しています。
HP03はMQTTというプロトコルを使用します。
ライブラリを使えば楽なのですが、MQTTについて少しでも理解を深めたいので、あえて標準ライブラリのSocketクラスでセンサーデータ取得や操作を行います。
操作するためにはID、パスワードなどの情報が必要です。情報取得のためのツールは前回の記事で紹介しています。
準備ができたら処理を実行してみましょう。
やってることの流れ
・認証
・subscribeリクエスト(受信登録)
・publishリクエスト(家電操作やセンサー情報取得要求)
です。
プログラムはこんな感じになりました。
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.SocketChannel;
- import java.nio.charset.StandardCharsets;
- public class DysonTest {
- private static String clientId="test_001";//client idは自分で適当に設定しても動きます
- private static String userName="ユーザー名";//パケットキャプチャで解析が必要
- private static String password="パスワード";//パケットキャプチャで解析が必要
- private static String model="モデル番号";//パケットキャプチャで解析が必要(HP03は455)
- private static String host="IPアドレス";//ルータなどにアクセスして調べる
- private static int port=1883;//恐らく1883で問題なし
- public static void main(String[] args) throws InterruptedException {
- try (SocketChannel sock = SocketChannel.open(new InetSocketAddress(host, port))){
- //データ受信スレッド起動
- new Thread(()->{
- try {
- receive(sock);
- } catch (IOException e) {
- } catch (InterruptedException e) {
- }
- }).start();
- //認証データを送信
- sendAuthMsg(sock);
- Thread.sleep(500);
- //データ取得出来るようにするsubscribeリクエスト
- sendSubscribeMsg(sock);
- Thread.sleep(500);
- //現在のセンサーなどのデータをこちらに送りつけるように指示する
- sendCurrentStatusGetMsg(sock,(short)2);
- Thread.sleep(500);
- //電源をつける
- sendCtrlMsg(sock, "{\"fmod\": \"FAN\"}", (short)3);
- Thread.sleep(3000);
- //首振りON
- sendCtrlMsg(sock, "{\"oson\": \"ON\"}", (short)4);
- Thread.sleep(3000);
- //風量を最小に
- sendCtrlMsg(sock, "{\"fnsp\": \"0001\"}", (short)5);
- Thread.sleep(3000);
- //暖房ON
- sendCtrlMsg(sock, "{\"hmod\": \"HEAT\"}", (short)6);
- Thread.sleep(3000);
- //暖房OFF
- sendCtrlMsg(sock, "{\"hmod\": \"OFF\"}", (short)7);
- Thread.sleep(3000);
- //風量を最大に
- sendCtrlMsg(sock, "{\"fnsp\": \"0010\"}", (short)8);
- Thread.sleep(3000);
- //首振りOFF
- sendCtrlMsg(sock, "{\"oson\": \"OFF\"}", (short)9);
- Thread.sleep(3000);
- //電源を消す
- sendCtrlMsg(sock, "{\"fmod\": \"OFF\"}", (short)10);
- Thread.sleep(3000);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- private static void sendCtrlMsg(SocketChannel sock, String ctrlJson, short indentifer) throws IOException {
- ByteBuffer result = ByteBuffer.allocate(1024);
- //ヘッダー
- result.put((byte)0x32);
- //データ
- ByteBuffer data = ByteBuffer.allocate(1024);
- String topic = model+"/"+userName+"/command";
- //Topic サイズ
- data.putShort((short)topic.length());
- //Topic
- data.put(topic.getBytes("UTF-8"));
- //Identifier
- data.putShort((short)indentifer);
- //データ取得リクエストJSON
- String json = "{\"data\": "+ctrlJson+",\"mode-reason\": \"iot\",\"time\": \"2020-05-17T12:21:14Z\",\"msg\":\"STATE-SET\"}";
- data.put(json.getBytes("UTF-8"));
- //サイズ計算
- putSize(data, result);
- data.flip();
- result.put(data);
- result.flip();
- //データの送信
- sock.write(result);
- }
- //データ取得リクエスト
- private static void sendCurrentStatusGetMsg(SocketChannel sock, short indentifer) throws IOException {
- ByteBuffer result = ByteBuffer.allocate(1024);
- //ヘッダー
- result.put((byte)0x32);
- //データ
- ByteBuffer data = ByteBuffer.allocate(1024);
- String topic = model+"/"+userName+"/command";
- //Topic サイズ
- data.putShort((short)topic.length());
- //Topic
- data.put(topic.getBytes("UTF-8"));
- //Identifier
- data.putShort((short)indentifer);
- //データ取得リクエストJSON
- String json = "{\"mode-reason\":\"iot\",\"time\":\"2020-05-17T20:21:33Z\",\"msg\":\"REQUEST-CURRENT-STATE\"}";
- data.put(json.getBytes("UTF-8"));
- //サイズ計算
- putSize(data, result);
- data.flip();
- result.put(data);
- result.flip();
- //データの送信
- sock.write(result);
- }
- //データ取得できるようにするリクエスト
- private static void sendSubscribeMsg(SocketChannel sock) throws IOException {
- ByteBuffer result = ByteBuffer.allocate(1024);
- //ヘッダー
- result.put((byte)0x82);
- //データ
- ByteBuffer data = ByteBuffer.allocate(1024);
- //Identifier
- data.putShort((short)0x0001);
- String topic = model+"/"+userName+"/status/current";
- //Topic サイズ
- data.putShort((short)topic.length());
- //Topic
- data.put(topic.getBytes("UTF-8"));
- //Requested QoS
- data.put((byte)0x01);
- //サイズ計算
- putSize(data, result);
- data.flip();
- result.put(data);
- result.flip();
- //データの送信
- sock.write(result);
- }
- //認証
- private static void sendAuthMsg(SocketChannel sock) throws IOException {
- ByteBuffer result = ByteBuffer.allocate(1024);
- //ヘッダー
- result.put((byte)0x10);
- //データ
- ByteBuffer data = ByteBuffer.allocate(1024);
- //プロトコル名サイズ
- data.putShort((short)0x0004);
- //プロトコル名(MQTT)
- data.putInt(0x4d515454);
- //MQTTバージョンv3.1.1
- data.put((byte)0x04);
- //Connect Flags
- data.put((byte)0xc2);
- //KeepAlive
- data.putShort((short)0x003c);
- //クライアントID Length
- data.putShort((short)clientId.length());
- //クライアントID
- data.put(clientId.getBytes("UTF-8"));
- //ユーザー名 サイズ
- data.putShort((short)userName.length());
- //ユーザー名
- data.put(userName.getBytes("UTF-8"));
- //パスワード サイズ
- data.putShort((short)password.length());
- //パスワード
- data.put(password.getBytes("UTF-8"));
- //サイズ計算
- putSize(data, result);
- data.flip();
- result.put(data);
- result.flip();
- //データの送信
- sock.write(result);
- }
- //ヘッダ以外のサイズ計算
- private static void putSize(ByteBuffer src, ByteBuffer dest){
- int x = src.position();
- int digit = 0;
- do {
- digit = x % 128;
- x = x / 128;
- if (x > 0) digit = digit | 0x80;
- dest.put((byte)(digit&0x000000ff));
- }while(x>0);
- }
- //データ受信
- private static void receive(SocketChannel in) throws IOException, InterruptedException {
- ByteBuffer readData = ByteBuffer.allocate(4096);
- while(true) {
- readData.clear();
- int r = in.read(readData);
- readData.flip();
- if ( r == -1 ) {
- System.out.println("connection end.");
- break;
- }
- if ( (readData.get(0) & 0x30) == 0x30 ) {
- //dysonからのメッセージ受信
- //本当はレスポンスを返すべき
- }
- printByte(readData);
- }
- }
- //データの表示
- private static void printByte(ByteBuffer bb) throws UnsupportedEncodingException {
- System.out.println("--------------------------------------");
- for ( int i = 0;i < bb.limit();i++ ) {
- byte b = bb.get(i);
- String ss = Integer.toHexString(Byte.toUnsignedInt(b)).toUpperCase();
- if ( ss.length() < 2 ) ss ="0"+ss;
- System.out.print("0x"+ss+",");
- }
- System.out.println();
- System.out.println(StandardCharsets.UTF_8.decode(bb));
- System.out.println("--------------------------------------");
- }
- }
{“msg”:”ENVIRONMENTAL-CURRENT-SENSOR-DATA”,”time”:”2020-06-22T13:07:02.001Z”,”data”:{“tact”:”OFF”,”hact”:”OFF”,”pact”:”0003″,”vact”:”0001″,”sltm”:”OFF”}}
プログラム内には適当にコメント入れておいたので、MQTTの動きをちょっとでも理解したい場合は、参考にしてみてください。
その他、私が調べたパラメータ情報を載せておきます。
操作時の送信パラメータ情報
設定名 | 設定値 | 説明 |
---|---|---|
fmod | FAN,OFF,AUTO | ファンのオンオフ制御 |
hmod | HEAT,OFF | 暖房機能のオンオフ |
hmax | 設定温度=(摂氏温度+273)*10 | 暖房機能の設定温度 |
fnsp | 0001~0010 | ファンの速度 |
oson | ON,OFF | 首振り |
ffoc | ON,OFF | ディフューズドモード |
nmod | ON,OFF | ナイトモード |
sltm | [分単位の数値],OFF | スリープタイマー |
センサーデータ取得時のパラメータ情報
センサー名 | 説明 |
---|---|
tact | 室温([(摂氏温度+273)*10]を表す数値) |
hact | 湿度 |
pact | 空気汚染レベル(低いほどきれい) |
これで、MQTTについて少しは理解できたかなぁ。。。
ディスカッション
コメント一覧
まだ、コメントがありません