博多南ウェブサービスのblog

博多南ウェブサービスのサービス紹介

【Play Framework 3.0.x サンプル】Apache Pekko Receptionist(Cluster) を使って、複数サーバー間でのメッセージ送信したい

Play Framework 3.0.x (以下、Play) を始めたばかりの方向けに、サンプルを進めるうえで困ったところを共有する目的で書いています。

過去のPlay Framework 2.8.x とAkka Typed を使ったServer-Sent Events (以下、SSE) を、Apache Pekko (以下、Pekko) に変更したものです。

以下、目次

やりたいこと

Playインスタンス1 (localhost:9000) にアクセスしたクライアントが、サーバーより受け取るメッセージを、Playインスタンス2(localhost:9001) にアクセスしているクライアントも受け取ることができる

見た目

Play Pekko Server Sent Events Sample

設計のポイント

Receptionist は、ActorRef を登録、検索できる仕組みで、cluster(node が複数ある場合)もサポートしている。サポートというのは、コードは変更せずに設定だけで、local、clusterを切り替えられる。

今回の(も)、サーバー間のメッセージ送信を実現するため、Receptionist を利用している。

Server1
Sender
Sen...
Receptionist
Receptionist
伝搬
伝搬
Actor1(SourceRef)
Actor1(SourceRef)
Server2
Receptionist
Receptionist
Actor1(SourceRef)
Actor1(SourceRef)
Actor2(SourceRef)
Actor2(SourceRef)
Actor1
Act...
Actor2
Act...
Source1
Source1
Source2
Source2
Tell
Tell
Tell
Tell
Client1
Client1
Client2
Client2
Tell
Tell
Actor2(SourceRef)
Actor2(SourceRef)
Find and Tell
Find and Tell
Text is not SVG - cannot display

実装してみて

  • 前回同様、Receptionist に、SSE 用のSource[String, ActorRef[String]] にメッセージを送るためのActorRef[String] を直接登録すると、Cluster にしたときにうまくいかない
  • なので、ActorRef[String] にメッセージを送る(リレーする)だけのActor を作って、Receptionist に登録すると、うまくいった

Githubこちら

以上でした。

【Play Framework 3.0.x サンプル】レスポンスエラーコードの表示画面を変更したい

Play Framework 3.0.x (以下、Play) を始めたばかりの方向けに、サンプルを進めるうえで困ったところを共有する目的で書いています。

Play にて、エラーレスポンス(クライアント、サーバー)の表示画面を、変更したときのメモです。

以下、目次

背景

ドキュメント にあるとおり、Play では、

  • リクエストに対して、自動的にclient error を検出する
  • アプリでの例外スローに対して、自動的にserver error を処理する

そして、エラーページを生成する。

また、別のドキュメント にあるとおり、Play では、

  • 明示的な(つまり、Action としての)、NotFoundInternalServerError に対して、エラーページは生成せずに該当するエラーステータスを返す

方針

  • エラーレスポンス(クライアント、サーバー)を返す際に、ほかのページと統一感のある(つまり、ヘッダー、フッター、メニュー部分が共通の)ページを表示する
  • (可能なら)明示的なNotFound についても、ドキュメント にある、onClientError, onServerError を利用する

結果

ほかのページと統一感のあるページを表示する

自動生成のエラーページ => 独自のエラーページ に変更した。

これには、ドキュメントにあるとおり、 DefaultHttpErrorHandler を拡張する か、 HttpErrorHandler を実装する かで、実現できた。

どちらを使えばよいかは、ドキュメントを見ても理解できていない。

ので、判断基準として、書きやすい(または、コードを見たときに一覧性がある)もを選んだ(HttpErrorHandler を実装した。)

onClientError, onServerError を明示的なNotFound 等にも利用する

Action.async { Future.successful(...) } (のようなもの)に対して、NotFound 等を指定しているところに、HttpErrorHandler を呼び出す処理に変更した。

これは、なにがしたかったのかというと、

  • HttpErrorHandler を実装する」では、route が定義されていないパスにアクセスがあったときは、実装したerror handler が呼び出される
  • HttpErrorHandler を実装する」では、NotFound Action を明示的に指定したときは(つまり、route を定義しておいて、NotFound を返すときは)、実装したerror handler が呼び出されない

となっているものを、どちらも同じ処理にしたかったというもの。

具体的には、

class TestController @Inject() (
    (省略)
    mcc: MessagesControllerComponents
)(implicit
    (省略)
) extends MessagesAbstractController(mcc) {

  def test =
    Action.async { implicit request: Request[AnyContent] =>
      play.api.mvc.Results.NotFound("レスポンス 404")
    }
}

のような、NotFound を返している(これが、良いかどうかはわからない)個所を、

class TestController @Inject() (
    (省略)
    mcc: MessagesControllerComponents,
+    errorHandler: ErrorHandler
)(implicit
    (省略)
) extends MessagesAbstractController(mcc) {

  def test =
    Action.async { implicit request: Request[AnyContent] =>
-      play.api.mvc.Results.NotFound("レスポンス 404")
+      errorHandler.onClientError(request, 404, "レスポンス 404")
    }
}

のように、onClientError を呼び出すようにした

できてないところ

そもそも、Action として、NotFound, InternalServerError 等を返す設計が良いかの判断ができていない。 ただ、play.api.mvc.Results.NotFound() を用意しているということは、 そのような設計も想定しているのだろうけど。

参考にしたところ

以上でした。

Powershell Core 7.3 から、SendMessage() を呼び出して、背面にあるGoogle Play Games BETA の画面をスワイプ操作したい

やりたいこと

2023/5/3時点の、Google Play Games BETA のプレイ画面に対して、プレイ画面が他のウィンドウの下にあっても、スワイプ操作したい。

やったこと

Powershell Core 7.3 から、SendMessage 関数 を呼び出して、スワイプ操作をエミュレートしたメッセージを送信した。

### C#
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class Win32
{
    [DllImport("user32.dll")]
    static extern IntPtr FindWindow(
        string lpClassName,
        string lpWindowName
    );
    
    public static IntPtr FindWindowByName(string lpWindowName)
    {
        return FindWindow(null, lpWindowName);
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(
        IntPtr hWnd,
        IntPtr ProcessId
    );

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

    [DllImport("kernel32.dll")]
    static extern int GetCurrentThreadId();

    [DllImport("user32.dll")]
    static extern bool SetWindowPos(
        IntPtr hWnd,
        IntPtr hWndInsertAfter,
        int X,
        int Y,
        int cx,
        int cy,
        int uFlags
    );

    [DllImport("user32.dll")]
    static extern bool AttachThreadInput(
        int idAttach,
        int idAttachTo,
        bool fAttach
    );

    [DllImport("user32.dll")]
    static extern IntPtr SendMessage(
        IntPtr hWnd,
        uint Msg,
        int wParam,
        int lParam
    );

    static void SimulateBackGround(
        string lpWindowName,
        string simualtedTask,
        int x = 0,
        int y = 0,
        int movedX = 0,
        int movedY = 0
    )
    {
        var targetWnd = FindWindowByName(lpWindowName);
        var wakfuThreadID = GetWindowThreadProcessId(targetWnd, IntPtr.Zero);

        var foreWnd = GetForegroundWindow();
        var windowThreadProcessId = GetCurrentThreadId();
        AttachThreadInput(windowThreadProcessId, wakfuThreadID, true);

        int mousePos = (y << 16) | (x & 0xffff);
        int movedMousePos = (movedY << 16) | (movedX & 0xffff);

        const int WM_MOUSEMOVE = 0x0200;
        const int WM_LBUTTONDOWN = 0x0201;
        const int WM_LBUTTONUP = 0x0202;
        const int MK_LBUTTON = 0x0001;
        
        switch (simualtedTask)
        {
            case "tap":
                SendMessage(targetWnd, WM_MOUSEMOVE, 0, mousePos);
                SendMessage(targetWnd, WM_LBUTTONDOWN, MK_LBUTTON, mousePos);
        
                System.Threading.Thread.Sleep(50);
                
                SendMessage(targetWnd, WM_MOUSEMOVE, 0, mousePos);
                SendMessage(targetWnd, WM_LBUTTONUP, 0, mousePos);
                break;
            case "swipe":
                SendMessage(targetWnd, WM_MOUSEMOVE, 0, mousePos);
                SendMessage(targetWnd, WM_LBUTTONDOWN, MK_LBUTTON, mousePos);

                System.Threading.Thread.Sleep(100);

                SendMessage(targetWnd, WM_MOUSEMOVE, 0, movedMousePos);
                SendMessage(targetWnd, WM_LBUTTONDOWN, MK_LBUTTON, movedMousePos);

                System.Threading.Thread.Sleep(110);
                SendMessage(targetWnd, WM_LBUTTONUP, 0, movedMousePos);
                break;
            default:
                break;
        }

        const int SWP_NOSIZE = 0x0001;
        const int SWP_NOMOVE = 0x0002;
        const int SWP_SHOWWINDOW = 0x0040;
        SetWindowPos(foreWnd, -1, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
        SetWindowPos(foreWnd, -2, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
        
        AttachThreadInput(windowThreadProcessId, wakfuThreadID, false);
    }

    public static void TapBackGround(
        string lpWindowName,
        int x,
        int y
        )
    {
        SimulateBackGround(lpWindowName, "tap", x, y);      
    }    

    public static void SwipeBackGround(
        string lpWindowName,
        int x,
        int y,
        int movedX,
        int movedY
        )
    {
        SimulateBackGround(lpWindowName, "swipe", x, y, movedX, movedY);
    }
}
"@

### Powershell
Set-Variable WINDOW_TITLE -Option Constant -Value "操作したいアプリのウィンドウタイトル"
[int]$local:startXforSwipe = 500
[int]$local:startYforSwipe = 600
[int]$local:endXforSwipe = 1000
[int]$local:endYforSwipe = 600
[Win32]::SwipeBackGround(
                            ${WINDOW_TITLE},
                            ${startXforSwipe},
                            ${startYforSwipe},
                            ${endXforSwipe},
                            ${endYforSwipe}
                            )

# tap
# [int]$local:xforTap = 500
# [int]$local:yforTap = 600
# [Win32]::TapBackGround(
#                         ${WINDOW_TITLE},
#                         ${xforTap},
#                         ${yforTap}
#                         )

やっていて困ったこと

参考

Bing Search を使っているときにEnter key -> 送信を防ぐときのメモ

やりたいこと

Bing Search を利用しているとき、Enter key でメッセージが送信されるのを防ぎたいときのメモです。

つまり

  • Enter key -> テキストエリア内の改行
  • Shift key + Enter key -> メッセージ送信

です。

やったこと

以下のUserScript を、Edge で利用できるようにした。

これは、

を行うものです。

// ==UserScript==
// @name         Enter for new line and Shift + Enter for submit in Bing Chat
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Allows using Enter for new line and Shift + Enter for submit in Bing Chat text area
// @author       You
// @match        https://www.bing.com/search*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    // https://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea/6015906#6015906
    // and modified
    document.addEventListener('keydown', function(event) {
        // ここはBing Search チャット画面の要素が変更されたら更新しないといけないよ
        let cibSerpMain = document.querySelector(".cib-serp-main");
        let cibActionBarMain = cibSerpMain.shadowRoot.querySelector("#cib-action-bar-main");
        let divRoot = cibActionBarMain.shadowRoot.querySelector(".root");
        let mainContainter = divRoot.querySelector(".main-container");
        let inputContainter = mainContainter.querySelector(".input-container");
        let cibTextInput = inputContainter.querySelector("cib-text-input");
        let searchBoxForm = cibTextInput.shadowRoot.querySelector("#searchboxform");
        let textInput = searchBoxForm.querySelector(".text-input");
        let searchBox = textInput.querySelector("#searchbox");

        if (searchBox) {
            if (event.keyCode == 13 && event.shiftKey) {
                event.stopImmediatePropagation();
                const keyboardEvent = new KeyboardEvent('keydown', {
                    code: 'Enter',
                    key: 'Enter',
                    view: window,
                    bubbles: true
                });
                searchBox.dispatchEvent(keyboardEvent);
                searchBox.value = '';
            } else if (event.keyCode == 13) {
                event.stopImmediatePropagation();
            }
        }
    }, true);
})();

できなかったこと

Shift + Enter key でメッセージ送信後、テキストエリアに改行が1つ残る(クリア後、挿入される?)。消す方法がわからなかった。

参考

Bing Search を使っているときにチャット→検索タブに移動するのを防ぐときのメモ

やりたいこと

Bing Search を利用しているとき、マウスのスクロールを使うと、ふとしたタイミングでチャットタブから検索タブに移動してしまう場合がある。

これを防ぎたいときのメモです。

やったこと

reddit の質問と回答Disabling scroll to chat? にあるUserScript を、Edge で利用できるようにした。

具体的には、

  1. Tampermonkey をEdge にインストール
  2. Tampermonkey にて、新規スクリプト

    window.addEventListener("wheel", e=>{ if(e.target.className.includes("cib-serp-main")) e.stopPropagation(); });
    

    を登録

  3. チャット画面 にて、登録したUserScript が有効となっていることを確認
  4. マウスのスクロールを行っても、チャット→検索タブに移動しないことを確認

参考

UWSC を PowerShell Core 7.3 に置き換えたときのメモ

やりたいこと

  1. Windows11 で表示した Window (Window1)のうち、(最小化ではなく)背面に隠れている画面のキャプチャを取得(画像1)
  2. UWSC の ChkImgX で、取得した画像1とターゲット画像(画像2)を比較し、画像2が画像1の中にあればその位置(位置1)を取得
  3. UWSC で、取得した位置1をもとに、Windows1 の該当する箇所をクリック

といった、自動化をやりたい。

ただ、Window1 が背面にある場合、ChkImgX で画面のキャプチャが取得できない(真っ黒画面が取得される)ことが分かった(これは、Window1側の設定によるものなのかは不明。)

そこで、Powershell Core 7.3 + C++ (14?)でやってみたときのメモです。

やったこと

  1. 以下を実施するdll(独自dll ) を、C++ で作成
    1. Win32API の PrintWindow を呼び出して、(最小化ではなく)背面に隠れている画面のキャプチャを取得(画像1)
    2. OpenCV 4.7 で、取得した画像1とターゲット画像(画像2)を比較し、画像2が画像1の中にあればその位置(位置1)を取得
  2. Powershell から、独自dll を呼び出して、位置1を取得
  3. (今回対象の Window が Windows Subsystem for Android のアプリだったので)Powershell から、位置1をタップするコマンド(adb.exe ***)を実行

初めてのC++ステップ

C++ について、初めて触れる → 独自dll 作成までの各ステップ

  1. Vissual Studio 2022 をインストールする
  2. マイクロソフトのLearning を参考に、独自dll とそれを呼び出すコンソールアプリ(アプリ1)を作成する
  3. このタイミングで、独自dll のRelease バージョンビルド後、Powershell から呼び出せることを確認する(でないと、どこが悪いかわからなくなるので)
  4. デバッグ用として、独自dll からの結果をアプリ1で表示する方法、またはVisual Studio 2022 のデバッガを起動して変数の値を表示する方法を確認する
  5. PrintWindow 関数を呼び出すときに必要なお作法(GetDC でデバイスコンテキスト取得したら、処理完了後、プログラム上で責任もって解放する(ReleaseDC)等)を調べる
  6. PrintWindow 関数を呼び出して、画像1が取得できることを確認する(クリップボードに保存が簡単だった)
  7. 画像1を、OpenCV で使用するMat 型に変換し、変換できていることを確認する(画像ファイルに保存が簡単だった)
  8. OpenCV の matchTemplate 関数を使用して位置1を取得し、取得できていることを確認する(OpenCV のサンプルコードが参考になった)
  9. 独自dll のRelease バージョンビルド後、Powershell から呼び出せることを確認する

実際のコード

ChkImgCpp.h

#pragma once

#define CHKIMGCPP_API __declspec(dllexport)

#include <windows.h>
#include <string>

struct CHKIMGCPP_API ReturnPoint {
    int doneMatching; // 0: not done template matching for some errors, 1: done template matching
    int maxValInt; // maxVal of temaplate matching scaled 0 to 100
    int x;
    int y;
};

extern "C" CHKIMGCPP_API ReturnPoint __stdcall point_template_matched_bitmap(
    const wchar_t* class_name,
    const wchar_t* window_title,
    const wchar_t* bitmap_file_path
);

ChkImgCpp.cpp

#include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier

#include <windows.h>
#include <dwmapi.h>

#include <string>

#include "ChkImgCpp.h"
#include <opencv2/opencv.hpp>

/**
 *  WindowDCを解放するためのdeleter
 * https://setuna-kanata.hatenadiary.org/entry/20100713/1279039787
 */
struct dc_deleter_release
{
    void operator()(HDC hdc)const
    {
        ReleaseDC(hwnd, hdc);
    }

    dc_deleter_release(HWND hwnd) : hwnd(hwnd) {}

    HWND hwnd;
};
struct dc_deleter_delete
{
    void operator()(HDC hdc)const
    {
        DeleteDC(hdc);
    }

    dc_deleter_delete() {}
};
struct hbitmap_deleter_delete
{
    void operator()(HBITMAP hBitmap)const
    {
        DeleteObject(hBitmap);
    }

    hbitmap_deleter_delete() {}
};

// https://gist.github.com/siwa32/98704
std::string convString(const std::wstring& input)
{
    size_t i;
    char* buffer = new char[input.size() * MB_CUR_MAX + 1];
    wcstombs_s(&i, buffer, input.size() * MB_CUR_MAX + 1, input.c_str(), _TRUNCATE);
    std::string result = buffer;
    delete[] buffer;
    return result;
}

ReturnPoint __stdcall point_template_matched_bitmap(
    const wchar_t* class_name,
    const wchar_t* window_title,
    const wchar_t* bitmap_file_path
)
{
    ReturnPoint r{};
    r.doneMatching = 0; // 0: not done template matching for some errors, 1: done template matching

    HWND hwnd = FindWindow(class_name, window_title);
    if (hwnd == NULL)
    {
        return r;
    }

    RECT rect{};
    if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect)) != S_OK)
    {
        return r;
    }

    // ReleaseDC 等を呼ばなくてすむように、std::unique_ptr を利用
    // https://setuna-kanata.hatenadiary.org/entry/20100713/1279039787
    std::unique_ptr<HDC__, dc_deleter_release> hdc(::GetDC(hwnd), dc_deleter_release(hwnd));

    // Bitmap to cv::Mat
    // http://ckron.net/?p=205
    BITMAPINFO bmpInfo{};
    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfo.bmiHeader.biWidth = rect.right - rect.left;
    bmpInfo.bmiHeader.biHeight = rect.bottom - rect.top;
    bmpInfo.bmiHeader.biPlanes = 1;
    bmpInfo.bmiHeader.biBitCount = 32;
    bmpInfo.bmiHeader.biCompression = BI_RGB;

    LPDWORD lpPixel{};
    std::unique_ptr<HBITMAP__, hbitmap_deleter_delete>
        hBitMap(CreateDIBSection(hdc.get(), &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0), hbitmap_deleter_delete());
    if (hBitMap.get() != NULL)
    {
        // メモリDC
        std::unique_ptr<HDC__, dc_deleter_delete> hdcMem(CreateCompatibleDC(hdc.get()), dc_deleter_delete());

        SelectObject(hdcMem.get(), hBitMap.get());
        PrintWindow(hwnd, hdcMem.get(), PW_RENDERFULLCONTENT);
    }

    BITMAP bmp{};
    GetObject(hBitMap.get(), sizeof(BITMAP), &bmp);

    /*
   // for debugging bitmap data
   // http://www13.plala.or.jp/kymats/study/MULTIMEDIA/convert_dib_ddb.html
   if (OpenClipboard(hwnd))
   {
       HBITMAP hb;
       hb = CreateDIBitmap(hdc.get(), &bmpInfo.bmiHeader, CBM_INIT, lpPixel, &bmpInfo, DIB_RGB_COLORS);
       EmptyClipboard();
       SetClipboardData(CF_BITMAP, hb);
       CloseClipboard();
   }
   return r;
   */

    // PrintWindow で取得した画像は32bitカラーイメージだったので決め打ち
    cv::Mat revMat(bmp.bmHeight, bmp.bmWidth, CV_8UC4, (void*)bmp.bmBits);
    cv::Mat mat{};
    cv::flip(revMat, mat, 0);
    if (!mat.data)
    {
        return r;
    }

    // 比較ファイル
    cv::Mat target = cv::imread(convString(bitmap_file_path), 1);
    if (!target.data)
    {
        return r;
    }

    // template matching
    cv::Mat matBGR{};
    cv::cvtColor(mat, matBGR, cv::COLOR_BGRA2BGR);

    int result_cols = matBGR.cols - target.cols + 1;
    int result_rows = matBGR.rows - target.rows + 1;

    cv::Mat result{};
    result.create(result_rows, result_cols, CV_8UC3);

    cv::matchTemplate(matBGR, target, result, cv::TM_CCOEFF_NORMED);

    double minVal{};
    double maxVal{};
    cv::Point minLoc;
    cv::Point maxLoc;
    cv::Point matchLoc;

    minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
    matchLoc = maxLoc;

    r.doneMatching = 1; // 0: not done template matching for some errors, 1: done template matching
    r.maxValInt = maxVal * 100;
    r.x = matchLoc.x + target.cols / 2;
    r.y = matchLoc.y + target.rows / 2;

    /*
   //for debugging matched point
   cv::Mat img_display{};
   matBGR.copyTo(img_display);

   rectangle(img_display, matchLoc, cv::Point(matchLoc.x + target.cols, matchLoc.y + target.rows), cv::Scalar::all(0), 2, 8, 0);

   imshow("debugging matched point", img_display);
   cv::waitKey(0);
   //*/

    return r;
}

Powershell で呼び出す

# [ChkImgCpp]::point_template_matched_bitmap returns ReturnPoint for window (also class name) and bitmap_file_path.
# Return Point has
#  - int doneMatching; // 0: not done template matching for some errors, 1: done template matching
#  - int maxValInt; // maxVal of temaplate matching scaled 0 to 10000
#  - int x; // center x position of maxVal location
#  - int y; // center y position of maxVal location
$dllFilePathEscaped = (Join-Path -Path ${PSScriptRoot} -ChildPath "ChkImgCpp.dll").Replace('\', '\\')
$source = @"
using System.Runtime.InteropServices;
public struct ReturnPoint {
    public int doneMatching; // 0: not done template matching for some errors, 1: done template matching
    public int maxValInt; // maxVal of temaplate matching scaled 0 to 100
    public int x;
    public int y;
}
public static class ChkImgCpp
{
    [DllImport("${dllFilePathEscaped}", CharSet=CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    public static extern ReturnPoint point_template_matched_bitmap(
        System.String class_name,
        System.String window_title,
        System.String bitmap_file_path
    );
}
"@
Add-Type -TypeDefinition $source;

$local:class_name_of_window = "<ウィンドウの class name: UWSC で取得が簡単>"
$local:title_of_window = "<ウィンドウの タイトル>"
$local:bmp_relative_path_non_escaped = "<比較するbmp file (24bit) の相対パス>"

$local:bmpFilePathEscaped = (Join-Path -Path ${PSScriptRoot} -ChildPath $bmp_relative_path_non_escaped).Replace('\', '\\')
$returnPoint = [ChkImgCpp]::point_template_matched_bitmap($class_name_of_window, $title_of_window, $bmpFilePathEscaped)

Write-Output $returnPoint  

つまづいた点

  • dll、lib、exe の違いって何?
  • ヘッダファイル、ソースファイルって何?
  • dll ってどこにある?どこにおく?
  • ビルドすると何が出力される?dll だけ?lib、exe も?
  • C++ のstring って何?どうやって関数に渡すの?ポインタの概念はなんとなくわかるけど・・
  • GetDC のあとは必ずReleaseDC しろって、何かいい方法はないの?
  • Win32API のBITMAP 型って何?save(bmp, filePath) で保存じゃないの?

参考

メモ:IRKit のサービス終了に伴う、ファームウェアの書換

IRKit のサービスが終了したので、 サーバーに接続せずにローカルのみで使うよう、 Adrian さんの fork settings-led-cloud を、IRKit に書き込んでWifi のセットアップ、リモコン信号をIRKit から送信まで確認したときのメモです。

なお、この記事は私の環境では上手くいったということを参考までにお知らせするものです。

この記事を参考に、ご自分でファームウェアのアップデートされる方に生じた何らかの損失、損害等について、 私は一切責任も負うものではありません。

以下、目次

手順

  1. Adrian さんの step-by-step guide を参考に IRKit に settings-led-cloud バージョンのファームウェアを書き込み
  2. IRKit の ssid に接続し、wifi セットアップ用の Post リクエストを送信
  3. リモコン信号がIRKit から送信されていることを確認

手順を進めるうえではまったところ

Adrian さんの step-by-step guide を参考に IRKit に settings-led-cloud バージョンのファームウェアを書き込み

IRKit の ssid に接続し、wifi セットアップ用の Post リクエストを送信

wifi 情報を serialized したものを Post する必要があるが、javascript でどうやってするのかはリンク切れのよう

なので、bouzuya さんのnode-irkit を参考にさせてもらった。

javascript で serialized wifi 情報を出力するコードは以下のとおり。

"use strict";
// Original Source Code: http://jsdo.it/mash/keyserializer-test
// MIT License

// This Source Code is transpiled from https://github.com/bouzuya/node-irkit/blob/master/src/irkit-device-key-serializer.ts typescript
// to javascript, and add a little change.

// CRC-8-Dallas/Maxim
function crc8n(u8, crcinit) {
    if (u8 < 0x00 || u8 > 0xFF) {
        throw new Error('only input less than 1byte as number');
    }
    var crc = crcinit ^ u8;
    for (var i = 0; i < 8; i++) {
        var poly = (crc & 0x80) === 0x80;
        crc = (crc << 1) ^ (poly ? 0x31 : 0x00); // 0x31 = X^8+X^5+X^4+X^0
    }
    return crc & 0xFF;
}
// tslint:enable
function crc8s(data, size, crcinit) {
    var crc = crcinit;
    for (var i = 0; i < size; i++) {
        var u8 = i < data.length ? data.charCodeAt(i) : 0x00; // 0 filled
        crc = crc8n(u8, crc);
    }
    return crc;
}
function hex(s) {
    var ret = '';
    for (var i = 0; i < s.length; i++) {
        ret += s.charCodeAt(i).toString(16).toUpperCase();
    }
    return ret;
}
function serializeSecurity(security) {
    return security.toString();
}
function serializeSSID(ssid) {
    return hex(ssid);
}
function serializePassword(password) {
    return hex(password);
}
function serializeWEPPassword(password) {
    return serializePassword(hex(password));
}
function serializeDevicekey(devicekey) {
    return devicekey;
}
var Security;
(function (Security) {
    Security[Security["NONE"] = 0] = "NONE";
    Security[Security["WEP"] = 2] = "WEP";
    Security[Security["WPA_WPA2"] = 8] = "WPA_WPA2";
})(Security || (Security = {}));

function serializeCRC(obj) {
    var crc = 0x00;
    crc = crc8n(obj.security, crc);
    crc = crc8s(obj.ssid, 33, crc);
    crc = crc8s(obj.password, 64, crc);
    crc = crc8n(1, crc); // wifi_is_set
    crc = crc8n(0, crc); // wifi_was_valid
    crc = crc8s(obj.devicekey, 33, crc);
    return crc.toString(16).toUpperCase();
}
function serialize(_a) {
    var devicekey = _a.devicekey, password = _a.password, security = _a.security, ssid = _a.ssid;
    return [
        serializeSecurity(security),
        serializeSSID(ssid),
        security === Security.WEP
            ? serializeWEPPassword(password)
            : serializePassword(password),
        serializeDevicekey(devicekey),
        '2',
        '',
        '',
        '',
        '',
        '',
        serializeCRC({
            devicekey: devicekey,
            password: password,
            security: security,
            ssid: ssid
        })
    ].join('/');
}

// ここに自分のwifi 設定を入力する
let obj = {
    devicekey: "", // ブランクでOK
    password: "", // home wifi passward (not IRKit wifi password)
    security: 8, // WPA_WPA2:8, WEP: 2
    ssid: "" // wifi ssid (not IRKit wifi ssid)
}
//

let serializedObj = serialize(obj);
console.log({ serializedObj });

例えば、ssid: test_ssid, password: test_password, security: WPA_WPA2 の場合、 以下のようなPost リクエストになるはず。

curl -X POST -i "http://192.168.1.1/wifi" -H "X-Requested-With: curl" -d '8/746573745F73736964/746573745F70617373776F7264//2//////F9'

参考リンク