RS485方式 風力・風向・照度計を作る

ブランド Qiilu
アマゾン購入価格¥5,259
5年位前に買ったもの再塗装

ブランド xuuyuu
アマゾン購入価格¥6,337

ブランド Jieoto
アマゾン購入価¥4,903
5年位前にRS485タイプの風速計と風向計を設置しました。
5年も経つとアルミ製でも表面塗装が剥がれて来ます、また風向計の方は内部基板も故障したようで
こちらは全面交換が必要になりました。
アマゾンで注文したところ風速計はRS485では無く0-10Vタイプが送られてきました。
外見は全く同じなのですが、どこで間違ったのか残念な結果でした。
返品するにも相手が中国では面倒ですので今回は諦めて古いものがまだ動作するので塗装をやり直し
て再度使用する事にしました。
ついでにRS485方式の照度計も取り付けることにしました。
回路図

CPUはもう大分古くなりましたが、安定のESP-WROOM-02を使用しました。
このCPUはもう5年以上IoTデバイスとしてメインに使用していますが、シンプル構造でトラブルも無
く消費電力も少ないことから重宝しています。
我が家の重要な給湯風呂システムの制御にも使っています、トラブルは殆ど無いです。
但し、古いので市場では収束気味な商品ではあります。
RS485 I/Fは、安価なものが多数出回っています。
使用したものは、120Ωの終端抵抗内蔵タイプでフロー制御は自動なのでCPUのポートを使用しなくて
済みます。(RS485基板の終端抵抗120ΩをONにするには、基板上のR0をショートする)
気を付けるべきところは、ESP-WROOM-02のTXはRS485基板のTXDにRXは基板のRXDに接続すると
ころです、クロスに接続してはいけません、私は間違えました。
DC12Vのアダプターから3端子レギュレーターで3.3Vを生成している関係から、GNDが信号ラインと共
通になりますが、RS485のケーブルは1.5m程度の短い距離の作りなのとCRC16でエラー管理をしてい
ますのであまりノイズの影響は無いと考えます。
ノイズ対策を強化したい場合やケーブルを長く引っ張る場合には、絶縁型DC-DCコンバーターに変更
して下さい。

購入先 秋月電子 ¥580

購入先 アマゾン¥149(10枚単位での販売)
Windows PCを使用して機器の事前チェック
RS485機器には、スレーブアドレスという固有の番号が振られています。
この番号が重複していると通信が出来ないので最初必ず確認が必要です。
また、今回のように同じRS485ラインに3つの機器がぶら下がっている場合には、このスレーブアドレ
スが重複しないよう考えて設定し、かつ先頭機器(RS485基板) 及び終端機器(照度計)には終端抵抗120Ωを配置する必要があります。
今回のような小規模設備の場合、終端抵抗が無くても一応動作はしますが、ちゃんとしたい場合には
省略しないで規定に従いましょう。
スレーブアドレスは、機器の購入時の設定値は特に決まったことでは無いので通常1番か2番が設定さ
れているようです。
これを変更して今回のように風力計->1 風向計->2 照度計->3としたい場合には、WindowsPCなどにUS
BタイプのRS485 I/Fを使ってアドレスの書き換えを行う必要があります。

購入先 アマゾン 購入価格¥2,699
まず風力計の付属ケーブルの黄色をI/FのA+ へ青色をI/FのB-へ 接続します。
RS485 I/Fとの接続は以上です、他の端子は使いません。
RS485 I/Fの+5V端子には何も接続しません。
+3.3VはPC本体のUSB端子経由で供給されています。
RS485機器の方は、DC12Vが必要ですので+12Vを風力計の茶色ケーブルへ供給します。
12Vのマイナス線は風力計の黒色ケーブルに接続します。
12V電圧は、こうゆう小電圧機器にとっては危険なので電源は最後の最後に十分に確認して
から通電するように心がけて下さい、間違うと一瞬でWindowsPCを含めて全損規模の故障になってしま
いますのでくれぐれも注意して下さい。
PCのUSB端子に差し込む前にPC側にRS485用のソフトをインストールします。
「ヒューマンデータ」さんのHPに「Modbus Tool for Debug 」というのが掲載されていて使用して良いと書かれていますの有難く使わせて頂きます、とても使いやすいソフトだと思います。
https://www.fa.hdl.co.jp/jp/html/app/Modbus_Tool/index.html
このソフトの機能を使って風力計にコマンドを送りますが、最低限コマンドの仕様を知っていないと
使えませんのでここでは、最低限必要なコマンドの内容を説明します。
通信方式は、「MODBUS-RTUプロトコル」といいます。
長距離、複数台接続、CRCエラー検知などに対応した方式です。
全部を習得するには複雑で大変すが、風力計などをDIYで設置する場合などでは、必要最低限知ってい
れば問題ありません。
必要最低限必要なコマンドとアドレスなど
●アナログデーター読み取り用
1、アナログデーター(風力の値0~30)の読み取りコマンド 03 (Read Holding Registers)
2、スレーブアドレス 風力計->1 風向計->2 照度計->3
3、上記のコマンド送出に必要なデーター格納アドレスの値
風力計->0016h(16進) 風向計->0017h(16進) 照度計->0000h(16進)
4、データーの数 すべての機器->0001h(16進)
5、CRC-16値 (数値の並びが下位、上位の順になっているので注意 リトルエンディアンという並び)
風力計->CE 65 風向計->3D 34 照度計->E8 85
CRC-16値は、送信したバイト、今回の場合先頭から7バイトの値を元に決められた多項式で計算して
決められます、詳しくは後で説明します。
「Modbus Tool Debug」を使って送信する際には、CRC-16は自動計算されますので入力は不要です。
このアナログデーター読み込みコマンド03hを送信すると機器側から7バイトの返信が帰ってきます。
例えば、回転0の風力計に「01 03 00 16 00 01 65 CE」を送信した場合には「01 03 0200 00 B8 44」が
返送されます。
この場合、01->スレーブアドレス 03->受信したコマンド 02->この後に続く返送するデーターの数
00 00->返送データー2バイト B8 44->CRC-16値(下位、上位の順)
となります、したがって0から数えて3番目と4番目に方向を示すデーター(この場合回転していないので
00 00)がある事が分かります。
試しに少し回転しながらコマンドを送ると、値が増えているのが確認出来ます。
●単一レジスタ書き込み用
上記の事が分かれば、スクリプトは組めますが、その前に買ったばかりだとスレーブアドレスが全部1
とかになっていますのでこれを変更しないといけません、その時に使用するコマンドが以下の06(Wr
ite Single Registers」コマンドです。
問題は、希望するスレーブアドレスを書き込むアドレスですが、これは機器のメーカーによってバラ
バラのようです。
ですから説明書が一切ないと探すのが大変です。
幸い今回の機器については説明書内に記載がありました。
風力計と風向計ではアドレス0x0100hでした。
照度計はアドレス0x0066hでした。
スレーブアドレスを変更するコマンドは以下のようになります、この場合、買った時に設定されてい
たアドレスが1の時これを2に変更するコマンドです、今回の場合風向計が該当します。
「01 06 01 00 00 02 09 F7」
01->現在のスレーブアドレス値 06->書き込みコマンド 01 00->書き込みアドレス100h 00 02->
設定するスレーブアドレスの値 、09 F7->CRC-16値
実際には、書き換えは「Modbus Tool Debug」で行いますのでCRC-16値は自動で計算して付加されま
すので入力が必要な部分は01 06 01 00 00 02迄です。
この場合の返信値は、送信値と同じものが返されます。
照度計は、1を3に変更します。
「01 06 00 66 00 03 E8 14」実際には、書き換えは「Modbus Tool Debug」で行いますのでCRC-16値
は自動で計算して付加されます、入力が必要な部分は01 06 00 66 00 03迄です。
その他、転送ボーレートは最初9600ですが、他に2400、4800に変更できます、アドレスは0101H。
但し、設定しても反映するまで時間がかかったり、何か動作が怪しいので必要が無い限り触らない
方がいいと思います、最悪通信出来なくなりますので。
Modbus Tool for Debugの起動
以上迄理解した上でRS485 I/FをPCのUSB端子に接続し、更に12Vの電源を入れます。
そしてPCの「Modbus Tool for Debug 」ソフトを起動します。


次にその下にある部分でデバイスを指定して「OPEN」ボタンをクリックします。


例えば、回転0の風向計に「01 03 00 16 00 01 65 CE」を送信した場合には「01 03 0200 00 B8 44」が返送されます。
右の一番上の枠が「Read Holding Registers(0x03)になっていることを確認して、その下
のスレーブに01 開始アドレスに0016 データーサイズに0001をセットして「送信クエリ
生成」ボタンをクリックします。
そうすると左の「送信クエリ」の右枠に 01 03 00 16 00 01 と自動生成されます。
CRC-16の65 CEは自動的に計算添付されますので「送信」ボタンをクリックすると送信
されます。

「送受信データ」のタブをクリックすると、Tx Rxで送受信のデーターを確認できます。
受信データーとして 01 03 02 00 00 B8 44 を確認出来ます。
今、風力計は回転していないのでデーターは00 00です。
風力計を少し回転させてからもう一度「送信」ボタンをクリックするとこの部分の値が
増えていることが確認出来ます。
風力計では、この部分の値を読み取ればいいわけですね。
次に目的の作業である風向計と照度計のスレーブアドレスを変更したいと思います。
現在はどちらも1になっていてこのままだと動きません。
風向計->2 照度計->3 に変更します。
風力計を取り外してください。
先に12Vラインを外し、次にRS485をUSBから引き抜きます。
風向計をRS485 I/Fにセットします。
「Modbus Tool for Debug 」ソフトを起動してRS232Cポートを先ほどと同様にセット
します

右上の「ファンクションコード」の下にある枠内をクリックして「Write Single Registaer(0x06)」
を表示させてください。
風向計のスレーブアドレスを1から2に変更するコマンドは、01 06 01 00 00 02 09 F7です。
01 06 01 00 00 02のみ入力します。
送信クエリ生成ボタンをクリックして、送信ボタンをクリックします。
これでスレーブアドレスは2に変更されました。
確認の為、以下のデーターリードコマンドを今度はスレーブ2に対して送信して読出しできるかを確
認します。

03 02 00 17 00 01 34 3D を送信すると正常に返事が返ってきましたのでスレーブは2に
書き換わっています。
この場合、スレーブアドレスを書き込んだ0x0100hを0x03コマンドで読み込んでも確認
出来ます。
今度は、照度計のスレーブアドレスを1から3に書き換えます。
RS485 I/Fの接続も必ず一旦全部USBから引き抜いてからもう一度行うようにして下さい
「Modbus Tool for Debug 」ソフトも必ず一旦終了して再度立ち上げ再度RS232Cの設定
も行ってください。

照度計は、スレーブアドレスの書き込みアドレスが0x0066hです。
従って現在のスレーブアドレス1から3に書き換えるコマンドは、01 06 00 66 00 03 E8 14
になります、01 06 00 66 00 03 まで打ち込んで送信します。

書き換わったかどうか確認します。
コマンド 03 03 00 00 00 01 8E8 のコマンドを送信します。
03 03 02 00 C5 01 D7 が返送されました。
00 C5 の部分が照度の値(16進)です。
さあここ迄分かればあとは実際のESP-WROOM-02スクリプトになります。
以下の様になります。
RS485式 風力計・風向計・照度計スクリプト ESP-WROOM-02用
説明
風速データーはレジスタ読出し値を1/10した数値になります。(付属取扱説明書に記載有)
風速は現在の瞬間値の他
3秒平均
10分平均
10分最大瞬間
を計測する仕組みです。
計測した数値は、MQTTでブローカー宛て送信します。
JSON形式サンプル送信データー記述例
{
“wind_now”: 3.4,
“wind_3s”: 3.1,
“wind_10min”: 2.8,
“wind_max_10min”: 6.2,
“wind_dir”: 180,
“wind_dir_str”:”S”,
“lux”: 12345
}
屋根に設置した後で風向計の向きが微妙に実方向と異なっている事に気が付きました。
また屋根に上るのはやだなあと思った瞬間閃きました。
そうだコマンドで向きを修正できるようにすれば良いんだと。
結果この機能も組み込みました、wind_dir_strの部分が風向計から送られてくる向きの情報です。
この方向が設置場所の実際の方向と違っている場合には、NODE-REDからコマンドで角度
修正出来るようにしてあります。
ですから設置時には適当に設置しやすい向きに取り付けて構いません。
const char* mqtt_server = “192.168.1.200”;自身のMQTTサーバーアドレスに変えて下さい。
WiFi.config(IPAddress(192, 168, 1, 100), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
自身の希望するIPアドレス、デフォルトゲートウエイ、サブネットマスクを入れて下さい。
ESP-WROOM-02のWifi IPアドレスを決め打ちします。
#define RTS 14 RS485の送受をRTSピンを使って行う場合には、ここのコメントアウトを外します。
RS485仕様 風速計・風向計・照度計 スクリプト
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <EEPROM.h>
#define MODBUS_RESP_LEN 7
#define WIFI_SSID "your_ssid"
#define WIFI_PWD "your_passwd"
const char* mqtt_server = "192.168.1.200";
#define LED 16
#define SAMPLE_INTERVAL 2000
#define TEN_MINUTES 600000
#define MAX_SAMPLES 300
#define EEPROM_SIZE 32
#define ADDR_WINDDIR_OFFSET 0
WiFiClient espClient;
PubSubClient client(espClient);
float wind_samples[MAX_SAMPLES];
int sample_index = 0;
unsigned long last_10min_reset = 0;
float max_gust_10min = 0;
int winddir_offset = 0;
const char* dir16[]={
"N","NNE","NE","ENE",
"E","ESE","SE","SSE",
"S","SSW","SW","WSW",
"W","WNW","NW","NNW"
};
void callback(char* topic, byte* payload, unsigned int length){
String msg="";
for(unsigned int i=0;i<length;i++) msg+=(char)payload[i];
msg.trim();
String t=String(topic);
if(t=="wind/dir/cal"){
int val=msg.toInt();
if(val>=-180 && val<=180){
winddir_offset=val;
EEPROM.put(ADDR_WINDDIR_OFFSET,winddir_offset);
EEPROM.commit();
client.publish("wind/dir/response","OK");
}
}
if(t=="wind/dir/cal/get"){
char buf[32];
sprintf(buf,"%d",winddir_offset);
client.publish("wind/dir/cal",buf);
}
}
void reconnect(){
while(!client.connected()){
String clientId="ESP8266Client-";
clientId+=String(random(0xffff),HEX);
if(client.connect(clientId.c_str())){
client.subscribe("wind/dir/cal");
client.subscribe("wind/dir/cal/get");
}
delay(500);
}
}
uint16_t modbusCRC(const uint8_t *buf,uint8_t len){
uint16_t crc=0xFFFF;
for(uint8_t pos=0;pos<len;pos++){
crc^=(uint16_t)buf[pos];
for(uint8_t i=0;i<8;i++){
if(crc & 0x0001){
crc >>=1;
crc ^=0xA001;
}else crc >>=1;
}
}
return crc;
}
bool readModbus(uint8_t *request,uint8_t *response,bool check_crc){
Serial.write(request,8);
Serial.flush();
delay(20);
size_t n=Serial.readBytes(response,MODBUS_RESP_LEN);
if(n!=MODBUS_RESP_LEN) return false;
if(check_crc){
uint16_t crc_calc=modbusCRC(response,5);
uint16_t crc_recv=response[5] | (response[6]<<8);
if(crc_calc!=crc_recv) return false;
}
return true;
}
void setup(){
pinMode(LED,OUTPUT);
digitalWrite(LED,LOW);
Serial.begin(9600);
Serial.setTimeout(200);
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(ADDR_WINDDIR_OFFSET,winddir_offset);
if(winddir_offset<-180 || winddir_offset>180)
winddir_offset=0;
WiFi.config(IPAddress(192,168,1,100),
IPAddress(192,168,1,1),
IPAddress(255,255,255,0));
WiFi.begin(WIFI_SSID,WIFI_PWD);
while(WiFi.status()!=WL_CONNECTED) delay(1000);
client.setServer(mqtt_server,1883);
client.setCallback(callback);
last_10min_reset=millis();
}
void loop(){
digitalWrite(LED,HIGH);
if(!client.connected()) reconnect();
client.loop();
uint8_t WindMeter_buf[MODBUS_RESP_LEN];
uint8_t Winddir_buf[MODBUS_RESP_LEN];
uint8_t Illumina_buf[MODBUS_RESP_LEN];
byte WindMeter_request[]={0x01,0x03,0x00,0x16,0x00,0x01,0x65,0xCE};
byte Winddir_request[]={0x02,0x03,0x00,0x17,0x00,0x01,0x34,0x3D};
byte Illumina_request[]={0x03,0x03,0x00,0x00,0x00,0x01,0x85,0xE8};
bool ok_wind=readModbus(WindMeter_request,WindMeter_buf,true);
delay(50);
bool ok_dir=readModbus(Winddir_request,Winddir_buf,true);
delay(50);
bool ok_lux=readModbus(Illumina_request,Illumina_buf,true);
delay(50);
float wind=0;
float wind_3s=0;
float wind_10min=0;
uint16_t dir=0;
uint16_t lux=0;
if(ok_wind){
uint16_t raw=(WindMeter_buf[3]<<8)|WindMeter_buf[4];
wind=raw/10.0;
if(wind>max_gust_10min) max_gust_10min=wind;
wind_samples[sample_index]=wind;
sample_index++;
if(sample_index>=MAX_SAMPLES) sample_index=0;
if(sample_index>=2){
wind_3s=(wind_samples[sample_index-1]
+wind_samples[sample_index-2])/2.0;
}else wind_3s=wind;
float sum=0;
int count=0;
for(int i=0;i<MAX_SAMPLES;i++){
if(wind_samples[i]>0){
sum+=wind_samples[i];
count++;
}
}
wind_10min=(count>0)?sum/count:0;
if(millis()-last_10min_reset>TEN_MINUTES){
max_gust_10min=0;
last_10min_reset=millis();
}
}
if(ok_dir){
dir=(Winddir_buf[3]<<8)|Winddir_buf[4];
dir=(dir+winddir_offset+360)%360;
}
if(ok_lux){
uint16_t raw=(Illumina_buf[3]<<8)|Illumina_buf[4];
lux=raw*100/1.21;
}
int index=(dir+11)/22.5;
index%=16;
char payload[256];
snprintf(payload,sizeof(payload),
"{\"wind_now\":%.1f,"
"\"wind_3s\":%.1f,"
"\"wind_10min\":%.1f,"
"\"wind_max_10min\":%.1f,"
"\"wind_dir\":%u,"
"\"wind_dir_str\":\"%s\","
"\"lux\":%u}",
wind,
wind_3s,
wind_10min,
max_gust_10min,
dir,
dir16[index],
lux);
client.publish("wind",payload);
digitalWrite(LED,LOW);
delay(SAMPLE_INTERVAL);
}CRC-16に関して補足
Modbus RTU通信において、メッセージの誤り検出(エラーチェック)に使用されるCRC-16 (Cyclic Redundancy Check)は、16ビットのデータを用いて通信データの信頼性を保証する仕組みです
実際のプログラミングでCRC-16値を知る場合には、先の「Modbus Tool for Debug 」を使って知る
事も出来ますが、以下のPythonスクリプトを使用しても計算出来ます。
「Jupyter notebook」で動くPythonスクリプトを掲載しておきます。
今回風力計に最初送信したコマンド 01 03 00 16 00 01 65 CE の計算例を挙げておきます。
!pip install crcmod
import crcmod.predefined
# Modbus (CRC-16/MODBUS) の定義
modbus_crc_func = crcmod.predefined.mkCrcFun('modbus')
# データ(バイト列)
data = bytes([0x01, 0x03, 0x00, 0x16, 0x00,0x01])
# CRC計算
crc_value = modbus_crc_func(data)
print(f"データ: {data.hex()}")
print(f"計算されたCRC: {crc_value:04x}") # リトルエンディアンで表示 上記の実行結果
データ: 010300160001
計算されたCRC: ce65




購入先 アマゾン 購入価格 ¥1,399(6個)

アマゾン 10個入り 購入先 アマゾン
購入価格 ¥1,640

購入先 アマゾン 購入価格 ¥1,990

ステンレス板 特注品
購入先 鋼屋 ¥2,156

4.2mm アマゾン¥783


切削オイル 800
アマゾン¥597

風速計取付台を製作する為の道具類。
60mmのステンレス円台に4か所M5のタップを切って固定する。
鉄製だと簡単だが、すぐ錆びてしまうのでステンレス製で作った。
ステンレス板厚さ6mmにタップを切るのは素人には簡単ではない。
また最終的に取付アーム(鉄製)に溶接固定するが、アームの鉄板が1mmあるかないかの薄い板なので
厚さ6mmのステンレス板との溶接はかなり難しい作業である。
別にこだわりが無ければ別のもっと簡単な方法で固定しても良い。私はあえてハードルを上げてかっこよく製作してみた。



NODE-RED側での作業
NODE-RED ダッシュボード
風速計・風向計・照度計で計測したデーターは、1.5m程度の位置にあるCPU処理用ボックス内からMQ
TT送信されます。
NODE-RED 上に設けたMQTT ブローカー経由で受信し、ダッシュボード上に常時表示し、必要に応じてGoogle HOMEで音声報告します。
まずは、ダッシュボードに表示する手順をご説明します。
事前にRaspberry Pi4やPi5にNODE-RED とMQTTブローカーを下記の記事を参考に設定して準備します。
https://marginalvillage.com/page/4/
NODE-REDで右上にある三のアイコンをクリックして「パレットの管理」を開き
「ノードを追加」で「@flowfuse/node-red-dashboard」を追加して下さい。
ちょっと前までは通常「node-red-dashboard」を使っていましたが、最近不具合が発生しております。
具体的には、私はダッシュボードが開かなる現象を経験しました。
正常動作する場合も多いので厄介です。
現在「非推奨」マークが付いています。
新しいダシュボード2は今までと使い方が違うので慣れていない私には正直使いにくいと思います。
風速計・風向計・照度計のフロー

データーはJSON式のかたまりで送信されて来ます。
個々の情報を取り出す際にはまずJSONノードを置き「動作」の項目を「常にjavaScriptオブジェクト
に変換」に設定します。
その後にfunctionノードを置き「var temp = msg.payload.wind_now);」のように必要な
情報を引き出して後に続けます。
下方にある「風向計 設置調整」ボタンは、設置時に必要があれば1回のみ使用するボタンです。
設置時の向きが正しい方向に無くても、あとで正しい方向になるようNODE-RED上から修正角度の数
値を送信して調整する仕組みです。
入力する数値は、-180度から180度までの数値を文字で入力します。(例:45)
一度合わせれば内部EEPROMに記憶され再起動してもメモリーから読みだしてセットしてくれます。
その下方にあるのは、Google HOMEの音声で風速や照度を定時に報告する仕組みです。
日本語の他、英語音声でも可能です。
Google Castは通常200文字が制限なので風速は「10分間の最大風速」の報告のみです。
200文字以上をしゃべらせる方法はあるのですが、音声で風速を長々聞いているのもうっとうしい
ので今回は標準機能のままです。
200文字を超える文字を送信すると完全無視されますので注意して下さい。
何処か故障してセンサーデーターが読みだせない場合には「OOセンサーが故障しています、点検
して下さい」の音声が報告されます。
サーバーアドレスが、架空の192.168.1.200 Port 1883 になっていますので自分のアドレスに変更し
て下さい。
同様にGoogle HOME IPアドレスは自分のものに変更して下さい。
[{"id":"18ff97e3268aa567","type":"tab","label":"風向・風力・降雨計","disabled":false,"info":""},{"id":"63bc11eab9683723","type":"mqtt in","z":"18ff97e3268aa567","name":"","topic":"wind","qos":"2","datatype":"auto","broker":"3ae7380911f0152a","nl":false,"rap":false,"inputs":0,"x":110,"y":120,"wires":[["deb7bf4c9ede69e3"]]},{"id":"3215404f5bf80291","type":"comment","z":"18ff97e3268aa567","name":"現在の風速","info":"","x":960,"y":120,"wires":[]},{"id":"cb7f8a3d50b67d97","type":"function","z":"18ff97e3268aa567","name":"wind_dir (風向画像変換)","func":"var winddir = msg.payload.wind_dir_str;\nif(winddir != \"\"){\nglobal.set(\"wind_str\",winddir);\nglobal.set(\"wind_dir_update_flg\",true);\n}\nswitch(winddir){\n case \"N\":\n msg.payload = \"/dir/wind_n.png\";\n break;\n case \"NNE\":\n msg.payload = \"/dir/wind_nne.png\";\n break; \n case \"NE\":\n msg.payload = \"/dir/wind_ne.png\";\n break;\n case \"ENE\":\n msg.payload = \"/dir/wind_ene.png\";\n break;\n case \"E\":\n msg.payload = \"/dir/wind_e.png\";\n break;\n case \"ESE\":\n msg.payload = \"/dir/wind_ese.png\";\n break;\n case \"SE\":\n msg.payload = \"/dir/wind_se.png\";\n break;\n case \"SSE\":\n msg.payload = \"/dir/wind_sse.png\";\n break;\n case \"S\":\n msg.payload = \"/dir/wind_s.png\"; \n break;\n case \"SSW\":\n msg.payload = \"/dir/wind_ssw.png\"; \n break;\n case \"SW\":\n msg.payload = \"/dir/wind_sw.png\";\n break;\n case \"WSW\":\n msg.payload = \"/dir/wind_wsw.png\";\n break;\n case \"W\":\n msg.payload = \"/dir/wind_w.png\";\n break;\n case \"WNW\":\n msg.payload = \"/dir/wind_wnw.png\";\n break;\n case \"NW\":\n msg.payload = \"/dir/wind_nw.png\";\n break;\n case \"NNW\":\n msg.payload = \"/dir/wind_nnw.png\";\n break;\n}\n\nreturn msg\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":300,"wires":[["cf0e96dac39f2cca"]]},{"id":"eae597e8bfd2abb0","type":"comment","z":"18ff97e3268aa567","name":"風向計","info":"","x":350,"y":300,"wires":[]},{"id":"56075074c9c741f9","type":"comment","z":"18ff97e3268aa567","name":"照度計","info":"","x":350,"y":360,"wires":[]},{"id":"deb7bf4c9ede69e3","type":"json","z":"18ff97e3268aa567","name":"","property":"payload","action":"obj","pretty":false,"x":370,"y":120,"wires":[["fa50b45e50c7ffc2","cb7f8a3d50b67d97","e7f94f7a77a15b5f","060cc5113fe6e002","586e7472e698b186","00a92e0d4ad081a3"]]},{"id":"fa50b45e50c7ffc2","type":"function","z":"18ff97e3268aa567","name":"wind_now","func":"var temp = msg.payload.wind_now;\nmsg.payload = temp + \" m/s\";\nglobal.set(\"wind_now\",temp);\nglobal.set(\"wind_now_update_flg\",true);\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":120,"wires":[["c9c5abcb5659ef19"]]},{"id":"e7f94f7a77a15b5f","type":"function","z":"18ff97e3268aa567","name":"lux","func":"var temp = msg.payload.lux;\nmsg.payload = temp + \" lux\";\nglobal.set(\"lux\",temp);\nglobal.set(\"lux_update_flg\",true);\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":360,"wires":[["d0f491f57fa0c4b6"]]},{"id":"060cc5113fe6e002","type":"function","z":"18ff97e3268aa567","name":"wind_3s","func":"var temp = msg.payload.wind_3s;\nmsg.payload = temp + \" m/s\";\nglobal.set(\"wind_3s\",temp);\nglobal.set(\"wind_3s_update_flg\",true);\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":160,"wires":[["df8958df029fd9ee"]]},{"id":"586e7472e698b186","type":"function","z":"18ff97e3268aa567","name":"wind_10min","func":"var temp = msg.payload.wind_10min;\nmsg.payload = temp + \" m/s\";\nglobal.set(\"wind_10min\",temp);\nglobal.set(\"wind_10min_update_flg\",true);\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":200,"wires":[["4202ba205e924c08"]]},{"id":"00a92e0d4ad081a3","type":"function","z":"18ff97e3268aa567","name":"wind_max_10min","func":"var temp = msg.payload.wind_max_10min;\nmsg.payload = temp + \" m/s\";\nglobal.set(\"wind_max_10min\",temp);\nglobal.set(\"wind_max_10min_update_flg\",true);\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":570,"y":240,"wires":[["dad9bcd9e21e7a9c"]]},{"id":"5dca5f9f4fe8a72f","type":"comment","z":"18ff97e3268aa567","name":"3秒間での平均風速","info":"","x":990,"y":160,"wires":[]},{"id":"8bdde5559bbeb79c","type":"comment","z":"18ff97e3268aa567","name":"10分間での平均風速","info":"","x":990,"y":200,"wires":[]},{"id":"7269b2f3afcc1925","type":"comment","z":"18ff97e3268aa567","name":"10分間での最大風速","info":"","x":990,"y":240,"wires":[]},{"id":"d717a6e21c30f31b","type":"inject","z":"18ff97e3268aa567","name":"風向計 設置調整","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"180","payloadType":"str","x":170,"y":600,"wires":[["accbbd626300f162"]]},{"id":"accbbd626300f162","type":"mqtt out","z":"18ff97e3268aa567","name":"","topic":"wind/dir/cal","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"3ae7380911f0152a","x":390,"y":600,"wires":[]},{"id":"de05a6224324b470","type":"comment","z":"18ff97e3268aa567","name":"val = -180~180","info":"","x":140,"y":560,"wires":[]},{"id":"c9c5abcb5659ef19","type":"ui-text","z":"18ff97e3268aa567","group":"3f08f5c0d85abc7f","order":2,"width":4,"height":1,"name":"瞬間風速","label":"瞬間風速","format":"{{msg.payload}}","layout":"row-spread","style":true,"font":"Arial,Arial,Helvetica,sans-serif","fontSize":"24","color":"#ffffff","wrapText":false,"className":"","value":"payload","valueType":"msg","x":780,"y":120,"wires":[]},{"id":"cf0e96dac39f2cca","type":"ui-template","z":"18ff97e3268aa567","group":"3f08f5c0d85abc7f","page":"","ui":"","name":"風向表示","order":1,"width":2,"height":0,"head":"","format":"<template>\n<img :src=\"msg.payload\"\n alt=\"icon\" \n width=\"50\"\n height=\"50\"\n style=\"float: left; margin-right: 15px; margin-bottom: 10px;\"\n />\n</template>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":800,"y":300,"wires":[[]]},{"id":"d0f491f57fa0c4b6","type":"ui-text","z":"18ff97e3268aa567","group":"3f08f5c0d85abc7f","order":7,"width":4,"height":1,"name":"照度計","label":"屋外照度","format":"{{msg.payload}}","layout":"row-spread","style":true,"font":"Arial,Arial,Helvetica,sans-serif","fontSize":"24","color":"#ffffff","wrapText":false,"className":"","value":"payload","valueType":"msg","x":790,"y":360,"wires":[]},{"id":"df8958df029fd9ee","type":"ui-text","z":"18ff97e3268aa567","group":"3f08f5c0d85abc7f","order":3,"width":4,"height":1,"name":"3秒間平均風速","label":"3秒間平均風速","format":"{{msg.payload}}","layout":"row-spread","style":true,"font":"Arial,Arial,Helvetica,sans-serif","fontSize":"24","color":"#ffffff","wrapText":false,"className":"","value":"payload","valueType":"msg","x":800,"y":160,"wires":[]},{"id":"4202ba205e924c08","type":"ui-text","z":"18ff97e3268aa567","group":"3f08f5c0d85abc7f","order":4,"width":4,"height":1,"name":"10分間平均風速","label":"10分間平均風速","format":"{{msg.payload}}","layout":"row-spread","style":true,"font":"Arial,Arial,Helvetica,sans-serif","fontSize":"24","color":"#ffffff","wrapText":false,"className":"","value":"payload","valueType":"msg","x":800,"y":200,"wires":[]},{"id":"dad9bcd9e21e7a9c","type":"ui-text","z":"18ff97e3268aa567","group":"3f08f5c0d85abc7f","order":5,"width":4,"height":1,"name":"10分間最大風速","label":"10分間最大風速","format":"{{msg.payload}}","layout":"row-spread","style":true,"font":"Arial,Arial,Helvetica,sans-serif","fontSize":"24","color":"#ffffff","wrapText":false,"className":"","value":"payload","valueType":"msg","x":800,"y":240,"wires":[]},{"id":"a282704ecb61315e","type":"function","z":"18ff97e3268aa567","name":"データー読み込み、言語選択、送出","func":"var number = msg.payload;\nvar data =\"\";\nvar wind_now = \"\";\nvar wind_3s = \"\";\nvar wind_10min = \"\";\nvar wind_max_10min =\"\";\n\nvar dir_ja = \"\";\nvar dir_en =\"\";\nvar lux =\"\";\n\nlet wind_speed_bad = false;\nlet wind_dir_bad = false;\nlet lux_bad = false;\n\nlet wind_now_update_flg = global.get(\"wind_now_update_flg\");\nlet wind_3s_update_flg = global.get(\"wind_3s_update_flg\");\nlet wind_10min_update_flg = global.get(\"wind_10min_update_flg\");\nlet wind_max_10min_update_flg = global.get(\"wind_max_10min_update_flg\");\nlet wind_dir_update_flg = global.get(\"wind_dir_update_flg\");\nlet lux_update_flg = global.get(\"lux_update_flg\");\n\nif(!wind_now_update_flg || !wind_3s_update_flg || !wind_max_10min_update_flg){\n wind_speed_bad = true;\n }else{\n wind_now = global.get(\"wind_now\");\n wind_3s = global.get(\"wind_3s\");\n wind_10min = global.get(\"wind_10min\");\n wind_max_10min = global.get(\"wind_max_10min\");\n }\nif(!wind_dir_update_flg){\n wind_dir_bad = true;\n }else{\n var winddir = global.get(\"wind_str\");\n switch(winddir){\n case \"N\":\n dir_ja = \"北の風 \";\n dir_en =\"It's the north wind. \";\n break;\n case \"NNE\":\n dir_ja = \"北北東の風 \";\n dir_en =\"North-northeast wind. \";\n break; \n case \"NE\":\n dir_ja = \"北東の風 \";\n dir_en =\"It's a northeast wind. \";\n break;\n case \"ENE\":\n dir_ja = \"東北東の風 \";\n dir_en =\"The wind is from the northeast east. \";\n break;\n case \"E\":\n dir_ja = \"東の風 \";\n dir_en =\"It's the east wind. \";\n break;\n case \"ESE\":\n dir_ja = \"東南東の風 \";\n dir_en =\"The wind is from the southeast. \";\n break;\n case \"SE\":\n dir_ja = \"南東の風 \";\n dir_en =\"It's a southeasterly wind. \";\n break;\n case \"SSE\":\n dir_ja = \"南南東の風 \";\n dir_en =\"The wind is from the south-southeast. \";\n break;\n case \"S\":\n dir_ja = \"南の風 \" ;\n dir_en =\"It's a south wind. \"; \n break;\n case \"SSW\":\n dir_ja= \"南南西の風 \";\n dir_en =\"\"; \n break;\n case \"SW\":\n dir_ja = \"南西の風 \";\n dir_en =\"The wind is from the south-southwest. \";\n break;\n case \"WSW\":\n dir_ja = \"西南西の風 \";\n dir_en =\"The wind is from the southwest. \";\n break;\n case \"W\":\n dir_ja = \"西の風 \";\n dir_en =\"It's a west wind. \";\n break;\n case \"WNW\":\n dir_ja = \"西北西の風 \";\n dir_en =\"The wind is from the northwest. \";\n break;\n case \"NW\":\n dir_ja = \"北西の風 \";\n dir_en =\"It's a northwest wind. \";\n break;\n case \"NNW\":\n dir_ja = \"北北西の風 \";\n dir_en =\"The wind is from the north-northwest. \";\n break;\n}\n \n }\n\nif(!lux_update_flg){\n lux_bad = true;\n}else{\n lux = global.get(\"lux\");\n}\n\nlet date = new Date();\n\nvar hour = date.getHours();\n\n\nif(number == \"1\"){\n msg.language = \"ja\";\n if (wind_speed_bad) { data = \" 風速計センサーが機能していません、点検して下さい。 \"; } else {\n data = \"現在の時刻は \" + hour + \" 時です。\" +\n //\"瞬間風速は \" + wind_now + \" メートル毎秒です。 \" +\n //\"3秒間平均風速は \" + wind_3s + \" メートル毎秒です。 \" +\n //\"10分間平均風速は \" + wind_10min + \" メートル毎分です。 \" +\n \"10分間最大風速は \" + wind_max_10min + \" メートル毎分です。 \" ;\n }\n \n if (wind_dir_bad) {\n data = data + \"風向計センサーが機能していません、点検して下さい。 \";\n }else{\n data = data + \"現在の風向は \" + dir_ja + \" です。 \";\n }\n\n if (lux_bad) {\n data = data + \"照度計センサーが機能していません、点検して下さい。 \";\n } else {\n data = data + \"現在の照度は \" + lux + \" ルックスです。 \" ;\n } \n\n}\n\nif(number == \"2\"){\n msg.language = \"en\";\n if (wind_speed_bad) { data = \"The anemometer sensor is not functioning, please check. \"; } else {\n data = \"The current time is \" + hour + \" o'clock. \" +\n //\"The instantaneous wind speed is \" + wind_now + \" meters per second. \" +\n //\"The average wind speed for 3 seconds is \" + wind_3s + \" meters per seond. \" +\n //\"The 10-minute average wind speed is \" + wind_10min + \" meters per minutes. \" +\n \"The maximum 10-minute wind speed is \" + wind_max_10min + \" meters per minutes. \" ;\n }\n\n if (wind_dir_bad) {\n data = data + \"Wind vane sensor not working, please check. \";\n } else {\n data = data + \"The current wind direction is \" + dir_en + \". \";\n }\n\n if (lux_bad) {\n data = data + \"The light meter sensor is not working, please check it. \";\n } else {\n data = data + \"The current illuminance is \" + lux + \" LUX. \" ;\n } \n\n}\n\n\nmsg.payload = data;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":720,"wires":[["137c3b6db24ea9af"]]},{"id":"5f8aa77771b20bf6","type":"inject","z":"18ff97e3268aa567","name":"日本語","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"0 6-20 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"str","x":140,"y":720,"wires":[[]]},{"id":"75a95eb1ee380c6b","type":"inject","z":"18ff97e3268aa567","name":"英語","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"0 6-20 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"2","payloadType":"str","x":130,"y":760,"wires":[["a282704ecb61315e"]]},{"id":"68a2ddcd6083bd1d","type":"comment","z":"18ff97e3268aa567","name":"AM6:00~PM21:00 風速・風向・照度報告","info":"","x":220,"y":680,"wires":[]},{"id":"f865849b6ab8d93d","type":"comment","z":"18ff97e3268aa567","name":"英語の場合には「英語」の方を繋げる。(両方はダメ)","info":"","x":280,"y":820,"wires":[]},{"id":"137c3b6db24ea9af","type":"cast-to-client","z":"18ff97e3268aa567","name":"","url":"","contentType":"","message":"","language":"en","ip":"192.168.1.100","port":"","volume":"","x":930,"y":720,"wires":[[]]},{"id":"3ae7380911f0152a","type":"mqtt-broker","name":"","broker":"192.168.1.200","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"3f08f5c0d85abc7f","type":"ui-group","name":"風速・風向・照度","page":"fdc7a1f73934fcc6","width":4,"height":1,"order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"fdc7a1f73934fcc6","type":"ui-page","name":"気象","ui":"2a3e995f97f6ec4f","path":"/page1","icon":"home","layout":"grid","theme":"216c59608dd6e9a0","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"false","disabled":"false"},{"id":"2a3e995f97f6ec4f","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":false},{"id":"216c59608dd6e9a0","type":"ui-theme","name":"Default Theme","colors":{"surface":"#505050","primary":"#505050","bgPage":"#505050","groupBg":"#505050","groupOutline":"#cccccc"},"sizes":{"density":"default","pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}},{"id":"227fc2fbc0cd496f","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.30.2","node-red-contrib-cast":"0.2.17"}}]上記のNODE-RED JSON Code (コピーしてNODE-REDの「読み込み」メニューから読み込みます)
風向画像を作る
このまま動作させても、風向計のみ風向画像を必要とするので正常動作しません。
風向画像を入れるフォルダーは決まり事で通常 /home/pi/Public になります。
2026/3時点の64bitLegasy OSではこのフォルダが漢字「公開」になっている場合があります。
その場合には、「Public」直してください、そのままだと使用不可です。
このフォルダー内に画像を作って入れておくわけですが、デフォルトではその機能が無効になってい
ますのでまずは機能を有効にします。
Publicフォルダーを有効にする手順
NODE-REDサーバーを動かしているRaspberry Piの.「.node-red」フォルダーの中に「setting.js」という
のがあります。
このファイルをMouspadで開き適当なところへ追加で書き加えます。
尚、「.node-red」は隠しファイルなので見つからない場合には、「Ctrl-H」を押して表示させて下さい。
「setting.js」に「httpStatic: ‘/home/pi/Public/’,」を加筆する。
/**
* All static routes will be appended to httpStaticRoot
* e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/"
* then "/home/nol/docs" will be served at "/static/"
* e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}]
* and httpStaticRoot = "/static/"
* then "/home/nol/pics/" will be served at "/static/img/"
*/
//httpStaticRoot: '/static/',
httpStatic: '/home/pi/Public/',最後にある「,」を忘れないようにして下さい、無いとNODE-REDが起動しません。
加筆したら保存してからNODE-REDを再起動させます。
NODE-RED上の作業
あと残る作業は、風向を示すアイコンを作成する作業です。
風向の定義としては、風が吹いてくる方向のことです。

アイコンは16方位分あり、その名前は上記の日本語を英語にして先頭文字を重ねたものになります。
例えば、北から時計回りに進むと、 dir_n .png , dir_nne.png , dir_ne.png , dir_ene .png , dir_e.png
のような形です、全部で方位分16枚作ります、名前の付け方が難しいですが、上記の16方位図の漢字を
英語の頭文字にして順番に付けて行けば間違いが少ないです。
風向画像は、1枚例えば「北」を作りあとは回転を加えて行けば比較的簡単に制作出来ます。
アイコンは、下記のようになります。



作成ソフトはAdobe Illustratorで・・・と行きたいとこですが、最近全部がサブスクになり年金生活者
には払えないので以下のようなソフトで行いました。
フリーソフトの「Inkscape」です。以下からダウンロード
https://forest.watch.impress.co.jp/library/software/inkscape/

Illustratorもまともに使えない私ですが、何とかWeb上にある説明書をあさって作ることが出来ました。
最低限のコツは編集画像を選択して「Shift+Ctrl+f」で右上に「ファイルストローク」と言うのが表示
されますので、その下にある「フィル」を選ぶと「画像内部の塗り」が指定でき、「クロストールの塗り」で輪郭の塗り指定が出来る。
画像の回転は、「Shift+ Ctrl + m」で右上に表示されるタブで「回転」を選んで回転角を数字で打ち込む。
あとは、レイヤーの使い方を習得して、最後に「export」で「png」画像を書き出せばOKです。
ちなみにPhotoshop・・・のようなフリーソフトは以下にあります、これはオンラインで動作すソフ
トのようです。「Photopea」
https://www.photopea.com/l/ja_jp/
作成したpng画像は、Publicフォルダー内に更に「dir」フォルダーを作ってそこに保存します。
以上で完成です。
ダッシュボード2の開き方は以下のようにして開きます


ボタンをクリックする。
ダッシュボードが開く(下図)
このダッシュボードを使い慣れていないのと、私の美的センスのなさからデザインは今一つですが
今後学習して改善したいとは思っています。
センスのある方はご自分で良いデザインの物にして下さい。

風速が合っているかの検証
風速を素人が正確に計測しようと思っても簡単には行きません。
そもそも扇風機を当てても風速計は思ったように回転してくれません。
素人で出来ることは、ある程度正確と思われる風速計と比較して大体あっているな 程度しか出来ま
せん。
それが我慢できない人は校正業者に行って風洞試験する必要があります。
私は嫌になってきたのでこれ以上追求しないことにします。
と思ったがやっぱり風洞作ろうっと!

ま、回らない!!

校正証書付風速計
アマゾン ¥5,000 (中古品)

偽の庭風洞テストに扇風機や大型送風機は使えない、これでないとカップが回ってくれない。
運用の様子
