HOME 教育用公開ウェブサービス ROBINS

ITO Manager 4.1 for Kinoma Create の技術情報

3-3301
KinomaSource
情報処理概論 センサー

はじめに

ITOマネジャーは,キノマクリエイトを使って電圧入力3チャンネルとデジタル出力2チャンネルによって,インターネット経由でスマホやデータベースから測定結果の閲覧と制御を可能したアプリである. 電圧入力3チャンネルは,電圧および電流,温度の入力インターフェースとして利用できるように設計した.最大入力電圧は,3.3Vであり,温度は,高精度IC温度センサLM61BIZに対応している. 電流測定については,まだ,電流-電圧変換器(I-V Convertor)仕様が決定していない.

ITOマネジャー 4.0までは,キノマクリエイトを使って複数のAD変換器を使うと正常に電圧が測定できない問題がった.ITO マネジャー4.1移行は,この問題を解決したものである.

ソースコードの管理をOSDN.JPのGitに移行してみました.プロジェクト名は,「ito-manager」です.

変更履歴

変更履歴
日付 Ver. 変更内容 ダウンロードファイル
4.1b1 初期バージョンリリース 20160707ITOManager4.1b1-Basis.zip
4.1b2 アナログ入力電圧上限を微調整 20160707ITOManager4.1b2-Basis.zip
4.1b3 子育て電池監視バージョンリリース 20160810ITOManager4.1b3-Kosodate.zip
4.1b3 稲のもみ乾燥機の廃熱温度モニタリングバージョン 20160810ITOManager4.1b3-RiceDryer.zip

開発環境

開発用パソコンにKinoma Studioをダウンロードしてインストールする. 開発用パソコンと同じLANに無線LANのアクセスポイントをブリッジモードで接続する. 無線LANのアクセスポイントとキノマクリエイトを接続する.

仕様

入出力ピンアサイン

入出力ピンのアサインは表1に示すとおりである. 物理的なピン配置については,Marvell社のProgramming with Hardware Pinsのドキュメントを参照してください.

表1 ITO Manager 4.1のピンアサイン
ピン番号 機能 BLL名称 コード内PIN名称 備考
52 アナログ入力 AD1 AD1 電圧測定用
51 アナログ入力 AD2 AD2 電流測定用;要I-V変換器
54 アナログ入力 LM61BIZ temperature1 温度測定用;要LM61BIZ
4 デジタル出力 DIO DIO1
6 デジタル出力 DIO DIO2
35,36,41,42,58 グラウンド - - -

サーバとのコマンドセット

サーバからキノマクリエイトを制御するとき通信のコマンドセットを表2に示す.デジタル出力のコマンドセットはデラマネジャー互換である.

表2 ITO Manager 4.1のサーバからのコマンドセット
コマンド文字列 機能
on1 DIO1出力ON
off1 DIO1出力OFF
on2 DIO2出力ON
off2 DIO2出力OFF

センターノードの一覧

https://a.yamagata-u.ac.jp/amenity/network/SensorNodeList.aspをブラウザーで閲覧するとセンサーノードのリストが表示される.

ソースコードの解説

以下にコメント付きのソースコード示す. 特にpinのアサインについては,キノマクリエイトのFirmwareを7.1.54のバージョンにアップしたら動作しなくなった. Maravel社が提供している7.1.54から採用されたJS6に対応したサンプルコードを元に開発すると,最初にアプリを起動するときに,Front pins appが立ち上がり,その都度,LCDパネルの[Apply]ボタンをタップする必要があり,無人運転ができない問題があった. この問題を解決するためは,ソースコードの(1)のおまじないをコードに追加した.

本ソースコードは,20160707ITOManager4.1b2-Basis.zipというファイル名でダウンロードできる.

//ピンアサインを設定するときのオブジェクトPinsの定義↓
let Pins = require("pins");
//bdebug変数はKinoma StudioでデバッグするときのデバッグモードのOn=True, Off=Falseで設定↓
const bdebug = false;

//グロバール変数の定義
var temp=0; //温度
var counter=0;
var AD1=0; //アナログ入力ch1の電圧
var AD2=0; //アナログ入力ch2の電圧
var tAD1=0; //アナログ入力ch1の一次保存電圧(積算処理用)
var tAD2=0; //アナログ入力ch2の一次保存電圧(積算処理用)
var ttemp = 0; //温度の一次保存電圧(積算処理用)
var cADC1=0; //アナログ入力ch1の積算回数カウンター
var cADC2=0; //アナログ入力ch2の積算回数カウンター
var ctemp=0; //温度の積算回数カウンター
var bCompletedAD1 = 0; //アナログ入力ch1のAD変換のスタートの完了フラグ
var bCompletedAD2 = 0;//アナログ入力ch2のAD変換のスタートの完了フラグ
var bCompletedTemp = 0; //温度測定のAD変換のスタートの完了フラグ
var nEventID =0; //イベント検出時のIDフラグ(未使用)
var DIO1=0; //DIO1の状態(ON:1 , OFF:0)
var DIO2=0; //DIO2の状態(ON:1 , OFF:0)
var szCommand = ""; //制御コマンド受信変数
//以下,定数の定義↓
const deviceID = K4.deviceID; //キノマクリエイトのデバイスID(例:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
const nDataUploadInterval=30000; //ms データアップロード間隔 単位: ms
const nGetCommandInterval=1000; //ms コマンド取得間隔 単位: ms
const nDisplayInterval=1000; //ms 画面表示更新間隔 単位:ms
const nAccumAD1=4; //AD 1chの積算回数の定義
const nAccumAD2=4; //AD 2chの積算回数の定義
const nAccumTemp=40; //温度の積算回数の定義
//const protomode = "MieSAN"; //制御モード変数: 子育て見守り MieSAN; 稲の水耕栽培 RiceField; ニッケル水素 NiMH ;
//const xAD1Gain = 1.998; //- //- 外部回路に増幅器または分圧
const protomode = "Basis"; 
const xAD1Gain = 1.000; //- 基本の1倍
const szVer = "4.1b1"; //バージョン定義
const xErrorVoltageMax = 3.1; //V AD変換器のノイズ処理のための上限閾値
const xErrorVoltageMin = 0.0; //V AD変換器のノイズ処理のための下限閾値
const xErrorTempMax = 100;//C 温度測定のノイズ処理のための上限値
const xErrorTempMin = -35;//C 温度測定のノイズ処理のための下限値

log("DeiveID: " +deviceID +"\n"); //deviceIDの動作デバッグ用

//画面の見てくれの作成↓
let whiteSkin = new Skin({ fill:"white" });
let normalText = new Style( { font: "30px", color:"black" } );
let smallText = new Style( { font: "20px", color:"black" } ); 
let label0 = new Label({ left:0, right:0, top:0, bottom:0, string:"ITO Manager Ver. " + szVer, style: normalText });
let labelpin = new Label({ left:0, right:0, top:40, bottom:0, string:"AD1:52, AD2:51, Vout(LM61BIZ):54 GND:58", style: smallText });
let label1 = new Label({ left:0, right:0, top:64, bottom:0, string:"---", style: smallText });

let mainCon = new Container({ 
    left:0, right:0, top:0, bottom:0, skin: whiteSkin
});
mainCon.add(label0);
mainCon.add(labelpin);
mainCon.add(label1);
application.add(mainCon);
//ここまで画面の見てくれ

//ここからPinの設定
// analogpins 37 -> 52, 38->51, 39->54
//(1)ピンの設定をするときに,Front pins appがを動作すると毎回[Apply]をタッチする必要がるので,下記のおまじない
Pins.invoke("setPinMux", {
    leftVoltage: 3.3, rightVoltage: 3.3,
    leftPins: [3, 3, 0, 3, 0, 0, 0, 2],
    rightPins: [0, 0, 0, 0, 0, 0, 0, 0]
});
//ここまでおまじない.動作的には,pinのモードが変更がなければ,Front pins appが起動しないことがわかった
//ピンのアサイン設定↓
				Pins.configure({
						analogSensor1: {
						require: "AD1",
						pins: {
							AD1: { pin: 52 }
                  			}
					},
					analogSensor2: {
						require: "AD2",
						pins: {
							AD2: { pin: 51 }
                  			}
					},
					analogTemp: {
						require: "LM61BIZ",
						pins: {
							temperature1: { pin: 54 }
                 			}
               		},
              		DIO: {
               			require: "DIO",
                			pins: {
                 				DIO1: { pin: 4 },
                  				DIO2: { pin: 6 }
               			}
            		},
               		ground: {
               			pin: 58, type: "Ground"
               			}
					}
					, function(success) {
   						 if (success){
   						 log("Successed to Pin configure\n");
   						//Pins.repeat("/analogTemp/read", 50, value => TemperatureValueChanged(value));
						//Pins.repeat("/analogSensor1/read", 3, value => Analog1ValueChanged(value));
						//Pins.repeat("/analogSensor2/read", 3, value => Analog2ValueChanged(value));
						//Pins.share("ws", {zeroconf: true, name: "analog-temperature"});
                        //アナログ測定開始↓
						application.invoke(new Message("/AnalogMeasureTimer"));
   						 }else{
   						 
   						  log("Failed to configure\n");
   						  }
					}
				);


        application.invoke(new Message("/UploadTimer")); //アップロードのタイマースタート
        application.invoke(new Message("/GetCommandTimer")); //コマンド取得タイマーのスタート
        application.invoke(new Message("/DisplayTimer")); //画面更新タイマーのスタート


//--sub routine --				
function log(messsage) {
if (bdebug==true){
   trace(messsage);
	}
};

function sleep(time) {
		 let d1 = new Date().getTime();
		 let d2 = new Date().getTime();
			 while (d2 < d1 + time) {
  				  d2 = new Date().getTime();
  				 }
 				  return;
};
	
function TemperatureValueChanged(value){
		let xtemp1 = value.temperature1;
		if (xtemp1 < xErrorTempMax && xtemp1 > xErrorTempMin) {
			ttemp = (ttemp * ctemp + xtemp1)/(ctemp+1);
			ctemp=ctemp+1;
		}
		if (ctemp==nAccumTemp){
			temp= ttemp;
			ctemp =0;
			log("temperature:"+temp.toFixed(3) + ' C AD1:'+AD1.toFixed(3)+'V AD2:'+AD2.toFixed(3)+'V\n');
			if ( bCompletedTemp==0){
	 			bCompletedTemp = 1;
	 			application.invoke(new Message("/KinomaDataUpload"));
			 }
		}					
}

function Analog1ValueChanged(value){
	let voltage = value.AD1;
	if (voltage <= xErrorVoltageMax && voltage >= xErrorVoltageMin) {
		tAD1 = (tAD1 * cADC1 +voltage/xAD1Gain)/(cADC1+1);
		cADC1 = cADC1+1;
	}
	if (cADC1==nAccumAD1){
		AD1= tAD1;
		cADC1 = 0;
		if ( bCompletedAD1==0){
	 		bCompletedAD1 = 1;
	 		application.invoke(new Message("/KinomaDataUpload"));
	 }
	}
//	log("AD1:"+AD1.toFixed(3) + 'V AD2:'+AD2.toFixed(3) +'\n');
}
function Analog2ValueChanged(value){
	let voltage = value.AD2;
	if (voltage <= xErrorVoltageMax && voltage >= xErrorVoltageMin) {
		tAD2 = (tAD2 * cADC2 +voltage)/(cADC2+1);
		cADC2 = cADC2+1;
	}
	if (cADC2==nAccumAD2){
		AD2= tAD2;
		cADC2 = 0;
		if ( bCompletedAD2==0){
	 		bCompletedAD2 = 1;
	 		application.invoke(new Message("/KinomaDataUpload"));
	 	}
	}
//	log('AD2:'+AD2.toFixed(3) +'\n');
}

//--handler routine--
//測定スケジューラ―タイマーハンドラー
Handler.bind("/AnalogMeasureTimer", {
    onInvoke: function(handler, message){
    log("TimeAnalog \n");
    if (nADChannel==1){
   		Pins.invoke("/analogTemp/read", value => TemperatureValueChanged(value));
		
   	  	Pins.invoke("setPinMux", {
  	 	 leftVoltage: 3.3, rightVoltage: 3.3,
  		  leftPins: [3, 0, 0, 0, 0, 0, 0, 2],
  		  rightPins: [0, 0, 0, 0, 0, 0, 0, 0]
		});
   	}
   	if (nADChannel==2){
   		Pins.invoke("/analogSensor1/read", value => Analog1ValueChanged(value));
   		
   		Pins.invoke("setPinMux", {
  	 	 leftVoltage: 3.3, rightVoltage: 3.3,
  		  leftPins: [0, 0, 0, 3, 0, 0, 0, 2],
  		  rightPins: [0, 0, 0, 0, 0, 0, 0, 0]
		});
   	}
   	if (nADChannel==3){
   		 Pins.invoke("/analogSensor2/read", value => Analog2ValueChanged(value));
   		 
   		 Pins.invoke("setPinMux", {
  	 	 leftVoltage: 3.3, rightVoltage: 3.3,
  		  leftPins: [0, 3, 0, 0, 0, 0, 0, 2],
  		  rightPins: [0, 0, 0, 0, 0, 0, 0, 0]
		});
   	}
   	 nADChannel = nADChannel +1;
     if (nADChannel > 3){
     	nADChannel = 1;
     } 
     counter=counter+1;
   handler.invoke( new Message("/delayAnalog?duration=" +nAnalogMeasureInterval ));
    },
    onComplete: function(handler, message){
     log("Time2 \n");
     //    handler.invoke( new Message("/delay?duration=" +nDataUploadInterval ));
    }
});

Handler.bind("/delayAnalog", {
    onInvoke: function(handler, message){
    	let query = parseQuery( message.query );
		let duration = query.duration;
        handler.wait(duration); 
    },
    onComplete: function(handler, message){
        handler.invoke(new Message("/AnalogMeasureTimer"));
    }
});


//アップロードタイマー
Handler.bind("/UploadTimer", {
    onInvoke: function(handler, message){
    handler.invoke(new Message("/KinomaDataUpload"));
     log("Time \n");
   handler.invoke( new Message("/delay?duration=" +nDataUploadInterval ));
    },
    onComplete: function(handler, message){
     log("Time2 \n");   
     //    handler.invoke( new Message("/delay?duration=" +nDataUploadInterval ));
    }
});

Handler.bind("/delay", {
    onInvoke: function(handler, message){
    	let query = parseQuery( message.query );
		let duration = query.duration;
        handler.wait(duration); //will call onComplete after 10 seconds
    },
    onComplete: function(handler, message){
        handler.invoke(new Message("/UploadTimer"));
    }
});
//ここまでアップロードタイマー

//サーバからコマンドを取得するコマンド取得タイマー
Handler.bind("/GetCommandTimer", {
    onInvoke: function(handler, message){
    handler.invoke(new Message("/KinomaGetCommand"));
     log("GetCommandTimer\n");
   handler.invoke( new Message("/delayCommand?duration=" +nGetCommandInterval ));
    },
    onComplete: function(handler, message){
     log("GetCommandTimer2 \n");   
     //    handler.invoke( new Message("/delay?duration=" +nDataUploadInterval ));
    }
});

Handler.bind("/delayCommand", {
    onInvoke: function(handler, message){
    	let query = parseQuery( message.query );
		let duration = query.duration;
        handler.wait(duration); 
    },
    onComplete: function(handler, message){
        handler.invoke(new Message("/GetCommandTimer"));
    }
});
//ここまでコマンド取得タイマー

//画面を更新する更新タイマー
Handler.bind("/DisplayTimer", {
    onInvoke: function(handler, message){
    label1.string = "Temperature:"+temp.toFixed(1) + ' C AD1:'+AD1.toFixed(3)+' V AD2:'+AD2.toFixed(3)+' V';
    handler.invoke( new Message("/delayDisplay?duration=" +nDisplayInterval ));
    log("TImeD\n");
    },
    onComplete: function(handler, message){
     log("TimeD2 \n");   
     //     handler.invoke( new Message("/delayCommand?duration=" +nDisplayInterval ));
    }
});

Handler.bind("/delayDisplay", {
    onInvoke: function(handler, message){
    	let query = parseQuery( message.query );
		let duration = query.duration;
        handler.wait(duration); //will call onComplete after 10 seconds
    },
    onComplete: function(handler, message){
        handler.invoke(new Message("/DisplayTimer"));
    }
});
//ここまで更新タイマー

//アップロードモジュールの処理ルーチン
Handler.bind("/KinomaDataUpload", {
			onInvoke: function(handler, message){
            //以下,測定データのアップロード処理
			if (bCompletedAD1 == 1 && bCompletedAD2 == 1 && bCompletedTemp == 1){
				var uri = "http://a.yamagata-u.ac.jp/amenity/network/M2M/KinomaDataReciever.aspx?DeviceID="+deviceID;
				uri = uri + "&AI1=" + AD1;
				uri = uri + "&AI2=" + AD2;
				uri = uri + "&AI3=" + temp;
				uri = uri + "&pm=" + protomode;
				uri = uri + "&EventID=" + nEventID;
				handler.invoke( new Message( uri ), Message.TEXT );
				log("uri:" + uri +"\n");
				}
			},	
			onComplete: function(handler, message){
			//szCommand = data
				//application.distribute( "onLabelDisplayCom", "Command: " + message.status + "," + szCommand);
			log("Http:" +  message.status +"\n");
			},	
			onError: function(handler, message){
				let result = { success: false, items: [] };
				log("Http error:" +  result +"\n");
				//application.distribute( "onLabelDisplayCom", "Command: " + result);
			}	
	}
);

//コマンド取得のルーチン	
Handler.bind("/KinomaGetCommand", {
			onInvoke: function(handler, message){
				var uri = "http://a.yamagata-u.ac.jp/amenity/network/M2M/KinomaControlRequest.aspx?DeviceID="+deviceID;
				handler.invoke( new Message( uri ), Message.TEXT );
			},	
			onComplete: function(handler, message,data){
			szCommand = data
                //以下得られたコマンドの処理ルーチン
				//command analysis
				if (szCommand.match(/on1/)){
                    //on1のコマンド処理
					application.invoke(new Message("/KinomaDataUpload"));
					DIO1=1;
					application.invoke(new MessageWithObject("pins:/DIO/turnOn1"));
					sleep( 50 );
					application.invoke(new Message("/KinomaDataUpload"));
				   }
				   if(szCommand.match(/off1/)) {
                     //off1のコマンド処理
					application.invoke(new Message("/KinomaDataUpload"));
					DIO1=0;
					application.invoke(new MessageWithObject("pins:/DIO/turnOff1"));
					sleep( 50 );
					application.invoke(new Message("/KinomaDataUpload"));
					}
				   if(szCommand.match(/on2/)){
                    //on2のコマンド処理
					DIO2=1;
					application.invoke(new MessageWithObject("pins:/DIO/turnOn2"));
				   }
				   if(szCommand.match(/off2/)) {
                    //off2のコマンド処理
					DIO2=0;
					application.invoke(new MessageWithObject("pins:/DIO/turnOff2"));
					}	 
				 log("DO1(4): "+DIO1+"\n");
            	 log("DO2(6): "+DIO2+"\n");
			    log("Http-GetCommand: " + message.status + "," + szCommand +"\n");
			},	
			onError: function(handler, message){
				let result = { success: false, items: [] };
				log("Http error:" +  result +"\n");
				//application.distribute( "onLabelDisplayCom", "Command: " + result);
			}	
	}
);

でら キノマクリエイト
デラさんのM2MとIoT
菅野の電卓
菅野の電卓2
菅野のクラウドポテンショスタット
その他のキノマクリエイトのソースコード 旧米沢高等工業学校の設立
リチウム電池とLEDによるイネの室内水耕栽培
伊藤の冬休みの自由研究 紙おむつの吸水性高分子を培地で稲の苗は育つか?

学会発表

エコ研究のすすめ


山形大学 大学院 理工学研究科 C1ラボラトリー
〒992-8510 山形県米沢市城南4丁目3-16 3号館(物質化学工学科棟) 3-3301
准教授 伊藤智博
0238-26-3753
http://c1.yz.yamagata-u.ac.jp/

QRコード
https://edu.yz.yamagata-u.ac.jp/Public/54299/c1/IoT/KinomaCreate/ITOmanager4.1/Default.asp