インフラ・デバイス

SwitchBotの気温と湿度を
BLEでアプリに連携する

この記事は約21分で読めます。
記事内に広告が含まれています。
スポンサーリンク

こんにちは、まっさん(@Tera_Msaki)です。

この記事は Androidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


SwitchBotから気温と湿度をBLE通信でアプリに連携する機能を実装する

意外かも知れませんが、
スマホには温度と湿度のセンサーをもっていません。
アプリで温度と湿度を扱う必要がある場合、
BLEで通信できるSwitchBotをおススメします♪

ポイント

SwitchBot温湿度計には高精度の温度センサーと湿度センサーを搭載しています。
本体の液晶画面に表示するほか、
BLE(BluetoothLowEnergy)を使って、外部機器に気温と湿度をハンドリングすることが可能です。
また、
SwitchBot温湿度計は、現在の気温と湿度をBLEアドバタイズに含めてデータ送信しています。
今回は、
SwitchBotから送信されているBLEアドバタイズにある気温と湿度をアプリに連携する機能を、Bluetoothライブラリを使用して実装したいと思います。

GPS走行記録アプリ(Archive)は、SwitchBot温湿度計から気温と湿度を取得しています。

BLEコントローラ

Bluetoothデバイスと通信する場合、マニフェストファイルへの権限指定が必要です。
さらに Android12以降では、アプリ側でユーザー承認をリクエストする必要があります。
マニフェストファイルへの権限指定と権限チェックの実装については、以下の参考記事を参照ください。

BLEコントローラクラス

BLEコントローラクラスでは、BLE MACBLEアドバタイズのタイプを指定して、受信するBLEアドバタイズのフィルタ処理を行い、目的のサービスデータを取得します。
スキャン(受信待ち状態)処理では、
Handlerを使用して、タイムアウト処理を実装します。
また、インタフェースを実装して、受信したサービスデータの受け渡しを行います。

:
public class BluetoothLowEnergyController {
    private static final long           SCAN_PERIOD = 30000;    // スキャンタイムアウト(30秒)
    private final Handler               handler;
    private final BluetoothAdapter      bluetoothAdapter;
    private final BleScanCallback       bleScanCallback;
    private final BluetoothLeScanner    bluetoothLeScanner;
    private OnChangeListener            onChangeListener;
    private final Set<ScanResult>       results = new HashSet<>();
    private final List<ScanResult>      batchScanResults = new ArrayList<>();
    private byte[]                      scanData = new byte[]{0};
    private Runnable                    runnable;
    private String                      macAddress;
    private Long                        updated;
    private long                        timer;
    private Byte                        type = null;
    private final Context               context;
    // コンストラクタ
    public BluetoothLowEnergyController(Context context) {
        this.context = context;
        this.handler = new Handler(Looper.getMainLooper());
        this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
        this.bleScanCallback = new BleScanCallback();
    }
    // インタフェース
    public interface OnChangeListener {
        void onValueChanged(byte[] value);
    }
    public void setOnChangeListener(final BluetoothLowEnergyController.OnChangeListener onChangeListener) {
        this.onChangeListener = onChangeListener;
    }
    @SuppressLint("MissingPermission")
    public void scan() {
        timer = System.currentTimeMillis();
        runnable = new Runnable() {
            @Override
            public void run() {
                if (results.size() == 0 && System.currentTimeMillis() > timer + SCAN_PERIOD ) {
                    handler.removeCallbacks(runnable);
                    // BLEスキャン停止
                    bluetoothLeScanner.stopScan(bleScanCallback);
                }
                handler.postDelayed(this, SCAN_PERIOD);
            }
        };
        bleScanCallback.clear();
        // BLEスキャン開始
        bluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), bleScanCallback);
        handler.post(runnable);
    }
    @SuppressLint("MissingPermission")
    public void pause() {
        handler.removeCallbacks(runnable);
        bluetoothLeScanner.stopScan(bleScanCallback);
        bleScanCallback.clear();
    }
    private List<ScanFilter> buildScanFilters() {
        List<ScanFilter> scanFilters = new ArrayList<>();
        ScanFilter.Builder builder = new ScanFilter.Builder();
        builder.setDeviceAddress(macAddress);
        scanFilters.add(builder.build());
        return scanFilters;
    }
    private ScanSettings buildScanSettings() {
        ScanSettings.Builder builder = new ScanSettings.Builder();
        builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
        return builder.build();
    }
    private class BleScanCallback extends ScanCallback {
        @SuppressLint("MissingPermission")
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
                results.add(result);
                BluetoothDevice bluetoothDevice = result.getDevice();
                scanData = result.getScanRecord().getBytes();
                updated = System.currentTimeMillis();
                if (type != null) {
                    int length;
                    int position = 0;
                    byte newType;
                    while (position < scanData.length) {
                        length = scanData[position];
                        if (length == 0) { break; }
                        position++;
                        newType = scanData[position];
                        if (newType == type) {
                            byte[] value = Arrays.copyOfRange(scanData, position + 1, position + length);
                            // 受信データ通知
                            onChangeListener.onValueChanged(value);
                        }
                        position = position + length;
                    }
                } else {
                    handler.removeCallbacks(runnable);
                    //BLEスキャン停止
                    bluetoothLeScanner.stopScan(bleScanCallback);
                }
            }
        }
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            batchScanResults.addAll(results);
        }
        @Override
        public void onScanFailed(int errorCode) {
        }
        synchronized void clear() {
            results.clear();
            batchScanResults.clear();
            Arrays.fill(scanData,(byte)0);
        }
    }
    // Android端末のBluetooth機能の有効化判定
    public boolean requestBluetoothFeature() {
        return bluetoothAdapter.isEnabled();
    }
    // デバイスのMACアドレス設定 //
    public void setMacAddress(String mac) { macAddress = (mac.length() > 0 && !mac.equals("none") ? mac : "00:00:00:00:00:00"); }
    // BLEアドバタイズパケット取得 //
    public byte[] getScanData() { return scanData; }
    // BLEアドバタイズパケット取得時刻 //
    public String getUpdated() {
        SimpleDateFormat HHmm = new SimpleDateFormat("HH:mm", Locale.JAPAN);
        Date date = new Date(updated);
        return HHmm.format(date);
    }
    // タイプ設定 //
    public void setType(Byte newType) { type = newType; }
    // 値取得 //
    public Set<ScanResult> getScanResult() { return results; }
    public List<ScanResult> getBatchScanResults() { return batchScanResults; }
}

SwichBotインタフェース

SwitchBotインタフェースでは、BLEコントローラに、SwitchBotデバイスのBLE MACBLEアドバタイズのタイプを指定して、BLEスキャンを開始します。
SwitchBotから取得した気温と湿度はバイナリデータのため、数値変換と、気温と湿度それぞれのTextViewに数値変換した値を出力します。
バイナリデータの構造(フォーマット)は、以下の参考記事を参照ください。

また、インスタンス化したBLEコントローラを操作するためのgetterメソッドを実装します。

    :
    private static final String         DEVICE_MAC = "CA:D4:47:66:1E:BB";
    private static final byte           SWITCHBOT = 0x16;
    :
    public BluetoothLowEnergyController getBluetoothLowEnergyController() {
        return bluetoothLowEnergyController;
    }
    public void setBluetoothLowEnergyController(BluetoothLowEnergyController newBluetoothLowEnergyController, TextView temp, TextView hum) {
        if (bluetoothLowEnergyController == null) {
            bluetoothLowEnergyController = newBluetoothLowEnergyController;
            bluetoothLowEnergyController.setOnChangeListener(value -> {
                if (value.length > 6) {
                    int sign = value[6] & 0x80;
                    float decimals = (value[5] & 0x0f);
                    temperature = (sign == 0x80) ? (value[6] & 0x7f) + (decimals / 10) : (value[6] & 0x7f) + (decimals / 10) * -1;
                    temp.setText(String.format(context.getString(R.string.format_temperature), temperature));
                    if (value.length > 7) {
                        humidity = value[7] & 0x7f;
                        hum.setText(String.format(context.getString(R.string.format_humidity), humidity));
                    }
                }
            });
            bluetoothLowEnergyController.setMacAddress(DEVICE_MAC);
            bluetoothLowEnergyController.setType(SWITCHBOT);
            bluetoothLowEnergyController.scan();
        }
    }
    :

Fragment

Fragement(または、Activity)では、気温と湿度を表示するためのTextViewをセットし、SwitchBotインタフェースにインスタンス化したBLEコントローラとTextViewを引き渡します。
BLEコントローラは、
SwitchBotから受信した気温と湿度をTextViewに一定間隔で更新します。
BLEコントローラの停止は onPause() で行い、再開は onResume() で行います。

    : 
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        view =  inflater.inflate(R.layout.fragment_home, container, false);
        :
        // 画面項目
        temperature = view.findViewById(R.id.temperature);
        humidity = view.findViewById(R.id.humidity);
        :
        return view;
    }
    @Override
    public void onResume() {
        super.onResume();
        :
        // SwitchBot
        setBluetoothLowEnergyController(new BluetoothLowEnergyController(getActivity()), temperature, humidity);
        :
    }
    @Override
    public void onPause() {
        :
        getBluetoothLowEnergyController().pause();
        super.onPause();
    }
    :

SwitchBotデバイスのBLE MACアドレス

SwitchBotデバイスのBLE MACは、SwitchBotアプリで確認できます。

Google Play で手に入れよう

SwitchBotアプリのマイホームから温湿度計をタップします。

デバイス情報をタップします。

③デバイス情報にBLE MACが表示されます。

今回は、ここまでです。

SwitchBotから気温と湿度をBLE通信でアプリに連携している Androidアプリです。

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にある
コメントか、
こちらから、お願いいたします♪

ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

ブログランキング・にほんブログ村へ

これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。

未経験者からシステムエンジニアを目指すのに最適かと、
まずは無料相談から♪

未経験者からプログラマーを目指すのに最適かと、
まずは無料カウンセリングから♪

カリキュラムとサポートがしっかりしています、
お得なキャンペーンとかいろいろやっています♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、
まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪

スポンサーリンク
msakiをフォローする
スポンサーリンク

コメント欄

タイトルとURLをコピーしました