2021年10月10日にシステム監査技術者試験を受けていたが記事にするのを忘れていた。
午前IIの結果が25問中14問しか合っていなかったので痛恨の午前落ちで書き忘れていた。
会場はTKPガーデンシティPREMIUM横浜西口(URL)。
会場はなかなか奇麗だった。
2021年10月10日にシステム監査技術者試験を受けていたが記事にするのを忘れていた。
午前IIの結果が25問中14問しか合っていなかったので痛恨の午前落ちで書き忘れていた。
会場はTKPガーデンシティPREMIUM横浜西口(URL)。
会場はなかなか奇麗だった。
自宅と実家をL2VPNで結びたくなったのでOpenVPNで繋ぐことにした。
自宅には前からあるNetBSD/amd64 9.2をOpenVPNのサーバとして使い、実家側にはラズパイを買って設置することにした。値段とか気にせずクリックしたらRaspberry Pi4 ModelB 4GBに必要な機材一式のセットを選んでいた。
試行錯誤して、やっとわかってきた。
・OpenVPNの設定以外にOS側のインターフェイスの設定が必要である。
・NetBSDとRaspberry Pi OSではbridgeインターフェイスの仕様が違う(NetBSDはbridgeインターフェイスにIPアドレスが付与できず、Raspberry Pi OSでは付与できる)
・L2VPNなので同一サブネットを共有するので拠点ごとにIPアドレスがぶつからない様に設計しておく。
いったん実家にもっていく前に自宅で試験環境を作成してやってみた。
構成はこんな感じ。
〇全般の設計
・共有するサブネットは192.168.0.0/24とする。
・サーバ側拠点は192.168.0.1~99、クライアント側拠点は192.168.0.100~254を利用する。
・デフォルトゲートウェイはサーバ側拠点は192.168.0.1、クライアント側拠点は192.168.0.100とする。
・OpenVPNはUDPでVPNを構築する。ポート番号をデフォルトの1194/UDPから(このblogでは)31194/UDPに変える。
・IPv4の事しか考えない。
・サーバ側拠点のグローバルIPアドレスは常時接続の為、滅多に変わらない事から自分で保有しているドメイン空間から切り出してAレコードで登録する。登録するDNSは個人契約のO365のサービスの中にあったので登録する。このblogでは「vpn.example.com」とする。
・今のところサーバ側、クライアント側でProxyArpの設定やFirewallの設定は無くても動いている。
〇NetBSD側(サーバ側)の対応
・クライアントから接続を受けるのでブロードバンドルータの設定を変更し外から31194/UDPがNetBSD側(サーバ側)に転送されるように設定(ポートフォワーディング)する。
・pkginを使い「openvpn-2.5.3」と「easy-rsa-3.0.8」を入れる。
・(メモ)netbsdのifconfigで一時的にIPアドレスを消すのは「ifconfig wm0 inet 192.168.0.3 delete」という文法
1. インターフェイス側の設定を行う
・物理IF名はwm0のみである。
・wm0はIPアドレスを与えないため、「/etc/ifconfig.wm0」を作成し中身は「up」のみ記載。
・tap0インターフェイスが必要で、IPアドレスも付与するので「/etc/ifconfig.tap0」に下記の内容を記述する(「The NetBSD Guide」の「24.6. Setting up a network bridge device」にあるExample 24.10にドンピシャの設定が書いてあった。このtap0の設定の中でbridgeも設定してしまえば確かにブリッジの設定前にtap-IFが出来ていない依存関係が解決されるや)。
---
create
inet 192.168.0.3 netmask 255.255.255.0
up
!ifconfig bridge0 create
!brconfig bridge0 add $int add wm0 up
---
・bridgeインターフェイスも必要だがtapの設定中で作成しているので不要
・「/etc/rc.conf」にはインターフェイスの設定は特に記載しない。
・ここで試しに再起動してみて、外部からsshなど出来るか、ネットワークインターフェイスやブリッジインターフェイスの状態、ルーティングテーブルの状態を確認すると区切りは良いでしょう。
tcpdumpでインターフェイスを指定してキャプチャして動作をみるとかも切り分けに良いかと。
ifconfig:
・wm0,bridge0,tap0はUPになっているか
・wm0,tap0は「PROMISC」が入っているか(netbsdのNW-IFはbridge-IFに参加すると自動でプロミスキャスモードになる)
・IPv4アドレスは、wm0のみに設定されているか
netstat -rn:
・defaultを含め、出力先IFがtap0になっているか(普通は物理IF(今回だとwm0)だが、今回はVPNの関係でtap0が出力先IFになる)。
brconfig bridge0:
・Interfacesにtap0とwm0が存在するか
・Address cacheに色々なMACアドレスが乗ってきているか。
2. OpenVPNのTLSの設定
証明書のくだりは全て下記のサイトに書いている通りに実施
VPNサーバー構築(OpenVPN) - CentOSで自宅サーバー構築 (centossrv.com)
3. OpenVPNの設定ファイル
・設定ファイル「server.conf」を「/usr/pkg/etc/openvpn」に設置。
・設定ファイルの中身は下記。動作のログはもともとこのサーバがsyslog.conf側で「*.info」を特定ファイルに書き出しているので、そっちに吐かれている
---
port 31194
proto udp
dev tap0
ca pki/ca.crt
cert pki/issued/server.crt
dh pki/dh.pem
server-bridge
keepalive 10 120
cipher AES-256-CBC
user nobody
group nobody
persist-key
persist-tun
status openvpn-status.log
verb 3
explicit-exit-notify 1
---
4. openvpnの起動設定
・起動ファイルのコピー「cp -p /usr/pkg/share/examples/rc.d/openvpn /etc/rc.d/」
・自動起動の設定「/etc/rc.conf」に「openvpn=YES」を追記する。
・この状態でrootにて「/etc/rc.d/openvpn start」としてログに「openvpn[2711]: Initialization Sequence Completed」が出ていれば待ち受け出来ている。
・プロセスなどをpsコマンドで見ておくと「/usr/pkg/sbin/openvpn --cd /usr/pkg/etc/openvpn --daemon --config server.conf」というプロセスが見える。
・sockstatでプロセス名でgrepをかければ「nobody openvpn 2711 7 udp6 *.31194 *.*」が見える
・最後に再起動してみて、プロセスの状態やログが目的の通りであれば設定完了。
〇Raspberry Pi OS側(クライアント側)の対応
・必要なソフトのインストール
apt-get install openvpn
apt-get install tcpdump
apt-get install bridge-utils
・あると便利なツール
apt-get install locate
apt-get install emacs
・クライアントから最初のUDPを送るのでブロードバンドルータの設定は不要。
・(メモ)Raspberry Pi OSのifconfigで一時的にIPアドレスを消すのは「ifconfig eth0 0.0.0.0」(この文法、気持ち悪い...)という文法
・なお私はRaspberry Pi OSを含めLinux系のシステム管理は良く分からないのでググって適当にやっています。
1.インターフェイス側の設定を行う
・eth0は、IPアドレスをつけずにUPだけさせる。
・「/etc/dhcpcd.conf」に下記を記述
---
interface eth0
static ip_address=0.0.0.0/0
---
・bridgeインターフェイスをbr0という名前で作成し、tapインターフェイスはtap0という名前で作成し、br0のブリッジにeth0とtap0を参加させる。
・「/etc/network/interfaces」に下記を記述する。
---
auto br0
iface br0 inet static
address 192.168.0.103
netmask 255.255.255.0
network 192.168.0.0
broadcast 192.168.0.255
gateway 192.168.0.101
dns-nameserver 192.168.0.101
bridge-ports eth0 tap0
bridge_stp off
bridge_maxwait 0
bridge_fd 0
pre-up ip tuntap add dev tap0 mode tap user root
pre-up ip link set tap0 up
post-down ip link set tap0 down
post-down ip tuntap del dev tap0 mode tap
---
・ここで試しに再起動してみて、外部からsshなど出来るか、ネットワークインターフェイスやブリッジインターフェイスの状態、ルーティングテーブルの状態を確認すると区切りは良いでしょう。
tcpdumpでインターフェイスを指定してキャプチャして動作をみるとかも切り分けに良いかと。
自宅からトラフィックがどれぐらい出ているのか、簡単にわかると良いなと常々思っており、ISPが配るホームルータには画面で分かるとかSNMPの対応をしてほしいと思っている。
そんな機能がついているのはであったことがなく、一般向けのルータにもついてない。
しょうがないので前のJCOM 320Mbps契約の際は、そもそもグローバルIPアドレスが1個だけ降ってきてホームルータも無いのでLinuxでNAT箱を作成し、そのNICのトラフィックを取り出して仕様帯域を確認していた。
普通に使う分にダウンロードとか遅いとかは無かった(*1)が、上りが10Mbpsしかない仕様なのでクラウドにデータバックアップをがっつりしていると帯域が飽和し、その状態でWeb会議を行うと仕事にならないので、JCOMのひかり1ギガに切り替えている。
(*1: Webを見ていると同軸の320Mbpsで全然速度も出ない人もいるようで運が必要なのかも)
JCOMの1ギガは約款を見ると、同軸1ギガ(1Gコース)、JCOM回線のひかりの1ギガ(ひかり1Gコース)、KDDIの1ギガの再販(光 1G コース on au ひかり)があるっぽい。契約したのはKDDIの再販のひかり1Gで契約するとONUとホームゲートウェイ(HGW)としてAterm BL1001HWが提供される。このBL1001HWが結構高性能で、自宅のWIFI6(802.11ax)はこのHGWのみが対応という状態。その為、WIFI6を使うとトラフィックが取れないという事を我慢していた。
先日、ふと気が付いて、ONUとHGW間のL2SWをかまして、そのL2SWからSNMPでデータを取り出せば良いじゃないと。
やってみたところ、うまく取り出せている模様。L2SWもポートVLAN機能が役に立ってうれしいだろう。ただISP契約を2.5Gbps以上にすると、この構成だと困るので、その際はMS510TXとか買い直さないといけないかも。
久しぶりにNASにログインしたら
・DISK4が認識していません。
・DISK1が壊れそうです。
とのエラー表示。
メーカー | 型番 | 内容 | 購入先 | 個数 | 値段 |
東芝 | MQ04ABD200 | 2.5インチHDD 2TB | ノジマオンライン | 1個 | 6,045円(税込) |
自宅にはWxBeacon2とSwitchBotの温湿度計が各々1台ずつあるのだが、 先日のスクリプトだと1台のセンサから情報取得するのに毎回5秒かかるので1分間には最大12個しか取れない。クラス定義のscanを読むとタイムアウトまでの時間で見つかったデバイス全てに処理をしているようだったので、チェックしたいデバイスのMacアドレスをリストで渡すと、その中で見つかったセンサのデータを返すようにしてみた。
センサからのデータを成形したりbluepyを使う部分は前回同様に @c60evaporatorさんの記事のコードそのまま。
WxBeacon2のブロードキャストモードへの変更やSwitchBotのMACアドレスの取得、周辺モジュールのインストールや設定は上記の記事を参照の事。
「env_broadcast.py」、「bluetoothsensor.py」を同一ディレクトリに書くのすれば動くはず。
「env_broadcast.py」は、リスト型で調査対象のMACアドレスを一覧で受け取る。スキャン時に一致するMACアドレスが見つかるとWxBeacon2かSwitchBotの温湿度計かを自動判別してセンサ情報を取得する。取得したデータはMACアドレスをキーにしたdict型(バリューも更にdict型)に格納する。同じMACアドレスで2度取れてしまった際は上書きしている。
env_broadcast.py
from bluepy import btle import struct class ScanDelegate(btle.DefaultDelegate): #コンストラクタ def __init__(self, macaddrs): btle.DefaultDelegate.__init__(self) #センサデータ保持用変数 self.macaddr = macaddrs self.sensorValue = dict() for mac in macaddrs: self.sensorValue[mac] = {'SensorType':'None'} # スキャンハンドラー def handleDiscovery(self, dev, isNewDev, isNewData): # 新しいデバイスが見つかったら if isNewDev or isNewData: # ターゲットのMACアドレス(環境センサ)であるか # print('dev.addr in self.macaddr ->', dev.addr, self.macaddr, dev.addr in self.macaddr) if dev.addr in self.macaddr: # アドバタイズデータを取り出し for (adtype, desc, value) in dev.getScanData(): # 環境センサのとき、データ取り出しを実行 # まずWxBeacon2/Omronの場合 if desc == 'Manufacturer' and value[0:4] == 'd502': #センサの種類(EP or IM)を取り出し sensorType = dev.scanData[dev.SHORT_LOCAL_NAME].decode(encoding='utf-8') #EPのときのセンサデータ取り出し if sensorType == 'EP': self.decodeSensorData_EP(value, dev.addr) #IMのときのセンサデータ取り出し if sensorType == 'IM': self.decodeSensorData_IM(value, dev.addr) # SwitchBotの温湿度計の場合 else: for (adtype, desc, value) in dev.getScanData(): #環境センサのとき、データ取り出しを実行 if desc == '16b Service Data': #センサデータ取り出し self._decodeSensorData(value, dev.addr) # センサデータを取り出してdict形式に変換(EPモード時) def decodeSensorData_EP(self, valueStr, macaddr): #文字列からセンサデータ(6文字目以降)のみ取り出し、バイナリに変換 valueBinary = bytes.fromhex(valueStr[6:]) #バイナリ形式のセンサデータを整数型Tapleに変換 (temp, humid, light, uv, press, noise, discomf, wbgt, rfu, batt) = struct.unpack('<hhhhhhhhhB', valueBinary) #単位変換した上でdict型に格納 self.sensorValue[macaddr] = { 'SensorType': 'EP', 'Temperature': temp / 100, 'Humidity': humid / 100, 'Light': light, 'UV': uv / 100, 'Pressure': press / 10, 'Noise': noise / 100, 'Discomfort': discomf / 100, 'WBGT': wbgt / 100, 'BatteryVoltage': (batt + 100) / 100 } # センサデータを取り出してdict形式に変換(IMモード時) def decodeSensorData_IM(self, valueStr, macaddr): #文字列からセンサデータ(6文字目以降)のみ取り出し、バイナリに変換 valueBinary = bytes.fromhex(valueStr[6:]) #バイナリ形式のセンサデータを整数型Tapleに変換 (temp, humid, light, uv, press, noise, accelX, accelY, accelZ, batt) = struct.unpack('<hhhhhhhhhB', valueBinary) #単位変換した上でdict型に格納 self.sensorValue[macaddr] = { 'SensorType': 'IM', 'Temperature': temp / 100, 'Humidity': humid / 100, 'Light': light, 'UV': uv / 100, 'Pressure': press / 10, 'Noise': noise / 100, 'AccelerationX': accelX / 10, 'AccelerationY': accelY / 10, 'AccelerationZ': accelZ / 10, 'BatteryVoltage': (batt + 100) / 100 } # センサデータを取り出してdict形式に変換 def _decodeSensorData(self, valueStr, macaddr): #文字列からセンサデータ(4文字目以降)のみ取り出し、バイナリに変換 valueBinary = bytes.fromhex(valueStr[4:]) #バイナリ形式のセンサデータを数値に変換 batt = valueBinary[2] & 0b01111111 isTemperatureAboveFreezing = valueBinary[4] & 0b10000000 temp = ( valueBinary[3] & 0b00001111 ) / 10 + ( valueBinary[4] & 0b01111111 ) if not isTemperatureAboveFreezing: temp = -temp humid = valueBinary[5] & 0b01111111 #dict型に格納 self.sensorValue[macaddr] = { 'SensorType': 'SwitchBot', 'Temperature': temp, 'Humidity': humid, 'BatteryVoltage': batt }
#!/usr/bin/python3 from bluepy import btle from env_broadcast import ScanDelegate import time while True: # 収集したいセンサのMACアドレスをリスト型で列挙(MACアドレスは小文字で記載) sensor_mac_list = ["c9:43:c3:XX:XX:XX","c5:d7:b1:XX:XX:XX"] # センサ値取得デリゲートを、スキャン時実行に設定 scanner = btle.Scanner().withDelegate(ScanDelegate(sensor_mac_list)) #スキャンしてセンサ値取得(タイムアウト10秒。10秒間で見つかったbluetoothデバイス全てにチェックを実施) scanner.scan(10.0) # 取得した値一覧 # print(scanner.delegate.sensorValue) if scanner.delegate.sensorValue is not None: for macaddr in sensor_mac_list: if scanner.delegate.sensorValue[macaddr]['SensorType'] == 'EP': print("update /XXXX/RRD/wxbeacon2.rrd " + str(int(time.time())) + ':' + str(scanner.delegate.sensorValue[macaddr]['BatteryVoltage']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Temperature']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Humidity']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Light']) + ':' + str(scanner.delegate.sensorValue[macaddr]['UV']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Pressure']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Noise']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Discomfort']) + ':' + str(scanner.delegate.sensorValue[macaddr]['WBGT']), flush=True) elif scanner.delegate.sensorValue[macaddr]['SensorType'] == 'IM':
print("update /XXXX/NoName.rrd " + str(int(time.time())) + ':' + str(scanner.delegate.sensorValue[macaddr]['BatteryVoltage']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Temperature']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Humidity']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Light']) + ':' + str(scanner.delegate.sensorValue[macaddr]['UV']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Pressure']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Noise']) + ':' + str(scanner.delegate.sensorValue[macaddr]['AccelerationX']) + ':' + str(scanner.delegate.sensorValue[macaddr]['AccelerationY']) + ':' + str(scanner.delegate.sensorValue[macaddr]['AccelerationZ']) + ':' + str(scanner.delegate.sensorValue[macaddr]['AccelerationX']), flush=True) elif scanner.delegate.sensorValue[macaddr]['SensorType'] == 'SwitchBot':
print('update /XXXX/RRD/swbot.rrd ' + str(int(time.time())) + ':' + str(scanner.delegate.sensorValue[macaddr]['Temperature']) + ':' + str(scanner.delegate.sensorValue[macaddr]['Humidity']) + ':' + str(scanner.delegate.sensorValue[macaddr]['BatteryVoltage']), flush=True) elif scanner.delegate.sensorValue.get[macaddr]['SensorType'] == 'None':
# 時間内にデバイスが見つからなかった pass else: # 想定外 print(scanner.delegate.sensorValue) time.sleep(50)
実行結果は下記の感じなので、rrdtoolが動いているサーバには「./bluetoothsensor.py |nc XXX.XXX.XXX.XXX 42217 >/dev/null &」な感じで飛ばしている。
$ ./bluetoothsensor.py update /XXXX/RRD/swbot.rrd 1629028238:26.4:66:100 update /XXXX/RRD/wxbeacon2.rrd 1629028238:2.91:19.63:100.0:0:0.01:1010.1:31.9:67.33:23.03
(8/15)追記
この記事では1回のSCANで1デバイスから取得しているが1回のSCANで複数のデバイスからデータ取得できるのを記事にした(URL)。
--------------
「Bluetooth気温計のデータをラズパイで取得しrrdtoolで描画できるようにした。」のセンサ接続とrrdtoolに繋げた部分について
@c60evaporatorさんの記事
を参考にして作成。
まずはラズパイ3の1台に各記事ごとに設定して動作の確認。
「2」の方の記事ではまったのは、
sudo install libglib2.0-dev
との記載は「sudo apt-get install libglib2.0-dev」 かと。てっきりripが抜けているのかとか悩んだ(linux,Python初心者なので勘がきかない)。
あとメインスクリプトの部分でMacアドレスを渡さないといけないのと、Macアドレスを小文字で書かないと駄目なのにはまる。
同一ディレクトリに、「1」の記事の「omron_env_broadcast.py」と「2」の記事の「switchbot.py」を放り込んで、両記事のメインスクリプトを合体させ、scanして見つからなかった際の処理を追加して出力をrrdtoolに都合の良いものに書き換えたbluetoothsensor.pyを放り込む。
(scanを1回で両方のデータを取れるようにできそうだけど、後日頑張ろう)
bluetoothsensor.py
#!/usr/bin/python3 from bluepy import btle from omron_env_broadcast import ScanDelegate from switchbot import SwitchbotScanDelegate import datetime import time while True: dt_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') sb_meter = { "kitchen":"c9:43:c3:xx:xx:xx"} #omron_env_broadcast.pyのセンサ値取得デリゲートを、スキャン時実行に設定 scanner = btle.Scanner().withDelegate(ScanDelegate()) #スキャンしてセンサ値取得(タイムアウト5秒) scanner.scan(5.0) if scanner.delegate.sensorValue is not None: #print(dt_now + "," + "OM, " + "update /home/XXXX/RRD/wxbeacon2.rrd " print("update /home/XXXX/RRD/wxbeacon2.rrd " + str(int(time.time())) + ":" + str(scanner.delegate.sensorValue['BatteryVoltage']) + ":" + str(scanner.delegate.sensorValue['Temperature']) + ":" + str(scanner.delegate.sensorValue['Humidity']) + ":" + str(scanner.delegate.sensorValue['Light']) + ":" + str(scanner.delegate.sensorValue['UV']) + ":" + str(scanner.delegate.sensorValue['Pressure']) + ":" + str(scanner.delegate.sensorValue['Noise']) + ":" + str(scanner.delegate.sensorValue['Discomfort']) + ":" + str(scanner.delegate.sensorValue['WBGT']), flush=True) ######SwitchBotの値取得###### for key in sb_meter: #switchbot.pyのセンサ値取得デリゲートを、スキャン時実行に設定 scanner = btle.Scanner().withDelegate(SwitchbotScanDelegate(sb_meter[key])) #スキャンしてセンサ値取得(タイムアウト5秒) scanner.scan(5.0) if scanner.delegate.sensorValue is not None: #print(dt_now + "," + "SW, " + "update /home/XXXXX/RRD/swbot.rrd " print("update /home/XXXX/RRD/swbot.rrd " + str(int(time.time())) + ":" + str(scanner.delegate.sensorValue['Temperature']) + ":" + str(scanner.delegate.sensorValue['Humidity']) + ":" + str(scanner.delegate.sensorValue['BatteryVoltage']), flush=True) time.sleep(50)
$ ./bluetoothsensor.py update /home/xxxxx/RRD/wxbeacon2.rrd 1628919666:2.92:21.5:99.0:76:0.02:999.7:35.89:70.63:24.46 update /home/xxxxx/RRD/swbot.rrd 1628919671:27.0:59:100 update /home/xxxxx/RRD/wxbeacon2.rrd 1628919726:2.92:21.5:99.03:81:0.02:999.8:31.58:70.63:24.46 update /home/xxxxx/RRD/swbot.rrd 1628919731:27.0:58:100
のようなものが1分ごとに出てきます。ラズパイ側にデータをためるつもりはないのでデータ保存兼画像作成のサーバ側でrrdcachedを立ち上げラズパイから渡します。
サーバはNetBSD 9.2なので、「pkgin install rrdtool 」して「usr/pkg/bin/rrdcached -p /var/run/rrdcached/rrdcached.pid -l xxx.xxx.xxx.xxx:42217 -U rrdcached -w 60」(-lの後のxxxの部分は自サーバのIFについているIPv4アドレス)で立ち上げる。
ラズパイ側から疎通確認のために「telnet xxx.xxx.xxx.xxx 42217」で繋いで、「HELP」とか打ち込んで、
$ telnet XXX.XXX.XXX.XXX 42217 Trying XXX.XXX.XXX.XXX... Connected to XXX.XXX.XXX.XXXX. Escape character is '^]'. HELP 22 Command overview UPDATE <filename> <values> [<values> ...] FLUSH <filename> FLUSHALL PENDING <filename> FORGET <filename> ....と出れば接続OK.QUITで抜ける。あとはラズパイ側でNC(NetCat)を利用して、「./bluetoothsensor.py |nc xxx.xxx.xxx.xxx 42217 &」とかで直接投げ込めば更新完了。 うまく行かない際はサーバ側のRRDファイルのパーミッションがユーザ「rrdcached」での書き込み権限があるか確認。 サーバ側で更新されているか確認は「rrdtool last RRDファイル名」などで最終更新日を確認する。以下にrrdtoolでのRRDの作成と画像のパラーメータを載せておく。 細かいオプションは「RRDtool 1.2系を使う」を参照の事。
rrdtool create wxbeacon2.rrd --start "00:00 07/01/2021" \ --step 60 \ --no-overwrite \ DS:Battery:GAUGE:600:0:U \ DS:Temperature:GAUGE:600:U:U \ DS:Humidity:GAUGE:600:0:100 \ DS:Light:GAUGE:600:0:U \ DS:UV:GAUGE:600:0:U \ DS:Pressure:GAUGE:600:0:U \ DS:Noise:GAUGE:600:0:U \ DS:Discomfort:GAUGE:600:U:U \ DS:HeatStrokeRisk:GAUGE:600:U:U \ RRA:MAX:0.5:1:5256000 rrdtool create swbot.rrd --start "00:00 07/01/2021" \ --step 60 \ --no-overwrite \ DS:Temperature:GAUGE:600:U:U \ DS:Humidity:GAUGE:600:0:100 \ DS:BatteryVoltage:GAUGE:600:0:U \ RRA:MAX:0.5:1:5256000 /usr/pkg/bin/rrdtool graph temp-2week.png -h 300 -w 500 -s 'now - 2weeks' \ DEF:a=/XXXX/RRD/usbrh.rrd:Temperature:MAX \ DEF:b=/XXXX/RRD/wxbeacon2.rrd:Temperature:MAX \ DEF:c=/XXXX/RRD/swbot.rrd:Temperature:MAX \ LINE2:a#FF0000:"usbrh(室内)\n" \ LINE2:b#00FF00:"wx2(庭)\n" \ LINE2:c#00FFFF:"SWbot(台所)" -t "気温" -v "degC" /usr/pkg/bin/rrdtool graph temp-2day.png -h 300 -w 500 -s 'now - 172800' \ DEF:a=/XXXX/RRD/usbrh.rrd:Temperature:MAX \ DEF:b=/XXXX/RRD/wxbeacon2.rrd:Temperature:MAX \ DEF:c=/XXXX/RRD/swbot.rrd:Temperature:MAX \ LINE2:a#FF0000:"usbrh(室内)\n" \ LINE2:b#00FF00:"wx2(庭)\n" \ LINE2:c#00FFFF:"SWbot(台所)" -t "気温" -v "degC" /usr/pkg/bin/rrdtool graph temp-60min.png -h 300 -w 500 -s 'now - 3600' \ DEF:a=/XXXX/RRD/usbrh.rrd:Temperature:MAX \ DEF:b=/XXXX/RRD/wxbeacon2.rrd:Temperature:MAX \ DEF:c=/XXXX/RRD/swbot.rrd:Temperature:MAX \ LINE2:a#FF0000:"usbrh(室内)\n" \ LINE2:b#00FF00:"wx2(庭)\n" \ LINE2:c#00FFFF:"SWbot(台所)" -t "気温" -v "degC"