1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

環境の変化と戦う④(スマートマットの文鎮化を回避)

Last updated at Posted at 2025-09-15

はじめに

毎週メールで通知されるスマートマットライトの商品残量状況のお知らせに気になる記載を見つけた。

「2025年9月30日にサービス終了」

スクリーンショット 2025-09-15 15.54.21.png

1年前(2024年10月1日)からサービス終了の案内をしていたようだが、気づいたのはサービス終了(2025年9月30日)の1ヶ月前(2025年8月30日)でした。

スクリーンショット 2025-09-13 21.25.31.png

IoTデバイスの文鎮化の危機

サービス終了の案内のページにはスマートマットの取扱いに関する記載があり、サービス終了後はIoTデバイスが「文鎮化」します。

スクリーンショット 2025-09-13 21.26.57.png

サービス終了したIoTデバイス

過去にサービス終了したIoTサービスやIoTデバイスは多いので驚きはありませんが、IoTデバイスの処分はエコではないので、何とか救済する方法を検討しました。

サービス終了したその他の例

  • Amazon Dash Button ・・2019年8月31日
  • うんこボタン ・・2020年8月31日
  • LINE CLOVA ・・2023年3月30日
  • Qrio Smart Lock (Q-SL1) ・・2023年10月31日

導入した背景

「現場で使える! 自動化入門」で記事を寄稿しています。

  • 同人本

  • 商業本

記事から抜粋して導入の背景を説明します。

今日から庶務担当

本業(エンジニアリング)の傍ら、庶務担当を命ぜられ、業務断捨離や業務改善した一節です。
庶務担当者が3人→1人に減り(2人は円満引退・シニア再雇用で別の道へ)、技術系管理職の宿命とは言え、すべての庶務業務が自分に回ってきました。
本業はインフラ設備の運用保守業務(いわゆる、SRE(Site Reliability Engineering))を担当していますが、運用保守業務はいわゆる「コストセンター」と呼ばれ、サービスやシステムの信頼性を高める活動や付加価値を創造する活動にもあまりコストを掛けられず、日々昼夜、自動化・効率化に勤しんでいます。
庶務業務もエンジニアリングの一種と捉えて、同じように取り組みはじめました。

庶務業務は属人化しやすい

本業でないことは誰も興味が無いし、担当者に丸投げ・任せきりになり、属人化しやすいです。担当者が突然いなくなると困る潜在的な問題を抱えています。
業務知識を公開(属人化→形式知化)し、業務理解に努める仕組みが重要です。
庶務業務一覧を作成し、カテゴリ分け、優先順位づけ(重要度と緊急性の2軸マトリクスで行うことが多いですが、困り度も考慮に含めて)行いました。

巡回業務の自動化

巡回業務として以下がありました。在庫確認にスマートマットを採用しました。

  • 在庫確認(スマートマットの活用)
  • 滞在人数の可視化(スマートAIカメラの活用)
  • 職場環境の快適化(IoTデバイスの活用)

スマートマット

あらかじめ登録した商品の重さを定期的に計測し、指定した条件になると自動で発注するスマートデバイスです。

製品としてスマートマットライトスマートマットクラウドの2種類がありました。
スマートマットライトはAmazonの日用品(水やペーパータオルなど、約3,000の対象商品)の自動注文に特化しており、管理画面が用意されています。マットの価格は安価(セール時〜通常: 1,980〜2,980円)で、マット10枚まで利用可能です。
スマートマットクラウドは法人向けサービスで、棚卸自動化、入出庫管理、自動注文など、フルスペック・フルサービスの在庫管理・発注プラットフォームです。

小規模での導入だったので、買い切りのスマートマットライトの方を採用しました。

スクリーンショット 2025-09-15 16.50.20.png

スマートマットライト

スマートマットライトはマット本体を購入すれば月額利用料は発生しません。マットは単三電池4本で動作し、Wi-Fiへ接続します。定期的に(デフォルトでは1日4回)重さを測定し、AWSへデータが送信されます。
データは専用の管理画面から、対象のマットを選択→詳細情報→消費レポートへ移動して確認することができます。

スクリーンショット 2025-09-15 16.55.55.png
スクリーンショット 2025-09-15 16.56.08.png

分解

スマートマットライトの裏側のシールを剥がすとネジが1本現れ、プラスドライバーで外すことができます。側面はガラス板とプラスチックの境界にマイナスドライバーを差し込んで開けることができます。スマートマットライト1はESP-WROOM-02(ESP8266チップ)基板のWi-Fiアンテナを使用していますが、スマートマットライト2はWi-Fiアンテナが外出しとなり、ネットワークの接続性が向上しています。

スクリーンショット 2025-09-15 16.58.11.png
スクリーンショット 2025-09-15 16.58.24.png

(左)マートマットライト1 (右)マートマットライト2

スクリーンショット 2025-09-15 16.58.52.png

スマートマットライトのハック

Amazonアカウントと連携して自動注文してくれたり、EメールやLINE(ID連携が必要)で通知されるので非常に便利ですが、注文先を変更したり、消費データだけを利用(分析)したり、他の機能と連携させる方法を検討し、Google SpreadsheetとGAS(Google Apps Script)を使用して管理画面の消費レポートからデータを取得し、他の機能と連携させる方法を記事にしました。

構成

デバイスからは重量の測定データをインターネット(AWS)へアップロードし、PC(またはスマホのアプリ)からは管理画面を経由してデータを確認できます。

スクリーンショット 2025-09-15 17.08.24.png

デバイスの通信内容をWiresharkでパケットキャプチャします。
データのアップロードでは以下の処理をしていることを確認しました。

  • DNSでネームを検索
  • HTTP POSTで測定データを送信
  • HTTP GETでデータを取得

スクリーンショット 2025-09-15 17.16.20.png

ブラウザの開発者ツールで管理画面の通信内容を確認します。
管理画面へのログインでは以下の処理をしていることを確認しました。

  • サインインのURL(/api/bff/v1/signin)でID(email)とパスワード(password)を送信
  • クッキー(cookie)でセッションID(sid)を受信

スクリーンショット 2025-09-15 17.17.51.png

APIの仕様を推察

管理画面にログインした後、スマートマットのデータを確認します。
詳細情報を確認すると、スマートマット毎にURLに管理番号(subscriptionId)が付与されています。
HTTP GETでデータ取得用のURL(/api/bff/v1/subscription/search_detail)にアクセスし、以下のJSONデータを取得しています。

{
  "deviceSerialNumber": "WXXXXXXXXXXX",
  "isFirstConnected": false,
  "isConnected": true,
  "battery": 66,
  "remainingPercent": 0,
  "triggerRemainingPercent": 20,
  "measuredAt": "2023-04-08T06:06:03+09:00",
  "current": 0,
  "frequency": 8,
  "subscriptionId": 8594,
  "title": "Ar ハンドタオル 200枚入 5個パック",
  "full": 2070.21,
  "productUrl": "https://www.amazon.co.jp/dp/B00K0Z0ZFU",
  "imageUrl": "https://m.media-amazon.com/images/I/414WdyYdVuL._SL500_.jpg",
  "outputType": 4,
  "orderable": false,
  "dailyAverageConsumption": 6.5,
  "dailyConsumption": 0,
  "weeklyConsumption": 0,
  "totalOrderCount": 4
}

スマートマットクラウドのAPIから取得できるデータとフォーマットが一部共通であることを確認できました。

GASの実装

通信内容の確認とAPIの解析結果をもとに、GAS(Google Apps Script)を作成します。
作成するスクリプトは大きく2つの処理から成ります。

  • スマートマットライトの管理画面へログインし、スマートマットの測定データを取得する
  • 取得したデータをスプレッドシートに記録する

GASの中の変数に必要な情報を設定します。
「email」 →管理画面のログインID
「password」 →管理画面のパスワード
「XXXX」 →スマートマットの管理番号
「sheet_id」 →スプレッドシートID
「sheet_name」 →スプレッドシートのシート名

function fetchJSONData() {

  const postdata = {
	'email': '', // ID
	'password': '' // Password
  }

  const loginUrl = 'https://lite.smartmat.io/api/bff/v1/signin'; // ログインURL
  const dataUrl = 'https://lite.smartmat.io/api/bff/v1/subscription/search_detail?subscriptionId=XXXX'; // データURL
  const sheet_id = '';  // SpreadSheetID
  const sheet_name = ''; // SpreadSheetName

  const options = {
	'method': 'post',
	'Content-Type': 'application/json',
	'payload': JSON.stringify(postdata),
	'followRedirects': false
  }

  // ログインページにPOSTリクエストを送信して、Cookieを取得する
  const response = UrlFetchApp.fetch(loginUrl, options);
  const headers = response.getHeaders();
  const cookie = headers['Set-Cookie'];

  // Cookieを使用して、JSONデータを取得する
  const jsonData = UrlFetchApp.fetch(dataUrl, {
	'headers': {
  	'Cookie': cookie
	}
  }).getContentText();

  // JSONデータを解析する
  const parsedData = JSON.parse(jsonData);

  // スマートマットがWi-Fiに接続されている場合: true → 1、切断されている場合: false → 0
  let connect = 0;
  if (parsedData['isConnected']) {
	connect = 1;
  }
 
  // JSONデータをSpreadSheetに出力する
  const MySheet = SpreadsheetApp.openById(sheet_id);
  MySheet.getSheetByName(sheet_name).appendRow(
	[new Date(), connect, parsedData['battery'], parsedData['remainingPercent']]
  );
}

サンプルコードはこちらです。

GASを実行するとスプレッドシートにスマートマットライトのデータが記録されるので、記録されたデータを用いてグラフを作成します。

スクリーンショット 2025-09-15 17.31.09.png

トリガーを設定しGASを定期的に実行します。

在庫の消費傾向に加えて、Wi-Fi接続状況と電池の消費傾向も把握できるようにしました。
在庫が無くなる時期を予測したり、意図しない使い過ぎ(持ち去り?!)を警告することもできました。

デバイスのハック

デバイスの歪みセンサーだけを使用し、M5Stack(ATOM Lite)と重さユニット(HX711)に換装した事例がありました。

デバイスを改造せず、そのまま利用する方法を検討しました。
以前に通信内容の解析はしていたので、構成を変更して、ラズパイをWi-Fiアクセスポイント化し、ラズパイにウェブサーバ(Python, Node.js, Node-REDの3種類のサンプルを作成)を立ててデータのアップロード先にします。

スクリーンショット 2025-09-15 19.19.14.png

デバイスのデバッグログ

デバイスの基板上にタップポイントが実装されているので、GNDとD_TXDにUSBシリアル変換デバイスを接続し、デバッグログを取得します。

基板タップ.jpg

スクリーンショット 2025-09-15 18.17.35.png

macOSのターミナル画面からscreenコマンドでデバッグログを確認します。(通信速度は9,600bps)

screen -L /dev/cu.usbserial-00000000 9600

正常に通信を完了した時の例です。

INF|user_main.c:182|Smartmat V2.08
INF|user_main.c:183|HW VER:B
INF|user_main.c:184|Build Jul 20 2019 11:17:26
INF|user_main.c:185|Boot flag:1
INF|spi_nor_at25sf.c:93|Flash detected
EspMsgMutex created
INF|user_main.c:208|mode: PRODUCT_MODE
CRT|basic_infor.c:57|get_api_param->system_param_load success
INF|user_main.c:234|MAT_ID :W42200500161
INF|user_main.c:235|OTA_VER:2.08
INF|user_main.c:236|API_URL:http://measure.lite.smartmat.io/v1/device/version2
INF|user_main.c:237|API_KEY:443190d1f417c680837cf6388dc191bb
DBG|basic_infor.c:264|OTA Reboot flag=0x00
mode : sta(f4:cf:a2:68:3d:3d)
add if0
DBG|usart_data_hand.c:1705|TICK_DATA_CMD -> MCU
DBG|weight_canculate_api.c:203|Zero:71079
DBG|weight_canculate_api.c:207|Span1:11473
DBG|weight_canculate_api.c:211|Span2:11424
INF|usart_data_hand.c:328|Mode:TIME_MESURE_CMD
DBG|usart_data_hand.c:499|hand data again 
DBG|usart_data_hand.c:503|time_mesure_data_hand
DBG|web_config.c:171|--locked
DBG|battery_level.c:43|BAT_VOL=5097, LEVEL=43
scandone
state: 0 -> 2 (b0)
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 1
pm open phy_2,type:2 0 0
cnt 

connected with RASPI, channel 1
dhcp client start...
DBG|web_config.c:171|--locked
ip:192.168.12.182,mask:255.255.255.0,gw:192.168.12.1
DBG|web_config.c:171|--locked
DBG|web_config.c:197|WIFI CONNECTED
DBG|web_config.c:237|send_data_to_cloud loop
DBG|cloud_api.c:698|send data flag=0
INF|cloud_api.c:392|CALL:get_mat_setting
DBG|cloud_api.c:97|pack data:{"id":"W42200500161","wv":"2.08","mv":"15"}
DBG|http.c:256|URL=http://measure.lite.smartmat.io/v1/device/version2/s
INF|http.c:92|DNS CALLBACK 
INF|http.c:309|send success!
DBG|http.c:394|====================
RECV=HTTP/1.1 200 OK
Date: Mon, 15 Sep 2025 09:17:58 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 111
Connection: keep-alive
x-envoy-upstream-service-time: 96
server: envoy

{"i":300,"c":"http://measure.lite.smartmat.io/v1/device/version2","mr":0,"mrd":"","fr":0,"frd":"","o":0,"md":0}
DBG|http.c:395|====================
DBG|cloud_api.c:480|get_frequency_rev_hand
DBG|cloud_api.c:495|freq=300
DBG|cloud_api.c:502|OTA REQ=0
DBG|cloud_api.c:507|New URL=http://measure.lite.smartmat.io/v1/device/version2
DBG|basic_infor.c:131|URL has no change
DBG|cloud_api.c:515|MCU RST=0
DBG|cloud_api.c:523|MCU RST DATE=
DBG|cloud_api.c:539|40FF5152
DBG|cloud_api.c:544|FAC RST=0
DBG|cloud_api.c:552|FAC RST DATE=
DBG|cloud_api.c:568|40FF5152
DBG|http.c:256|URL=http://measure.lite.smartmat.io/v1/device/version2/sd
INF|http.c:309|send success!
DBG|http.c:394|====================
RECV=HTTP/1.1 200 OK
Date: Mon, 15 Sep 2025 09:17:59 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 38
Connection: keep-alive
x-envoy-upstream-service-time: 2
server: envoy

{"d":"2025-09-15 09:17:59","tz":"UTC"}
DBG|http.c:395|====================
DBG|cloud_api.c:611|Date field=2025-09-15 09:17:59
DBG|cloud_api.c:633|DATE->MCU 012C07E9090F09113B38
DBG|web_config.c:171|--locked
DBG|web_config.c:197|WIFI CONNECTED
DBG|web_config.c:237|send_data_to_cloud loop
DBG|cloud_api.c:698|send data flag=0
DBG|cloud_api.c:715|send_mesure_data_to_cloud
INF|cloud_api.c:226|Send measured data
DBG|cloud_api.c:230|POST DATA={"id":"W42200500161","md":[{"w":"1480.00","d":"2025-09-15 09:17:57"}],"b":"0.43","p":"0","r":"-40"}
DBG|http.c:256|URL=http://measure.lite.smartmat.io/v1/device/version2/m
INF|http.c:309|send success!
DBG|http.c:394|====================
RECV=HTTP/1.1 200 OK
Date: Mon, 15 Sep 2025 09:18:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 47
Connection: keep-alive
x-envoy-upstream-service-time: 45
server: envoy

{"m":"OK","d":"2025-09-15 09:18:00","tz":"UTC"}
DBG|http.c:395|====================
INF|data_save.c:133|clear success
DBG|web_config.c:171|--locked
DBG|web_config.c:197|WIFI CONNECTED
DBG|web_config.c:237|send_data_to_cloud loop
INF|usart_data_hand.c:1416|hand result done
DBG|web_config.c:171|--locked
DBG|web_config.c:197|WIFI CONNECTED
DBG|web_config.c:171|--locked
DBG|web_config.c:197|WIFI CONNECTED
INF|usart_data_hand.c:1584|POWER_OFF_CMD -> MCU

ラズパイのWi-Fiアクセスポイント化

まずは通常通りラズパイをセットアップします。

  • Raspberry Pi OS (64-bit) with desktop (13 May 2025)
sudo apt update
sudo apt upgrade

ラズパイをWi-Fiアクセスポイント化するソフトウェア(linux-wifi-hotspot)をインストールするため、先にPi-Appsをインストールします。

wget -qO- https://raw.githubusercontent.com/Botspot/pi-apps/master/install | bash

デスクトップ画面のPi-Appsを起動します。

rpi1.jpg

ToolsからLinux Wifi Hotspotをインストールします。

rpi2.jpg

GUI画面からLinux Wifi Hotspotを起動することができますが、スマートマットライトの通信先を変更するためコマンドラインから起動します。

rpi3.jpg
rpi4.jpg

sudo create_ap --config /etc/create_ap.conf

コンフィグの変更

デバイスの通信先を変更するため、/etc/hostsファイルに設定を追加します。(192.168.3.37はラズパイのIPアドレス)

192.168.3.37	measure.lite.smartmat.io

/etc/hostsファイルに追加した内容をdnsmasqのDNS応答に反映させるため、/etc/create_ap.confファイルに設定を追加します。(ETC_HOSTS=1)
SSIDとPASSPHRASEはGUI画面から設定した内容が反映されています。

/etc/create_ap.conf
CHANNEL=default
GATEWAY=192.168.12.1
WPA_VERSION=2
ETC_HOSTS=0
DHCP_DNS=gateway
NO_DNS=0
NO_DNSMASQ=0
HIDDEN=0
MAC_FILTER=0
MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept
ISOLATE_CLIENTS=0
SHARE_METHOD=nat
IEEE80211N=0
IEEE80211AC=0
IEEE80211AX=0
HT_CAPAB=[HT40+]
VHT_CAPAB=
DRIVER=nl80211
NO_VIRT=0
COUNTRY=
FREQ_BAND=2.4
NEW_MACADDR=
DAEMONIZE=0
DAEMON_PIDFILE=
DAEMON_LOGFILE=/dev/null
DNS_LOGFILE=
NO_HAVEGED=0
WIFI_IFACE=wlan0
INTERNET_IFACE=eth0
SSID=RASPI
PASSPHRASE=12345678
USE_PSK=0
ADDN_HOSTS=
NO_VIRT=1
ETC_HOSTS=1

ウェブサーバの構築

デバッグの試行錯誤の過程で、Python, Node.js, Node-REDの3つでウェブサーバを実装しました。
ポート80で起動するため、sudoで実行します。

Python版

python -V
Python 3.11.2

sudo python server.py
server.py
# -*- coding: utf-8 -*-
import os
import http.server as s
from urllib.parse import urlparse
from urllib.parse import parse_qs

class MyHandler(s.BaseHTTPRequestHandler):
    def do_POST(self):
        self.make_data()
    def do_GET(self):
        self.make_data()
    def make_data(self):
        parsed = urlparse(self.path)
        print(parsed)
        params = parse_qs(parsed.query)
        print(params)
        if (self.headers.get("Content-Length")):
            content_len  = int(self.headers.get("Content-Length"))
            print(content_len)
            req_body = self.rfile.read(content_len).decode("utf-8")
            print(req_body)

        if (parsed.path == '/v1/device/version2/s'):
            body = '{"i":5846,"c":"http://measure.lite.smartmat.io/v1/device/version2","mr":0,"mrd":"","fr":0,"frd":"","o":0,"md":0}'
        elif (parsed.path == '/v1/device/version2/sd'):
            body = '{"d":"2025-09-13 16:18:34","tz":"UTC"}'
        elif (parsed.path == '/v1/device/version2/m'):
            body = '{"m":"OK","d":"2025-09-13 16:18:34","tz":"UTC"}'
        else:
            body = '{}'
            
        self.send_response(200)
        self.send_header('Content-type', 'application/json; charset=utf-8')
        self.send_header('Content-length', len(body.encode()))
        self.end_headers()
        self.wfile.write(body.encode())
host = '0.0.0.0'
port = 80
httpd = s.HTTPServer((host, port), MyHandler)
httpd.serve_forever()

Node.js版

node -v
v20.19.5

sudo node server.js
server.js
const http = require("http");
const port = 80;

const server = http.createServer((request, response) => {
  console.log(request.url);
  var dt = new Date();
  var dth = dt.toUTCString();  // for HTTP Date header
  var dtb = dt.toISOString().slice(0,10) + " " + dt.toISOString().slice(11,19);

  if (request.method === 'GET' && request.url === '/v1/device/version2/sd') {
    response.writeHead(200, {
      "Date": dth,
      "Content-Type": "application/json; charset=utf-8",
      "Content-Length": 38,
      "Connection": "keep-alive",
      "x-envoy-upstream-service-time": 2,
      "server": "envoy"
    });
    const responseMessage = '{"d":"' + dtb + '","tz":"UTC"}';
    response.end(responseMessage);
  }
  else if (request.method === 'POST' && request.url === '/v1/device/version2/i') {
    response.writeHead(200, {
      "Date": dth,
      "Content-Type": "application/json; charset=utf-8",
      "Content-Length": 2,
      "Connection": "keep-alive",
      "x-envoy-upstream-service-time": 96,
      "server": "envoy"
    });
    const responseMessage = '""';
    response.end(responseMessage);
  }
  else if (request.method === 'POST' && request.url === '/v1/device/version2/s') {
    response.writeHead(200, {
      "Date": dth,
      "Content-Type": "application/json; charset=utf-8",
      "Content-Length": 111,
      "Connection": "keep-alive",
      "x-envoy-upstream-service-time": 96,
      "server": "envoy"
    });
    const responseMessage = '{"i":300,"c":"http://measure.lite.smartmat.io/v1/device/version2","mr":0,"mrd":"","fr":0,"frd":"","o":0,"md":0}';
    response.end(responseMessage);
  }
  else if (request.method === 'POST' && request.url === '/v1/device/version2/m') {
    response.writeHead(200, {
      "Date": dth,
      "Content-Type": "application/json; charset=utf-8",
      "Content-Length": 47,
      "Connection": "keep-alive",
      "x-envoy-upstream-service-time": 45,
      "server": "envoy"
    });
    const responseMessage = '{"m":"OK","d":"' + dtb + '","tz":"UTC"}';
    response.end(responseMessage);
  }
  else {
    response.writeHead(200, {
      "Content-Type": "text/html; charset=utf-8"
    });
    const responseMessage = "OK";
    response.end(responseMessage);
  }
});

server.listen(port);
console.log(`The server started: ${port}`);

Node-RED版

sudo node-red -u ~/.node-red

nr1.jpg

Node-REDフローはこちらに置きます。

ハマったポイント

ラズパイをWi-Fiアクセスポイントにして、スマートマットライト(デバイス)を接続するところまでは問題なくスムースにできました。
通信先の変更と、Wi-Fiアクセスポイントへの再接続(デバッグの目的で頻繁にデバイスをラズパイへ接続)でハマりました。

デバイスの通信が途中で止まる

デバイスの通信をパケットキャプチャ(Wireshark)で確認すると、初回は2回、2回目以降は3回通信します。

rpi5.jpg

(初回)

  • HTTP POST /v1/device/version2/i
    • デバイス情報の送信
  • HTTP GET /v1/device/version2/sd
    • 時刻情報の取得

(2回目以降)

  • HTTP POST /v1/device/version2/s
    • デバイス情報の送信
  • HTTP GET /v1/device/version2/sd
    • 時刻情報の取得
  • HTTP POST /v1/device/version2/m
    • 重量測定データの送信

デバッグログ上で、デバイス情報の送信は正常に行われるが、時刻情報の取得で止まる事象に遭遇しました。結果的にTTLの値が問題(原因)と分かりました。(デバイスがDNS応答のTTLに従っている挙動に関心しました)

成功する時のDNS応答(TTLが300)
dig measure.lite.smartmat.io

; <<>> DiG 9.10.6 <<>> measure.lite.smartmat.io
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2783
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 4, ADDITIONAL: 9

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;measure.lite.smartmat.io.	IN	A

;; ANSWER SECTION:
measure.lite.smartmat.io. 300	IN	CNAME	k8s-smlprdmeasure-514e25ea64-631360711.ap-northeast-1.elb.amazonaws.com.
k8s-smlprdmeasure-514e25ea64-631360711.ap-northeast-1.elb.amazonaws.com. 60 IN A 54.65.29.5
k8s-smlprdmeasure-514e25ea64-631360711.ap-northeast-1.elb.amazonaws.com. 60 IN A 35.72.48.174
失敗する時のDNS応答(TTLが0)
dig measure.lite.smartmat.io @192.168.3.37

; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> measure.lite.smartmat.io @192.168.3.37
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41994
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;measure.lite.smartmat.io.	IN	A

;; ANSWER SECTION:
measure.lite.smartmat.io. 0	IN	A	192.168.3.37

;; Query time: 0 msec
;; SERVER: 192.168.3.37#53(192.168.3.37) (UDP)
;; WHEN: Mon Sep 15 12:31:50 JST 2025
;; MSG SIZE  rcvd: 69

dnsmasqのmanページでデフォルトでは/etc/hostsのDNS応答のTTLが0になることがわかりました。

-T, --local-ttl=<time>
    When replying with information from /etc/hosts or configuration or the DHCP leases file dnsmasq by default sets the time-to-live field to zero, meaning that the requester should not itself cache the information. This is the correct thing to do in almost all situations. This option allows a time-to-live (in seconds) to be given for these replies. This will reduce the load on the server at the expense of clients using stale data under some circumstances. 
  • TTLを60秒に変更
    • dnsmasqの設定を追加(local-ttl=60)

デバイスがラズパイ(Wi-Fiアクセスポイント)に再接続できない

デバッグのため、デバイスを頻繁に再接続させようとする(手動で重量測定のボタンを押す)と、初回はラズパイ(Wi-Fiアクセスポイント)に接続できますが、2回目以降は接続できません。
しばらく時間が経過すると再接続できるようになりますが、create_apコマンドを再起動すると直ぐに接続できます。

正常に接続できた時のログ出力
sudo create_ap --config /etc/create_ap.conf 
WARN: brmfmac driver doesn't work properly with virtual interfaces and
      it can cause kernel panic. For this reason we disallow virtual
      interfaces for your adapter.
      For more info: https://github.com/oblique/create_ap/issues/203
Config dir: /tmp/create_ap.wlan0.conf.pOYLV6Y3
PID: 1126925
Network Manager found, set wlan0 as unmanaged device... DONE
Sharing Internet using method: nat
hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.pOYLV6Y3/hostapd_ctrl
wlan0: interface state UNINITIALIZED->ENABLED
wlan0: AP-ENABLED 
wlan0: STA f4:cf:a2:68:3d:3d IEEE 802.11: associated
wlan0: AP-STA-CONNECTED f4:cf:a2:68:3d:3d
wlan0: STA f4:cf:a2:68:3d:3d RADIUS: starting accounting session F9DA118443915C76
wlan0: STA f4:cf:a2:68:3d:3d WPA: pairwise key handshake completed (RSN)
wlan0: EAPOL-4WAY-HS-COMPLETED f4:cf:a2:68:3d:3d

(しばらく時間が経過)

wlan0: AP-STA-DISCONNECTED f4:cf:a2:68:3d:3d
wlan0: STA f4:cf:a2:68:3d:3d IEEE 802.11: disassociated due to inactivity
wlan0: STA f4:cf:a2:68:3d:3d IEEE 802.11: deauthenticated due to inactivity (timer DEAUTH/REMOVE)
wlan0: STA f4:cf:a2:68:3d:3d IEEE 802.11: disassociated

接続したデバイスの無効化がデフォルトでは300秒(5分)に設定されていることを確認しました。

hostapd.conf
# Station inactivity limit
#
# If a station does not send anything in ap_max_inactivity seconds, an
# empty data frame is sent to it in order to verify whether it is
# still in range. If this frame is not ACKed, the station will be
# disassociated and then deauthenticated. This feature is used to
# clear station table of old entries when the STAs move out of the
# range.
#
# The station can associate again with the AP if it is still in range;
# this inactivity poll is just used as a nicer way of verifying
# inactivity; i.e., client will not report broken connection because
# disassociation frame is not sent immediately without first polling
# the STA with a data frame.
# default: 300 (i.e., 5 minutes)
#ap_max_inactivity=300
  • デバイスの無効化の時間を60秒(1分)に変更
    • hostapdの設定を追加(ap_max_inactivity=60)

create_apファイルの変更

2つの修正は/etc/create_ap.confでは対応できないため、/usr/bin/create_apを直接変更しました。

/usr/bin/create_ap
diff /usr/bin/create_ap /usr/bin/create_ap_org
1685d1684
< ap_max_inactivity=60
1795d1793
< local-ttl=60

さいごに

スマートマットライトのデバイスの文鎮化を回避することができました。
IoTサービスやIoTデバイスのサポートが終了せず、安心して長く提供されることを切に願います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?