1.目的

水産養殖などでは水質のチェックのために、水中に溶け込んでいる電解質の総濃度を調べて管理する必要があります。

TDS(Total Dissolved Solids)メータは、水中に無機イオンを多く含む水は電気を通しやすく、無機イオンを含まない純水は電気を通さないという原理を利用し、水中の電気伝導度を測定し、この伝導度に係数をかけた値を便宜的に濃度ppmに換算して表示します。測定できる物質は、水中に含まれる無機イオン(カルシウム、マグネシウム、ナトリウム、カリウム、鉄、マンガン、など)の総量で、電解質の種類を特定することはできません。

水中に溶けている電解質の総濃度を調べることができるkeyestudio社製TDSメータであるKS0429の立上げを今回は行います。

立上げの最終目標は、投稿記事「温度センサーDS18B20内蔵温度プローブの動作実験 」で立ち上げた温度センサDS81B20内蔵温度プローブで水温を測定し、その結果を電解質の総濃度計算に使用することとします。上記のように、電気伝導度から電解質の総濃度ppmへの変換には、測定している水の温度を用いて行います。そこで、温度プローブでの測定結果を使用します。

アイキャッチ画像は、TDSメータKS0429と温度プローブを一つのコップの中に入れて温度補正をした電解質の総濃度計算をやっている画像です。

2.  keyestudio社製TDSメータ KS0429の仕様

TDSメータKS0429の仕様を以下に示します。TDSプローブとTDSメータボードとボードから出力される3本の線があり、3本の線をAruduinoに接続して使用することが前提のようです。HPからは、簡単な仕様とともにAruduinoでの測定プログラムをダウンロードできます。

Home Page : KS0429 keyestudio TDS Meter V1.0

3本の線は、電源ライン(赤色)とGNDライン(黒色)と信号ライン(黄色)です。電源電圧は、3.3V~5.5Vです。信号線の出力は0~2.3Vのアナログ電圧です。下の図に示すように、ArduinoのAnalog端子(A1)に接続してAD変換してその結果から電解質濃度をArduinoに計算させます。電源電圧は、5.0Vと3.3Vでアナログ信号値レベルに差は出ませんでした。AD変換するADCの電源電圧は、電解質濃度の計算時に補正をかけます。

TDS Meter:

  •  Input Voltage: DC 3.3 ~ 5.5V
  • Output Voltage: 0 ~ 2.3V
  • Working Current: 3 ~ 6mA
  • TDS Measurement Range: 0 ~ 1000ppm
  • TDS Measurement Accuracy: ± 10% F.S. (25 ℃)
  • Module Interface: XH2.54-3P
  • Electrode Interface: XH2.54-2P

Shipping List:

  • keyestudio TDS Meter V1.0 for Arduino x1
  • Waterproof TDS Probe x1
  • XH2.54-3Pin Jumper Wire x1

Hookup Guide:

3. TDSメータ立上げステップ

TDSメータKS0429の立上げは、次の3段階で行います。いずれもGatewayからクラウドにデータを送信し、クラウド上のNode-REDのダッシュボードでグラフ表示を行うところは共通ですが、クラウドに送るまでが異なります。

  1. AD変換と電解質濃度の計算をArduino UNOで行い、その後、Gatewayにデータを渡す。クラウドへの送信はHTTP Request。
  2. AD変換をR-MSM内蔵ADC(ADS1015)で行い、電解質濃度計算をGatewayで行う。クラウドへの送信はHTTP Request。
  3. AD変換をR-MSM内蔵ADC(ADS1015)で行い、電解質濃度計算はGatewayで行う。その際に、温度プローブの測定結果で温度補正を実施する。クラウドへの送信はMQTTを使う。

4.TDSメータ立上げ:ステップ a)

4.1. Arduino UNO上でテストコードを動作させる

ステップa)の第1段階として、Arduino UNOにHPからダウンロードしたテストコードをコンパイルし、Arduino UNOに書込みます。

Windows PCとArduino UNOをUSBケーブルで接続し、テストコードをコンパイル、書き込みます。

そして、第2項のHookup Guideに従い、ArduinoとTDSメータを接続します。TDSメータを水道水に浸けます。

Arduinoの上部の「ツール」をクリックし、「シリアルモニタ」をクリックして、シリアルモニタを表示します。シリアルモニタの右下にある転送スピードから「115200bps」を選びます。

結果、以下の様な表示出力されました。この時、Hookup Guideの通りに接続していますので、TDSメータの電源は5.0Vです。

実験として、VDDをArduino UNOの隣りの3.3V電源に接続し直してみます。結果は以下の様になりました。

電源電圧で出力が変わらないようです。

4.2. Gatewayにデータを送る

PCに接続されていたArduino UNOのUSBケーブルをGateWayにつなげます。

上の写真の左下が、今回動作を確認しているKS0429とArduino UNOです。右上のBRBと黒いケーブルは後に使用する温度プローブです。

その状態で、Gateway(Raspberry Pi)のGTKtermを起動し、信号が来ているかを確認します。

GTKtermの’Configuration’ -> ‘Port’の画面で、Portに’/dev/ttyACM0’と入力し、Baud Rateを’115200’に設定します。

PCの時と同じ値が確認でき、Gatewayでもデータが読めることが分かりました。

4.3. Gateway上のNode-REDでデータ受信確認

Gateway(Raspberry Pi)上のNode-REDでもデータを受信できること確認します。

まず、GTKtermを終了します。これが動いているとNode-REDのシリアル入力がバッティングしてエラーメッセージを出します。

Gateway上でNode-REDを起動し、フローエディタを起動します。

(Node-REDの起動に関しては、「Node-REDの起動とアクセス(Ubuntu)」を参照ください。Rashberry Piでも内容は同じです。)

 

シリアル入力のノードを置き、ダブルクリックをして編集します。シリアルポートの横の鉛筆マークをクリックして、シリアルポートノードの編集画面を開きます。シリアルポートに’/dev/ttyAMC0’と入力し、ボーレートを’115200’を選択して更新ボタンを押します。ひとつ前の画面に戻りますので、完了ボタンを押します。

シリアル入力の後ろにデバッグノードを置いてデプロイをします。するとデバッグ画面に、”TDS Value: XXppm”と表示されます。

Node-REDでも、シリアル入力ノードを使って、Arduinoからのデータを受け取ることができることが確認できました。

4.4. Gatewayからクラウドへのデータ送信

Gateway上のNode-REDでArduinoからの電解質濃度の計算結果を受け取ることができましたので、このデータをクラウド(GCP: Google Cloud Platform)に送信します。

Node-REDのフローにクラウドに上げるためのノードを追加します。

  1. functionノードを追加し、送られてくるデータから不要な部分を削除し、json形式に変形します。
    TDS Value: 60ppm  → {“data”:60}
  2. changeノードを追加し、センサ名とノード名を追加します。
    {“sensor”:”KS0429″}
    {“node”:0}
  3. http requestノードを追加し、GCPの外部IPとラベルを指定します。
    メソッドは’GET’です。
    http://(外部IPアドレス):1880/KS0429/

それぞれ記入したら、完了ボタンを押してポップアップを閉じます。

(GCPのhttp requestの設定の詳細に関しては、「温度センサーDS18B20内蔵温度プローブの動作実験」を参照ください)

以上で、Gatewayからクラウド(GCP)への送信の準備が出来ました。次は、クラウド側のNode-REDの設定です。

4.5. クラウド(GCP)上でのNode-REDデータの受信とグラフ表示

クラウド(GCP)上のVMインスタンス上にNode-REDを立ち上げる方法に関しては、投稿記事「GCPのUbuntuを使ってみる」を参照してください。

ここでは、クラウド上にNode-REDが起動できたところからスタートします。

4.5.1. データ受信

Node-REDを起動し、http requestノードから送信されたデータを受け取るために、http inノードを配置します。http inを受け取ったら、返事をしないといけませんので、http responseノードを配置し、入力をhttp inノードの出力と接続します。http inノードをダブルクリックして、メソッドは’GET’のままで、URLのところに、http requestノードでIPアドレスの後に書いたラベルを記述します。ここでは、’/KS0429’です。

これで、http requestノードからのデータを受け取る準備が出来ましたので、この後は、受け取ったデータをどう処理するかのフローを描きます。ここでは、グラフとチャートをダッシュボードに表示します。

上のフローでは、http requestノードの出力をフローを見やすくするために、線でつなげるのではなく、Link outノードとLink in ノードを使用しています。Link out ノードに名前を付けて、Link inノードでその名前を選択することで接続できます。

4.5.2. グラフ表示

a) 前準備

Link inノードの後に、ChangeノードとSwitchノードを配置して、グラフ表示の準備をします。

Changeノードでは、単位を追加するために、データをmsg.payload.TDS.valに代入し、単位’ppm’をmsg.payload.TDS.unitsに代入しています。これでオブジェクトTDSは、valというデータとunitsという単位を持ちます。次に、switchノードを配置して、msg.payload.sensorの種別で選びます。ここでは、’KS0429_0’という名前をmsg.payload.sensorに持つのデータのみを通過させます。

これで、グラフ表示の準備が出来ました。グラフ表示としてゲージとチャートのノードを配置していきます。

b) ゲージの表示

gaugeノードを配置して、入力にchangeノードの出力を接続したら、ダブルクリックしてgaugeノードを編集します。

  1. 「表示グループ」の入力:ここでは、’Aqa-Culture’の下の’Pool No.0’にゲージを配置します。
  2. 「ラベル」の入力:ゲージの上に表示される文字です。ここでは、’TDS No.0’と表示させます。
  3. Value formatの入力:データが入っているオブジェクトを記します。ここでは、{{msg.payload.TDS.val}}です。
  4. Units の入力:Unitが入っているオブジェクトを記します。ここでは、{{msg.payload.TDS.units}}です。
  5. Rangeの入力:最大値、最小値を記入します。
  6. その他:必要のある個所は記入します。

以上、入力が終了したら、「完了」ボタンを押します。

c) チャートの表示

チャートは、データが’msg.payload’オブジェクトに入っていることが前提とされていますので、まず、changeノードをもう一つ追加して、データを’msg.payload.TDS.val’から’msg.payload’に移します(ノード名:assignment)。

その後、chartノードを配置し、ノードassignmentの出力を入力に接続したら、ダブルクリックしてchartノードを編集します。

  1. グループの入力:ここでは、’Aqa-Culture’の下の’Pool No.0’にチャートを配置します。
  2. 「ラベル」の入力:チャートの上に表示される文字です。ここでは、’TDS No.0’と表示させます。
  3. X軸情報の入力:ここでは、直近4時間のデータを表示させます。
  4. X軸ラベルの入力:ここでは、横軸を時間とし、HH:mm:ssのフォーマットで表示させます。
  5. Y軸の最大値/最小値の入力:表示させる最大値、最小値を記入します。
  6. その他:必要のある個所は記入します。

以上、入力が終了したら、「完了」ボタンを押します。

以上で、グラフ表示の設定の終了です。

ダッシュボードの画面の表示を行った結果を次に示します。赤枠が、TDSメータの出力表示です。

表示は、「温度センサーDS18B20内蔵温度プローブの動作実験」に追加する形にしています。

4.6. 水に食塩追加時の変化

コップの水に食塩を追加した時の変化を次に示します。食塩を適当に入れたため、1000ppmにかなり近づいてしまいました。

5. TDSメータ立上げ:ステップ b)

次のステップとして、TDSメータKS0429の出力をR-MSMに搭載しているADCであるADS1015を使います。この時、電解質の総濃度の計算は、R-MSMで行うのではなくGateway(Raspberry Pi)で実施します。そのため、ステップa)で使用したArduinoのプログラムを改造します。

  1. 電解質の総濃度の計算部分をArduinoのプログラムから抽出し、C++に変換します。
  2. Gatewayの中で、Node-REDとC++のデータのやり取りをUDPで行わせます。そのための、記述を追加します。
    (UDP通信に関しては、「メカトロ講座03 c++でudp通信をする方法」を参考にしています。非常に簡単に接続できます)

また、計算結果が正しいかを評価するため、Arduino UNOの結果とR-MSMの結果を同時に表示させます。

5.1. 機器の接続

TDSメータKS0429とArduino UNO、R-MSMの接続関係を以下に示します。

 

接続した評価環境を以下に示します。

5.2. GatewayのNode-REDへのノード追加

GatewayのNode-REDへ追加になったノードレッドを以下に示します。R-MSMを追加したことでNode-REDに追加になった部分を青枠で、TDSメータに関係する部分を赤枠で囲いました。

以下、赤枠の部分に関して詳しく説明します。

5.2.1. ADS1015追加部分の説明

ADS1015は、他のR-MSMのセンサと同じく/dev/rfcomm0のシリアルポートからNode-REDに信号が入ってきます。ADS1015が他のセンサと異なるのは、電解質の濃度計算を行うため、UDPでC++のプログラムに送信されて、電解質濃度になってUDPでC++から送り返されてくるところです。

ここでは、UDP送受信前後を説明します。

(1) UDP送信

UDPでC++に送信される前に、データだけの部分にするために、Changeノードで’msg.payload.TDS.val’を’msg.payload’に移します。その後、UDPメッセージとして、ポート4002でローカルホスト内に送信されます。そこで、udp outノードを配置し、以下を編集します。

  1. UDPメッセージを選択する
  2. ポートをC++と合わせる(ここでは4002)
  3. アドレスをローカルホストである127.0.0.1にする

その後、完了ボタンを押す。

(2) UDP受信

ローカルホスト内でUDP送信されたデータは、C++で受け取られて電解質濃度に計算される。計算結果は、UDPで送り返されてくるので、Node-RED上にUDP inのノードを配置します。そして、以下を編集します。

  1. 待ち受けをUDPメッセージにします。
  2. ポートをC++から送られてくるポートに合わせます(ここでは、4001)。

以上を編集して「完了」ボタンを押します。

クラウドに送信するために、センサ名とプール名を追加します。changeノードを配置し編集します。編集後、「完了」ボタンを押します。

5.2.2. 濃度計算実行部分

電解質の濃度計算を実行するC++プログラムを起動するための処理をNode-REDに追加します。

injectノードとexecノードを配置し、接続します。

injectノードは、「Node-RED起動の0.1秒後、以下の動作を行う」の頭をチェックします。その後、「完了」ボタンを押します。これで、Node-RED起動後、もしくはデプロイ後にプログラムが起動されるようになります。

execノードは、コマンド欄に実行するプログラム名をフルパスで記載します。その後、「完了」ボタンを押します。msg.payloadで引数を渡す場合や、出力を使って処理を行う場合には、それぞれ、引数、出力の欄を変更します。ここでは、引数無しで、結果はUDPで返しますので、初期状態のままです。

以上で、Gateway側のNode-REDの追加を終了しました。

電解質濃度計算プログラムは、「5.5.電解質濃度計算プログラムの作成」をご覧ください。

5.3. クラウド(GCP)のNode-REDへのノード追加

クラウド(GCP)のNode-REDには、http requestの受けとグラフ表示のノード一式を追加します。基本、「4.5. クラウド(GCP)上でのNode-REDデータの受信とグラフ表示」と同じです。http inのラベルが「/KS0429-R」に変更になっているだけです。

5.4. Arduino UNOとR-MSMとの結果の比較

Arduino UNOのADCを使いArduino UNOで電解質の濃度計算をさせた結果と、R-MSMのADCを使いGatewayのC++で電解質の濃度計算をさせた結果を比較した。差はほぼ見られなかった。

5.5. 電解質濃度計算プログラムの作成

メーカーから提供されている電解質濃度計算プログラムは、Arduinoのプログラムです。

この項「5. TDSメータ立上げ:ステップ b)」の初めに記載したように、次の2つの変更を行います。

  1. 電解質の総濃度の計算部分をArduinoのプログラムから抽出し、C++に変換します。
  2. Gatewayの中で、Node-REDとC++のデータのやり取りをUDPで行わせます。そのための、記述を追加します。

C++を選んだ理由は、ArduinoのプログラムがほぼC++だからである。

5.5.1. 本体プログラム(TDS_meter.cpp)

Arduinoのプログラムから大きな変更点は以下のとおりである。

  1. C++の標準ライブラリの追加
  2. Arduinoのライブラリの削除、A1ポートの記述の削除
  3. 電源電圧VREF=3.0Vに変更
  4. setup()、loop()の記述を int main()に変更
  5. データの読み込み、送信をUDPに変更
  6. メディアンフィルタの計算関数呼び出しを時間からデータの数に変更
  7. 標準出力への出力にADCの電圧を追加

以下にプログラムを示します。

/*
/  TDS_meter.cpp : calculate TDS value from ADC data
/       ver 0.01 : 2022/03/28
/       $ g++ -o TDS_meter TDS_meter.cpp
*/

#include 
#include 
#include 
#include        // for usleep()
#include 
#include "simple_udp.h"

//#define VREF 5.0 // analog reference voltage(Volt) of the ADC
#define VREF 3.0 // analog reference voltage(Volt) of the ADC
#define SCOUNT 30 // sum of sample point
#define MAXNUM 256

int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0,copyIndex = 0;
float averageVoltage = 0,tdsValue = 0,temperature = 25;
simple_udp udp0("127.0.0.1",4001);  // send data
simple_udp udp1("0.0.0.0"  ,4002);  // receive data

int getMedianNum(int bArray[], int iFilterLen);

int main(){
  char buf[MAXNUM];
  int i;
  
  /* UDP ポート オープン */
  udp1.udp_bind();

  while (1){
  /* 受信処理 */
    //read the analog value
    std::string rdata=udp1.udp_recv();
    strcpy(buf, rdata.c_str());
    i = strlen(buf);
    if (i<=1){
      continue;
    }

    //read the analog value and store into the buffer
    analogBuffer[analogBufferIndex] = atoi(buf);
    analogBufferIndex++;

    if(analogBufferIndex == SCOUNT){
      for(copyIndex=0;copyIndex<SCOUNT;copyIndex++)
	      analogBufferTemp[copyIndex]= analogBuffer[copyIndex];
      printf("analog:");
      printf("%d ",analogBufferTemp[0]);

      // read the analog value more stable by the median filtering algorithm,
      // and convert to voltage value
      averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF/1024.0;
      //temperature compensation formula:
      // fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
      float compensationCoefficient = 1.0 + 0.02 * (temperature-25.0);
      //temperature compensation    
      float compensationVolatge = averageVoltage / compensationCoefficient;
      //convert voltage value to tds value
      tdsValue=(133.42*compensationVolatge*compensationVolatge*compensationVolatge - 255.86*compensationVolatge*compensationVolatge + 857.39*compensationVolatge)*0.5;
      printf("voltage:");
      printf("%.2f",averageVoltage);
      printf("V ");
      printf("TDS Value:");
      printf("%.0f",tdsValue);
      printf("ppm\n");
      sprintf(buf,"{\"data0\":%.0f}",tdsValue);
      udp0.udp_send(buf);
      analogBufferIndex = 0;
    }
  }
}

int getMedianNum(int bArray[], int iFilterLen){
  int bTab[iFilterLen];
  for (int i = 0; i<iFilterLen; i++)
    bTab[i] = bArray[i];
  int i, j, bTemp;
  for (j = 0; j < iFilterLen - 1; j++){
    for (i = 0; i < iFilterLen - j - 1; i++){
      if (bTab[i] > bTab[i + 1]){
	bTemp = bTab[i];
	bTab[i] = bTab[i + 1];
	bTab[i + 1] = bTemp;
      }
    }
  }
  if ((iFilterLen & 1) > 0)
    bTemp = bTab[(iFilterLen - 1) / 2];
  else
    bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
  return bTemp;
}

5.5.2. UDPプログラム(simple_UDP.h)

UDP通信に関しては、「メカトロ講座03 c++でudp通信をする方法」を参考にしています。

変更点は1点です。コンパイルでエラーになりましたので、デバッグの結果、プログラムの冒頭部分の標準ライブラリ読込の部分に、次の一行を追加しています。

#include <string>

以下にプログラムを示します。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include 
#include 
#include 

class simple_udp{
    int sock;
    struct sockaddr_in addr;
public:
    simple_udp(std::string address, int port){
        sock = socket(AF_INET, SOCK_DGRAM, 0);
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(address.c_str());
        addr.sin_port = htons(port);        
    }
    void udp_send(std::string word){
        sendto(sock, word.c_str(), word.length(), 0, (struct sockaddr *)&addr, sizeof(addr));
    }

    void udp_bind(){
        bind(sock, (const struct sockaddr *)&addr, sizeof(addr));

    }
    std::string udp_recv(){
        #define BUFFER_MAX 400
        char buf[BUFFER_MAX];
        memset(buf, 0, sizeof(buf));
        recv(sock, buf, sizeof(buf), 0);
        return std::string(buf);
    }
    void udp_recv(char *buf, int size){
        memset(buf, 0, size);
        recv(sock, buf, size, 0);
    }

    ~simple_udp(){
        close(sock);
    }
};

5.5.3. コンパイル

コンパイルは以下のコマンドで行います。

$ g++ -o TDS_meter TDS_meter.cpp

6. TDSメータ立上げ:ステップc)

最後のステップです。大きな変更点は、以下の2つです。

  1. Arduino UNOを外す。
  2. 電解質濃度計算に、温度プローブの測定結果を使用する。
  3. Gatewayからクラウド(GCP)への送信方法をhttp requestからMTTQに変更。

接続状態の写真を以下に示します。Arduino UNOを外しています。

以下、順番に説明していきます。

6.1. GatewayのNode-REDのフロー変更

GatewayのNode-REDのフローの変更点を示します。

  1. Arduino UNOの入力フローを削除
  2. C++のexecフローの位置を変更
  3. クラウドへの送信方法をhttp requestノードからmqtt outノードに変更
  4. 温度プローブのNode-RED画面を編集。C++へ温度を送信するためのudp outノードを追加

以下に、変更箇所について示します。

MQTT outノードの編集については、以下を参照ください。

MQTT outノードを配置して、ダブルクリックします。MQTT outノードの編集画面が現れますので、「サーバ」の新規作成を選んで、鉛筆マークをクリックします。MQTT Brokerノードの編集画面が出てきますので、

  1. サーバの個所にクラウドの外部IPアドレスを記載します。
  2. ポートを記載します。ここでは、8101にしてます。
  3. その他は必要があれば変更ください。

「更新」ボタンを押します。

その後、MQTT outノードの編集画面で、MQTTのトピックスを入力します。

ここでは、’/tds_data’としています。

クラウド(GCP)上でのMQTTの立上げの詳細に関しては、「GCPのUbuntuを使ってみる」の「2. MQTTを使った通信実験」を参照ください。

最後に、温度プローブのNode-REDフローを修正します。UDP outノードを配置します。ダブルクリックして編集画面を表示します。

  1. 送信をUDPメッセージにします。
  2. ポートを追記します。ここでは、’4003’です。
  3. アドレスを追記します。ここでは、ローカルホストの’127.0.0.1’です。
  4. 記入完了したら、完了を押します。

6.2. クラウド(GCP)のNode-REDのフローの変更

次に、クラウド側のNode-REDのフローの変更に関して説明します。

  1. Arduino UNOの入力フローを削除
  2. MQTT Brokerノードの配置
  3. MQTT in ノードの配置

以下に、2と3に関して説明します。

まず、MQTT Brokerノードに関してです。

MQTT Brokerノードを配置します。その後ダブルクリックをして、MQTT Brokerノードの編集画面を表示します。基本、MQTT Portの変更と名前の変更くらいです。変更後、「完了」ボタンを押します。

次に、MQTT inノードに関してです。

http inノードとhttp responseノードの代わりに、MQTT inノードを配置します。

    1. MQTT inノードをダブルクリックして、編集画面を表示します。
    2. サーバの新規作成を選んで、鉛筆ボタンを押します。
    3. 出てきたMQTT brokerノードの編集画面で、サーバに’localhost'(127.0.0.1でもOK)を入力
  1. ポート番号も入力。ここでは’8101′
  2. 更新ボタンを押す
  3. mqtt inノードでトピックスを入力。ここでは、’/tds_data’
  4. その他必要があれば編集
  5. 編集後、完了ボタンを押す

以上で、クラウド側のNode-REDのフローの編集は完了です。

6.3. C++プログラムの変更

次に、電解質濃度計算プログラムの修正に関して説明します。

6.3.1. プログラムの変更内容

修正内容は、以下の3点です。プログラム名を’TDS_meter_tc.cpp’に変更しました。

  1. 温度データの入力のためのUDPポートを1つ追加
  2. 読み込んだ結果を、変数’temperature’に代入
  3. 標準出力に’temperture’を表示

修正箇所は9行です。

6.3.2. コンパイルコマンド

$ g++ -o TDS_meter TDS_meter_tc.cpp

実行ファイルの名称が変わらないように -o オプションの後ろ名前をステップb)と同じにしています。

6.3.2. ソースコード:TDS_meter_tc.cpp


/*
/  TDS_meter_tc.cpp : calculate TDS value from ADC data
/       ver 0.01 : 2022/03/28
/       ver 0.02 : 2022/03/29 Thermo compensattion
/       $ g++ -o TDS_meter TDS_meter_tc.cpp
*/

#include 
#include 
#include 
#include        // for usleep()
#include 
#include "simple_udp.h"

//#define VREF 5.0 // analog reference voltage(Volt) of the ADC
#define VREF 3.0 // analog reference voltage(Volt) of the ADC
#define SCOUNT 30 // sum of sample point
#define MAXNUM 256

int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0,copyIndex = 0;
float averageVoltage = 0,tdsValue = 0,temperature = 25;
simple_udp udp0("127.0.0.1",4001);  // send data
simple_udp udp1("0.0.0.0"  ,4002);  // receive data
simple_udp udp2("0.0.0.0"  ,4003);  // receive data  added for Temperature compensation

int getMedianNum(int bArray[], int iFilterLen);

int main(){
  char buf[MAXNUM];
  char tbuf[MAXNUM];              // added for Temperature compensation
  int i;
  
  /* UDP ポート オープン */
  udp1.udp_bind();
  udp2.udp_bind();                // added for Temperature compensation

  while (1){
  /* 受信処理 */
    //read the analog value
    std::string rdata=udp1.udp_recv();
    strcpy(buf, rdata.c_str());
    i = strlen(buf);
    if (i<=1){
      continue;
    }

    //read the analog value and store into the buffer
    analogBuffer[analogBufferIndex] = atoi(buf);
    analogBufferIndex++;

    if(analogBufferIndex == SCOUNT){
      for(copyIndex=0;copyIndex<SCOUNT;copyIndex++)
	analogBufferTemp[copyIndex]= analogBuffer[copyIndex];
      printf("analog:");
      printf("%d ",analogBufferTemp[0]);

      std::string tdata=udp2.udp_recv();      // added for Temperature compensation
      strcpy(tbuf, tdata.c_str());            // added for Temperature compensation
      temperature = atof(tbuf);               // added for Temperature compensation
      // read the analog value more stable by the median filtering algorithm,
      // and convert to voltage value
      averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF/1024.0;
      //temperature compensation formula:
      // fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
      float compensationCoefficient = 1.0 + 0.02 * (temperature-25.0);
      //temperature compensation    
      float compensationVolatge = averageVoltage / compensationCoefficient;
      //convert voltage value to tds value
      tdsValue=(133.42*compensationVolatge*compensationVolatge*compensationVolatge - 255.86*compensationVolatge*compensationVolatge + 857.39*compensationVolatge)*0.5;
      printf("voltage:");
      printf("%.2f",averageVoltage);
      printf("V ");
      printf("Temperature:");                // added for Temperature compensation
      printf("%.2f",temperature);            // added for Temperature compensation
      printf("deg ");                        // added for Temperature compensation
      printf("TDS Value:");
      printf("%.0f",tdsValue);
      printf("ppm\n");
      sprintf(buf,"{\"data0\":%.0f}",tdsValue);
      udp0.udp_send(buf);
      analogBufferIndex = 0;
    }
  }
}

int getMedianNum(int bArray[], int iFilterLen){
  int bTab[iFilterLen];
  for (int i = 0; i<iFilterLen; i++)
    bTab[i] = bArray[i];
  int i, j, bTemp;
  for (j = 0; j < iFilterLen - 1; j++){
    for (i = 0; i < iFilterLen - j - 1; i++){
      if (bTab[i] > bTab[i + 1]){
	bTemp = bTab[i];
	bTab[i] = bTab[i + 1];
	bTab[i + 1] = bTemp;
      }
    }
  }
  if ((iFilterLen & 1) > 0)
    bTemp = bTab[(iFilterLen - 1) / 2];
  else
    bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
  return bTemp;
}

6.4. 温度補正を実施した結果

最後に、温度補正を行い、R-MSMのADCのみのダッシュボードの画面を示します。