本記事のポイント

本ブログはIoT-CoreエンジンNEQTOの特徴の一つであるJavaScriptによるNEQTO Engine対応デバイスの開発にフォーカスし、JavaScriptの基礎およびneqto.js特有の拡張機能について連載形式で紹介していきます。

この連載記事では、JavaScriptの基礎となる構文や文法について順番に解説していきます。第4回目の今回は「文字列操作」について解説します。NEQTOユーザー様がJavaScript開発を円滑に進めるために活用頂くこと、また、NEQTOに興味をお持ちの方がNEQTOを使用したJavaScript開発について理解を深めて頂くことが本ブログの目的となります。



1. はじめに

各種デバイスやクラウド間をインタフェースする際、あらかじめ取り決められたコマンドやメッセージを取り扱います。これらをプログラム上で処理する場合、文字列の操作(結合・検索・分割・切り出し・置換・変換)が不可欠となります。第4回は、この「文字列操作」にフォーカスし、neqto.jsを使用して詳しく解説していきます。また、ブログ後半では、「文字列操作」を活用した例として、NEQTOのカスタムメッセージ通知機能を使用した遠隔コマンド制御、UARTを使用したコマンド制御について紹介します。

「neqto.js」は、NEQTO Engine上で動作するJavaScript環境です。ECMAScript 5.1 Editionに対応しています。

2. 文字列操作手法

JavaScriptには文字列を操作するための各種メソッドが標準搭載されています。
その中でもよく使用する文字列操作手法を取り上げます。


文字列結合

複数の文字列を結合する場合、+演算子を使う方法が一般的です。

var str1 = "ABC";
var str2 = "DEF";
var str3 = "GHI";
print(str1 + str2 + str3);
ABCDEFGHI

バッククォーテーション(`)囲みで記述する「テンプレートリテラル」を活用する方法もあります。

var str1 = "ABC";
var str2 = "DEF";
var str3 = "GHI";
print(`${str1}${str2}${str3}`);
ABCDEFGHI

また、配列に格納された文字列はArrayオブジェクトのjoinメソッドを使用して結合することができます。


<Array>.join([separator])

区切り文字(separator)は配列の各要素を結合する際に挿入されます。省略した場合はカンマ(,)が挿入されます。

var arr = ['ABC', 'DEF', 'GHI'];
print(arr.join(''));
print(arr.join('::'));
print(arr.join());
ABCDEFGHI
ABC::DEF::GHI
ABC,DEF,GHI

文字列検索

"String".indexOf(searchString[,position])

対象文字列("String")の先頭から後方に向けて検索文字列(searchString)の検索を行い、最初に検出した位置をインデックス値で返します。
インデックス値は対象文字列の1文字目が0、最後尾の文字が対象文字列長-1となります。
検索文字列が検出されなかった場合は-1を返します。

Notes:
  • 大文字・小文字が区別されて検索が行われます。
  • position (オプション): 検索を開始する位置をインデックス値で指定します。
    • < 0が指定された場合: 0として扱われます。
    • > 対象文字列長が指定された場合: 対象文字列長として扱われます。
    • 数値以外が指定された場合: 0として扱われます。
//                   11111
//Index:   012345678901234
var str = "ABC DEF DEF GHI"; //str.length=15

print(str.indexOf("ABC")); //0
print(str.indexOf("DEF")); //4
print(str.indexOf("GHI")); //12
print(str.indexOf("XYZ")); //-1
//                   11111
//Index:   012345678901234
var str = "ABC DEF def gHi"; //str.length=15

print(str.indexOf("ABC")); //0
print(str.indexOf("DEF")); //4
print(str.indexOf("GHI")); //-1
print(str.indexOf("XYZ")); //-1
//Index:   01234
var str = "OK OK"; //str.length=5

print(str.indexOf("OK"));     //0
print(str.indexOf("OK",-1));  //0
print(str.indexOf("OK",0));   //0
print(str.indexOf("OK",1));   //3
print(str.indexOf("OK",2));   //3
print(str.indexOf("OK",3));   //3
print(str.indexOf("OK",4));   //-1
print(str.indexOf("OK",5));   //-1
print(str.indexOf("OK",6));   //-1
print(str.indexOf("OK","a")); //0
print(str.indexOf("OK",NaN)); //0

"String".lastIndexOf(searchString[,position])

対象文字列("String")の最後尾から前方に向けて検索文字列(searchString)の検索を行い、最初に検出した位置をインデックス値で返します。
インデックス値は対象文字列の1文字目が0、最後尾の文字が対象文字列長-1となります。
検索文字列が検出されなかった場合は-1を返します。

Notes:
  • 大文字・小文字が区別されて検索が行われます。
  • position (オプション): 検索を開始する位置をインデックス値で指定します。
    • < 0が指定された場合: 0として扱われます。
    • > 対象文字列長が指定された場合: 対象文字列長として扱われます。
    • 数値以外が指定された場合: 対象文字列長として扱われます。
//                   11111
//Index:   012345678901234
var str = "ABC DEF DEF GHI"; //str.length=15

print(str.lastIndexOf("ABC")); //0
print(str.lastIndexOf("DEF")); //8
print(str.lastIndexOf("GHI")); //12
print(str.lastIndexOf("XYZ")); //-1
//                   11111
//Index:   012345678901234
var str = "ABC DEF def gHi"; //str.length=15

print(str.lastIndexOf("ABC")); //0
print(str.lastIndexOf("DEF")); //4
print(str.lastIndexOf("GHI")); //-1
print(str.lastIndexOf("XYZ")); //-1
//Index:   01234
var str = "OK OK"; //str.length=5

print(str.lastIndexOf("OK"));     //3
print(str.lastIndexOf("OK",6));   //3
print(str.lastIndexOf("OK",5));   //3
print(str.lastIndexOf("OK",4));   //3
print(str.lastIndexOf("OK",3));   //3
print(str.lastIndexOf("OK",2));   //0
print(str.lastIndexOf("OK",1));   //0
print(str.lastIndexOf("OK",0));   //0
print(str.lastIndexOf("OK",-1));  //0
print(str.lastIndexOf("OK","a")); //3
print(str.lastIndexOf("OK",NaN)); //3

"String".search(regexp)

対象文字列("String")の先頭から正規表現パターン(regexp)の検索を行い、最初に検出した位置をインデックス値で返します。
インデックス値は対象文字列の1文字目が0、最後尾の文字が対象文字列長-1となります。
正規表現パターンが検出されなかった場合は-1を返します。

Notes:
  • 「正規表現」は、ある規則性を持った文字列を1つの文字列パターンとして表現する記法です。
  • 正規表現パターンには2通りの記述方法があります。
    • スラッシュ(/)囲みで記述する「正規表現リテラル」を使用する方法:
      (/パターン/オプション)
      
    • RegExpコンストラクタを使用する方法:
      new RegExp('パターン','オプション')
      

正規表現リテラルで単純文字列パターンを使用した検索方法を紹介します。

//                   11111
//Index:   012345678901234
var str = "ABC DEF DEF GHI"; //str.length=15

print(str.search(/ABC/)); //0
print(str.search(/DEF/)); //4
print(str.search(/GHI/)); //12
print(str.search(/XYZ/)); //-1
//                   11111
//Index:   012345678901234
var str = "ABC DEF def gHi"; //str.length=15

print(str.search(/ABC/)); //0
print(str.search(/DEF/)); //4
print(str.search(/GHI/)); //-1
print(str.search(/XYZ/)); //-1

正規表現は、単純文字列だけではなく、特殊文字を組み合わせることで高度なパターンを表すことができます。
詳しい説明は割愛しますが、使用例をいくつか紹介します。

//                   11111111112222222222333333333
//Index:   012345678901234567890123456789012345678
var str = "2022/11/01 10:11:12,123-4567,-23.4,0xFF"; //str.length=39

print(str.search(/\d{4}\/\d{1,2}\/\d{1,2}/)); //0  (2022/11/01)
print(str.search(/\d{1,2}:\d{1,2}:\d{1,2}/)); //11 (10:11:12)
print(str.search(/\d{3}-\d{4}/));             //20 (123-4567)
print(str.search(/[-]?([1-9]\d*|0)(\.\d+)/)); //29 (-23.4)
print(str.search(/0x[0-9A-Fa-f]+/));          //35 (0xFF)

文字列分割

"String".split(separator[,limit])

対象文字列("String")を区切り文字列(separator)で分割した文字列を配列(Arrayオブジェクト)で返します。
分割された文字列に区切り文字列は含まれません。

Notes:
  • 対象文字列に区切り文字列が存在しなかった場合は、配列の1要素に対象文字列全体が格納されます。
  • 対象文字列の先頭もしくは最後尾に区切り文字列が存在した場合、配列に空白文字("")が挿入されます。
  • limit (オプション): 分割数の上限を指定します。上限に達した時点で分割処理が終了します。
var str = "2022/11/01 10:11:12,123-4567,-23.4,0xFF";
var ret = str.split(',');
for(var i=0; i<ret.length; i++) {
    print(ret[i]);
}
2022/11/01 10:11:12
123-4567
-23.4
0xFF
var str = "2022/11/01 10:11:12,123-4567,-23.4,0xFF";
var ret = str.split('#');
for(var i=0; i<ret.length; i++) {
    print(ret[i]);
}
2022/11/01 10:11:12,123-4567,-23.4,0xFF
var str = "2022/11/01 10:11:12,123-4567,-23.4,0xFF";
var ret = str.split(',',1);
for(var i=0; i<ret.length; i++) {
    print(ret[i]);
}
2022/11/01 10:11:12

区切り文字列(separator)に正規表現を使用することもできます。
複数の区切り文字列を指定したい場合に有効です。

var str = "2022/11/01 10:11:12,123-4567,-23.4,0xFF";
var ret = str.split(/,|\/| |:/);
for(var i=0; i<ret.length; i++) {
    print(ret[i]);
}
2022
11
01
10
11
12
123-4567
-23.4
0xFF

文字列切り出し

"String".substring(start[,end])

対象文字列("String")のうち、開始位置(start)から終了位置(end)までの文字列を返します。
開始位置に該当する文字は含まれ、終了位置に該当する文字は含まれません。
位置には対象文字列の1文字目が0となるインデックス値を指定します。

Notes:
  • 位置の指定範囲は0対象文字列長となります。
    • < 0が指定された場合: 0として扱われます。
    • > 対象文字列長が指定された場合: 対象文字列長として扱われます。
    • 数値以外が指定された場合: 0として扱われます。
  • 終了位置(end)は省略可能です。省略した場合、対象文字列長として扱われます。
  • 開始位置が終了位置より大きい場合は、開始位置と終了位置を入れ替えたものとして扱われます。
  • 該当文字列が存在しない範囲が指定された場合は、空白文字("")が返ります。
//                   1111111
//Index:   01234567890123456
var str = "ABCDE-FGHIJ-KLMNO"; //str.length=17

print(str.substring(0));  //ABCDE-FGHIJ-KLMNO
print(str.substring(6));  //FGHIJ-KLMNO
print(str.substring(12)); //KLMNO
print(str.substring(16)); //O
print(str.substring(17)); //("")
print(str.substring(18)); //("")

print(str.substring(0,0));   //("")
print(str.substring(0,5));   //ABCDE
print(str.substring(6,11));  //FGHIJ
print(str.substring(12,17)); //KLMNO
print(str.substring(12,18)); //KLMNO

print(str.substring(0,3));   //ABC
print(str.substring("a",3)); //ABC
print(str.substring(NaN,3)); //ABC
print(str.substring(-1,3));  //ABC

print(str.substring(3,0));   //ABC
print(str.substring(3,"a")); //ABC
print(str.substring(3,NaN)); //ABC
print(str.substring(3,-1));  //ABC

"String".slice(start[,end])

対象文字列("String")のうち、開始位置(start)から終了位置(end)までの文字列を返します。
開始位置に該当する文字は含まれ、終了位置に該当する文字は含まれません。
位置には対象文字列の1文字目が0となるインデックス値を指定します。

機能的にsubstringメソッドと同じですが、位置指定の扱いに違いがあります。(Notesを比較してください)

Notes:
  • 位置の指定範囲は-対象文字列長0対象文字列長となります。
    • < 0が指定された場合: 対象文字列最後尾から前方に向けて数えられます。
    • > 対象文字列長が指定された場合: 対象文字列長として扱われます。
    • 数値以外が指定された場合: 0として扱われます。
  • 終了位置(end)は省略可能です。省略した場合、対象文字列長として扱われます。
  • 終了位置(end)が開始位置(start)より前の位置の場合、空白文字("")が返ります。
  • 該当文字列が存在しない範囲が指定された場合は、空白文字("")が返ります。
//                   1111111
//Index:   01234567890123456
var str = "ABCDE-FGHIJ-KLMNO"; //str.length=17

print(str.slice(0));  //ABCDE-FGHIJ-KLMNO
print(str.slice(6));  //FGHIJ-KLMNO
print(str.slice(12)); //KLMNO
print(str.slice(16)); //O
print(str.slice(17)); //("")
print(str.slice(18)); //("")

print(str.slice(-1));  //O
print(str.slice(-5));  //KLMNO
print(str.slice(-11)); //FGHIJ-KLMNO
print(str.slice(-16)); //BCDE-FGHIJ-KLMNO
print(str.slice(-17)); //ABCDE-FGHIJ-KLMNO
print(str.slice(-18)); //ABCDE-FGHIJ-KLMNO

print(str.slice(0,0));   //("")
print(str.slice(0,5));   //ABCDE
print(str.slice(6,11));  //FGHIJ
print(str.slice(12,17)); //KLMNO
print(str.slice(12,18)); //KLMNO

print(str.slice(0,-12)); //ABCDE
print(str.slice(6,-6));  //FGHIJ
print(str.slice(12,-1)); //KLMN
print(str.slice(12,0));  //("")

print(str.slice(-18,-12)); //ABCDE
print(str.slice(-17,-12)); //ABCDE
print(str.slice(-11,-6));  //FGHIJ
print(str.slice(-5,-1));   //KLMN
print(str.slice(-5,0));    //("")

print(str.slice(0,3));   //ABC
print(str.slice("a",3)); //ABC
print(str.slice(NaN,3)); //ABC
print(str.slice(-1,3));  //("")

print(str.slice(3,0));   //("")
print(str.slice(3,"a")); //("")
print(str.slice(3,NaN)); //("")
print(str.slice(3,-1));  //DE-FGHIJ-KLMN

"String".substr(start[,length])

対象文字列("String")のうち、開始位置(start)から文字数(length)までの文字列を返します。
位置には対象文字列の1文字目が0となるインデックス値を指定します。

Notes:
  • 位置の指定範囲は-対象文字列長0対象文字列長となります。
    • < 0が指定された場合: 対象文字列最後尾から前方に向けて数えられます。
    • > 対象文字列長が指定された場合: 対象文字列長として扱われます。
    • 数値以外が指定された場合: 0として扱われます。
  • 文字数の指定範囲は0対象文字列長となります。
    • < 0が指定された場合: 0として扱われます。
    • > 対象文字列長が指定された場合: 対象文字列長として扱われます。
    • 数値以外が指定された場合: 0として扱われます。
  • 文字数(length)は省略可能です。省略した場合、対象文字列長として扱われます。
  • 該当文字列が存在しない範囲が指定された場合は、空白文字("")が返ります。
//                   1111111
//Index:   01234567890123456
var str = "ABCDE-FGHIJ-KLMNO"; //str.length=17

print(str.substr(0));  //ABCDE-FGHIJ-KLMNO
print(str.substr(6));  //FGHIJ-KLMNO
print(str.substr(12)); //KLMNO
print(str.substr(16)); //O
print(str.substr(17)); //("")
print(str.substr(18)); //("")

print(str.substr(-1));  //O
print(str.substr(-5));  //KLMNO
print(str.substr(-11)); //FGHIJ-KLMNO
print(str.substr(-16)); //BCDE-FGHIJ-KLMNO
print(str.substr(-17)); //ABCDE-FGHIJ-KLMNO
print(str.substr(-18)); //ABCDE-FGHIJ-KLMNO

print(str.substr(0,0));  //("")
print(str.substr(0,5));  //ABCDE
print(str.substr(6,5));  //FGHIJ
print(str.substr(12,5)); //KLMNO
print(str.substr(12,6)); //KLMNO

print(str.substr(-18,5)); //ABCDE
print(str.substr(-17,5)); //ABCDE
print(str.substr(-11,5)); //FGHIJ
print(str.substr(-5,5));  //KLMNO
print(str.substr(-5,6));  //KLMNO

print(str.substr(0,16)); //ABCDE-FGHIJ-KLMN
print(str.substr(0,17)); //ABCDE-FGHIJ-KLMNO
print(str.substr(0,18)); //ABCDE-FGHIJ-KLMNO

print(str.substr(0,3));   //ABC
print(str.substr("a",3)); //ABC
print(str.substr(NaN,3)); //ABC
print(str.substr(-1,3));  //O

print(str.substr(3,0));   //("")
print(str.substr(3,"a")); //("")
print(str.substr(3,NaN)); //("")
print(str.substr(3,-1));  //("")

"String".match(regexp)

対象文字列("String")の先頭から正規表現パターン(regexp)の検索を行い、検出した文字列を配列(Arrayオブジェクト)で返します。
正規表現パターンが検出されなかった場合はnullを返します。

なお、正規表現オプションによって、検出挙動が変わります。

  • gオプション
    • 指定あり: 対象文字列のうち、検出したすべての文字列が配列に格納されます。
    • 指定なし: 対象文字列のうち、最初に検出した文字列のみが配列に格納されます。
  • iオプション
    • 指定あり: 大文字・小文字を区別しません。
    • 指定なし: 大文字・小文字を区別します。

正規表現リテラルで単純文字列パターンを使用した検索方法を紹介します。

var str = "ABC DEF1 DEF2 def3 GHI";
print(str.match(/ABC/));   //ABC
print(str.match(/DEF/));   //DEF
print(str.match(/DEF/g));  //DEF,DEF
print(str.match(/DEF/gi)); //DEF,DEF,def
print(str.match(/XYZ/));   //null

正規表現パターンを駆使すれば、規則性を持った文字列を容易に抜き出すことができます。
詳しい説明は割愛しますが、使用例を紹介します。

var str = "2022/11/01 10:11:12,data1=100,data2=0.57,data3=-24.5,OK";
var ret = str.match(/data\d+=[-]?([1-9]\d*|0)(\.\d+)?/g);
for(var i=0; i<ret.length; i++) {
    print(ret[i]);
}
data1=100
data2=0.57
data3=-24.5

文字列置換

"String".replace(searchValue,replaceValue)

対象文字列("String")の先頭から検索文字列(searchString)の検索を行い、最初に検出した位置の文字列を置換文字列(replaceValue)に置き換えた文字列を返します。

Notes:
  • 対象文字列に置換対象の文字列が存在しなかった場合は、対象文字列がそのまま返ります。
var str = "ABC DEF DEF GHI";
print(str.replace("ABC","123")); //123 DEF DEF GHI
print(str.replace("DEF","456")); //ABC 456 DEF GHI
print(str.replace("XYZ","789")); //ABC DEF DEF GHI

上記実行例の2つ目のように、対象文字列のなかに複数の置換対象文字列が含まれる場合、最初に検出した箇所のみ置換されることに注意してください。置換対象文字列すべてを置換する場合は、下記のように繰り返し置換を行う処理を実装する必要があります。

var str = "ABC DEF DEF GHI";
var tmp = str.replace("DEF","456")
while(tmp.lastIndexOf("DEF") != -1) {
    tmp = tmp.replace("DEF","456")
}
print(tmp);
ABC 456 456 GHI

検索文字列(searchValue)に正規表現を使用することもできます。
正規表現を使用すれば、上記のような繰り返し置換処理を実装することなく、置換対象文字列すべてを置換することができます。

var str = "ABC DEF DEF GHI";
print(str.replace(/DEF/,"456"));  //ABC 456 DEF GHI
print(str.replace(/DEF/g,"456")); //ABC 456 456 GHI

また、複数の検索文字列を指定したい場合にも有効です。

var str = "ABC:DEF-DEF GHI";
print(str.replace(/:|-| /g, ",")); //ABC,DEF,DEF,GHI

文字列変換

"String".toUpperCase()

対象文字列("String")をすべて大文字に変換した文字列を返します。

"String".toLowerCase()

対象文字列("String")をすべて小文字に変換した文字列を返します。

var str = "AbCdEfGhI-12345";
print(str.toUpperCase()); //ABCDEFGHI-12345
print(str.toLowerCase()); //abcdefghi-12345

3. neqto.jsによる動作検証

ここからは、neqto.jsを使用して、文字列操作を活用する例を紹介していきます。
neqto.jsの動作環境について詳しく知りたい方は、下記のリンク先を確認してください。

NEQTO Hello World!


① カスタムメッセージを使用した遠隔コマンド制御

NEQTOには、NEQTO Consoleから任意の文字列を対象NEQTOデバイスに送信する(プッシュ通知)機能があります。 neqto.jsには、受信したカスタムメッセージを文字列操作機能を用いて識別し、所望の動作に紐づける処理を実装します。

下記スクリプトは、カスタムメッセージによる遠隔コマンド指示により、周期的に"Hello World"を表示するためのサンプルです。

カスタムメッセージのコマンド仕様は次の通りです。

  • start[,interval[,times]] : "Hello World"表示を開始します。
    • interval (オプション): 表示周期をms単位で指定します。デフォルトは1000msとなります。
    • times (オプション): 表示回数を指定します。デフォルトは60となります。
    • 例) start: デフォルト周期、デフォルト回数で開始されます。
    • 例) start,100: 100ms周期、デフォルト回数で開始されます。
    • 例) start,100,10: 100ms周期、回数10で開始されます。
  • stop : "Hello World"表示を停止します。

カスタムメッセージのプッシュ通知は、nqMqttオブジェクトの'push'イベントコールバックでハンドリングすることができます。スクリプトコード中、nqMqtt.on('push',...)部分が該当します。受信したカスタムメッセージ文字列を解析し、"Hello World"表示処理の開始・終了を制御します。

var tmout;
var maxNumOfTimes;
var loopcnt;

var startIntervalTimer = function(interval, times) {
    stopIntervalTimer();

    if(!interval) interval = 1000; //default
    if(!times) times = 60; //default

    var date = new Date();
    print(date.toString() + '+' +  date.getMilliseconds() + 'ms : Start (' + interval + 'ms,' + times + ')');
    maxNumOfTimes = times;
    loopcnt = 0;

    tmout = setInterval(function() {
        var date = new Date();
        loopcnt++;
        print(date.toString() + '+' +  date.getMilliseconds() + 'ms : Hello World!!! ' + loopcnt);
        if(loopcnt >= maxNumOfTimes) {
            stopIntervalTimer();
        }
    }, interval);
}
var stopIntervalTimer = function() {
    if(tmout) {
        var date = new Date();
        print(date.toString() + '+' +  date.getMilliseconds() + 'ms : Stop');
        clearInterval(tmout);
        tmout = undefined;
    }
}

//==============================================================
// nqMqtt event handler
//==============================================================
nqMqtt.on('connect', function(connack) {
    print(Date() + ': nqMqtt connect');
});
nqMqtt.on('close',function() {
    print(Date() + ': nqMqtt close');
});
nqMqtt.on('error', function(error) {
    print(Date() + ': nqMqtt error: ' + error.code.toString());
});
nqMqtt.on('push', function(message) {
    print(Date() + ': nqMqtt push: ' + message);
    //TODO: Custom message handling from NEQTO
    var arr = message.split(',');

    if(arr[0].indexOf("start") == 0) { //start command
        if(arr.length == 2) startIntervalTimer(Number(arr[1])); //start,interval
        else if(arr.length == 3) startIntervalTimer(Number(arr[1]),Number(arr[2])); //start,interval,times
        else startIntervalTimer(); //use default paramater

    } else if(arr[0].indexOf("stop") == 0) { //stop command
        stopIntervalTimer();
    }
});

print("Waiting for custom message command!");
Wed Nov 09 2022 07:02:06 GMT+00:00: nqMqtt push: start,1000,10
Wed Nov 09 2022 07:02:06 GMT+00:00+31ms : Start (1000ms,10)
Wed Nov 09 2022 07:02:07 GMT+00:00+32ms : Hello World!!! 1
Wed Nov 09 2022 07:02:08 GMT+00:00+32ms : Hello World!!! 2
Wed Nov 09 2022 07:02:09 GMT+00:00+34ms : Hello World!!! 3
Wed Nov 09 2022 07:02:10 GMT+00:00+35ms : Hello World!!! 4
Wed Nov 09 2022 07:02:11 GMT+00:00+37ms : Hello World!!! 5
Wed Nov 09 2022 07:02:12 GMT+00:00+38ms : Hello World!!! 6
Wed Nov 09 2022 07:02:13 GMT+00:00+41ms : Hello World!!! 7
Wed Nov 09 2022 07:02:14 GMT+00:00+42ms : Hello World!!! 8
Wed Nov 09 2022 07:02:15 GMT+00:00+45ms : Hello World!!! 9
Wed Nov 09 2022 07:02:16 GMT+00:00+47ms : Hello World!!! 10
Wed Nov 09 2022 07:02:16 GMT+00:00+47ms : Stop

② UART接続された外部デバイスのコマンド制御

NEQTOデバイスにUARTコマンドインタフェースを持った外部デバイスを接続し、制御することを想定します。

下記スクリプトは、外部デバイスにコマンドを送信後、レスポンスを待ち受けるサンプルです。

外部デバイスのコマンド仕様を下記とします。

  • コマンド: START<CR>
  • レスポンス:
    • 正常時: OK<CR><LF>
    • 異常時: ERROR<CR><LF>

はじめにUARTをオープンし、UART受信バッファをクリアします。次に受信データ結合バッファ(recvStr)を初期化し、コマンドを送信します。その後、メインループでレスポンスを待ち受けます。
メインループでは、随時UARTから受信したデータを結合バッファに追記し、レスポンス文字列の存在を確認します。
UARTは非同期通信となるため、一度のUART受信メソッド呼び出しで、すべてのレスポンス文字列を取得できるとは限りません。そのため、結合バッファを用いて逐次的に取り溜める処理を追加しています。

var uart = new UART(1);
uart.open(115200, false, 0);
while(uart.checkRecvData()) uart.receive(false); //uart receive buffer clear

var recvStr = ""
uart.send('START\r');

while(1) {
    recvStr += uart.receive(false);
    if(recvStr.length) {
        if(recvStr.indexOf("OK") != -1) {
            print("received ok");
            break;
        } else if(recvStr.indexOf("ERROR") != -1) {
            print("received error");
            break;
        }
    }
}

print("done");
received ok
done

③ UART接続された外部デバイスから定期的に通知されるデータの取得

先ほど同様に、NEQTOデバイスにUARTコマンドインタフェースを持った外部デバイスを接続し、制御することを想定します。

外部デバイスに下記コマンドを送信すると、レスポンスで規定されたフォーマットの測定データが定期的に通知される仕様とします。

  • コマンド: START<CR>
  • レスポンス: yyyy/MM/dd HH:mm:ss,Temperature,Humidity<CR><LF> (例: 2022/11/01 12:34:00,23.4,50.0)

先ほど同様に、メインループで受信データ結合バッファ(recvStr)を監視します。
1データ毎に<CR><LF>終端されていることを利用して、この<CR><LF>を待ち受けます。
<CR><LF>を検出したならば、<CR><LF>前までのデータを切り出し、表示します。
<CR><LF>後の未処理データは再び結合バッファに戻し、次の<CR><LF>を待ちます。

var uart = new UART(1);
uart.open(115200, false, 0);
while(uart.checkRecvData()) uart.receive(false); //uart receive buffer clear

var termStr = "\r\n";
var recvStr = ""
uart.send('START\r');

while(1) {
    recvStr += uart.receive(false);
    if(recvStr.length) {
        var index = recvStr.indexOf(termStr);
        if(index != -1) {
            var str = recvStr.substring(0, index);
            print(str);
            recvStr = recvStr.substring(index + termStr.length);
        }
    }
}
2022/11/01 12:34:00,23.4,50.0
2022/11/01 12:35:00,23.5,50.0
2022/11/01 12:36:00,23.4,49.9

次に、外部デバイスから取得したデータをクラウド仕様の文字列に形成する処理を想定して、切り出したデータを要素ごとに分割し、再フォーマットしてみます。

var uart = new UART(1);
uart.open(115200, false, 0);
while(uart.checkRecvData()) uart.receive(false); //uart receive buffer clear

var termStr = "\r\n";
var recvStr = ""
uart.send('START\r');

while(1) {
    recvStr += uart.receive(false);
    if(recvStr.length) {
        var index = recvStr.indexOf(termStr);
        if(index != -1) {
            var str = recvStr.substring(0, index);
            //changed=========================================
            var arr = str.split(',');
            if(arr.length == 3) {
                print("temp: " + arr[1] + ", humi: " + arr[2]);
            } else {
                print("format error");
            }
            //================================================
            recvStr = recvStr.substring(index + termStr.length);
        }
    }
}
temp: 23.4, humi: 50.0
temp: 23.5, humi: 50.0
temp: 23.4, humi: 49.9

4. まとめ

いかがでしたか。文字列操作はコマンド制御、データの抽出や形成等、各種デバイスやクラウド間をインタフェースするための処理を実装する上で欠かせないものです。アプリケーションプログラムは文字列操作の組み合わせで構成されると言っても過言ではありません。また、文字列操作手法・メソッドを幅広く知ることは、より効率的でかつ拡張性の高いプログラムの構築に役立ちます。ぜひご参考にしてください。


リンク


Standard ECMA-262 5.1 Edition ECMAScript® Language Specification - 15.5 String Objects

NEQTOとは

NEQTOハードウェア

NEQTO技術ドキュメント