//
//  RotationControl.m
//  制御部
//  ChariReco
//
//  Created by AIG-Soft on 11/07/13.
//  Copyright 2011 AIG-Soft. All rights reserved.
//

#import "RotationControl.h"
#import	"FileControl2.h"
#import	"main.h"
#import	"libs.h"
#import	"setup.h"	// LEVEL_*

@implementation RotationControl

//-------------------------------------------------------------------
// 初期化 
//-------------------------------------------------------------------

-(id)initWithSampling:(NSUInteger)sampling
{
	printSelf();
	self=[super init];
	if (self!=nil) {
		[self initialize:sampling];
	}
	return(self);
}

// 初期化
// しきい値は全て初期化されるので、設定済みの値はこの後にreadSettingsで読み込む
-(void)initialize:(NSUInteger)sampling
{
	printSelf();
	// データ範囲と格子分割数；データ源別
#ifdef	JIKUBETU_MAXMINY
	// 必要な処理をここに実装する。以下は例
	for (NSUInteger i=GRAPH_X;i<=GRAPH_ALL;i++)
#endif
	{
		// 最大最小値
		_minY(DATA_DEGREE	,i)=-1.0;		// -90
		_maxY(DATA_DEGREE	,i)= 1.0;		// +90
		_minY(DATA_VELOCITY	,i)=-M_PI*2;	// 角速度なので、これを越えることもある
		_maxY(DATA_VELOCITY	,i)= M_PI*2;
		_minY(DATA_ROTATION	,i)=-M_PI;
		_maxY(DATA_ROTATION	,i)= M_PI;
	}
	// 格子分割数；データ源別
	for (NSUInteger i=GRAPH_X;i<=GRAPH_ALL;i++) {
		ycount[DATA_DEGREE  ][i]=180/15;	// 180度を15度単位で
		ycount[DATA_VELOCITY][i]=4*2;		// +-両方あるから２倍
		ycount[DATA_ROTATION][i]=(360/30);
	}
	ycount[DATA_ROTATION][GRAPH_Z]= (360/45);	// 方位だけ別
	
	// CMMotionManager生成
	_opQueueMotion=nil; // サンプリングはまだ開始しない
	motionManager = [[CMMotionManager alloc] init];
	
	// データ読み込み用ワーク確保
	dArray = [[NSMutableArray alloc] init];
	samples=0;
	cdArray=nil;
	
	interval=((NSTimeInterval)(SAMPLING)/(NSTimeInterval)(SEC_MS)); // 先に型キャストしておかないと整数演算されて0になってしまう
	_datasPerSec=(SEC_MS/sampling);
	nslog(@"1秒間のサンプリング回数=%d,サンプリングインターバル=%f(秒)",(int)_datasPerSec,interval);
	
	// 初期化
	fsetNortification=NO;	// 通知設定してない
	_fpause=NO;				// 一時停止しない（初期）
	_fLevel=LEVEL_OFF;		// デフォルトではレベル判定しない
	counter=0;				// カウンター未使用
	_dataSource=self;		// デフォルトのデータ格納メソッド；必要なら再設定すること
	[self resetPeakHold];
	[self resetThreshold];
	_levelCount=0;
}

-(void)setAlarm3DNortification:(id)target_ selector:(SEL)selector_
// 3Dアラームに対する通知登録
{
	printSelf();
	[[NSNotificationCenter defaultCenter] addObserver:target_
											 selector:selector_ // すべての引数がいることに注意!!
												 name:NOTIFICATION_ALARM
											   object:nil];
	fsetNortification=YES; // 通知設定済み
}

-(void)stopAlarm3DNortification
// 通知の解除
{
	printSelf();
	if (fsetNortification) {
		[[NSNotificationCenter defaultCenter]removeObserver:self
													   name:NOTIFICATION_ALARM
													 object:nil];
		fsetNortification=NO; // 通知解除
	}
}

-(void)setAlarm3DLevel:(NSUInteger)onoff
// しきい値のレベル判定を有効にするかどうか
// (3Dアラーム設定中など)通常はOFF。実録画中のみ必要に応じて設定する。
// onoff=LEVEL_*;setup.h
{
	_fLevel=onoff;
	// 設定したときにレベルカウンターを初期化する
	_levelCount=0;
	// cancelGno,cancelDnoの初期化はここではしない
}

//-------------------------------------------------------------------
// 再初期化処理
//-------------------------------------------------------------------

-(void)resetPeakHold
// Peakの初期化
{
	for (NSUInteger j=DATA_DEGREE;j<=DATA_ROTATION;j++) {
		for (NSUInteger i=GRAPH_X;i<=GRAPH_Z;i++) {
			GRAPH_SPECIFICATIONS *spc=[self specDnoGno:j gno:i];
			spc->peakMax=_minY(j,i); // まずは最小値を最大に
			spc->peakMin=_maxY(j,i); // 最大値を最小に設定しておく
			// 真の値はmin〜maxの範囲で収まるとは限らない
			spc->truePeakMax=DEF_TRUE_PEAK_MAX;
			spc->truePeakMin=DEF_TRUE_PEAK_MIN;
		}
	}
}

-(void)resetThreshold
// しきい値の初期化
{
	for (NSUInteger j=DATA_DEGREE;j<=DATA_ROTATION;j++) {
		for (NSUInteger i=GRAPH_X;i<=GRAPH_Z;i++) {
			GRAPH_SPECIFICATIONS *spc=[self specDnoGno:j gno:i];
			spc->threshold1 =_maxY(j,i);
			spc->threshold2 =_minY(j,i);
			spc->fthreshold1=
			spc->fthreshold2=NO;
		}
	}
}

//-------------------------------------------------------------------
// サンプリング処理
//-------------------------------------------------------------------

-(void)selectData:(NSUInteger)dno_
// サンプリングするデータ源を指定
{
	_dno=dno_;
}

-(void)startSampling
// サンプリング処理設定
{
	printSelf();
	nslog(@"_dno=%d,interval=%f",(int)_dno,interval);
	
	_opQueueMotion = [[NSOperationQueue alloc] init];
	
	// デバイスモーション取得間隔 [sec]
	// これは実機でしか実行できないけど、if (getDeviceType()!=DEVICE_SIMULATOR) は入れない
	motionManager.deviceMotionUpdateInterval = interval;
	
	// デバイスモーションの取得開始
	// 現在のバージョンでは、一度に使えるデータ源は１つのみ
	[motionManager startDeviceMotionUpdatesToQueue:_opQueueMotion
			withHandler:^(CMDeviceMotion *data, NSError *error) {
				dispatch_async(dispatch_get_main_queue(),
					^{
						//nslog(@"sampling:_fpause=%d",_fpause);
						if (counter>0) { // サンプリングカウンター利用中
							counter--;
						}
						if (!_fpause) {
							CMRotationRate xyz;
							CMAcceleration axyz;
							//nslog(@"sampling:_dno=%d",_dno);
							switch (_dno) {
								case DATA_DEGREE: 
									//nslog(@"角度");
									axyz=data.gravity; // CMDeviceMotionはクラス,CMAccelerationは構造体なので取り出しが必要
									[_dataSource rotationControlStoreData:(CMRotationRate *)&axyz];
									// 本来CMAcceleration*だが、内容が同じなので型キャストで渡す
									break;
								case DATA_VELOCITY:
									//nslog(@"角速度");
									// ラジアン/秒＝角速度
									xyz=data.rotationRate; // CMDeviceMotionはクラス,CMRotationRateは構造体なので取り出しが必要
									[_dataSource rotationControlStoreData:&xyz];
									break;
								case DATA_ROTATION:
									//nslog(@"揺れ");
									xyz.x=data.attitude.roll;	// 左右の揺れ(ラジアン）
									xyz.y=data.attitude.pitch;	// 上下の揺れ(ラジアン）
									xyz.z=data.attitude.yaw;	// 方位(ラジアン）
									[_dataSource rotationControlStoreData:&xyz];
									break;
							}
						} // 一時停止中は無視
					} // ^{
				); // dispatch_async
			} // withHandler
	 ];
}

//-------------------------------------------------------------------
// データ制御
//-------------------------------------------------------------------

// 外部からのアクセス用1
-(GRAPH_SPECIFICATIONS *)specDnoGno
{
	return(&spec[_dno][_gno]);
}

// 外部からのアクセス用2
-(GRAPH_SPECIFICATIONS *)specDnoGno:(NSUInteger)gno_
{
	return(&spec[_dno][gno_]);
}

// 外部からのアクセス用3
-(GRAPH_SPECIFICATIONS *)specDnoGno:(NSUInteger)dno_ gno:(NSUInteger)gno_
{
	return(&spec[dno_][gno_]);
}

-(NSUInteger)getYcount
{
	return (ycount[_dno][_gno]);
}

// dArray[index=時間]
-(float)getDarray:(NSUInteger)index gno:(NSUInteger)gno_
{
//	printf("getDarray[%d]:[%d]=",plotIndex,index);
	#if 0
	NSValue *dat=dArray[index];
	#else	// こっちのほうがほんの僅かに速い
	// gno_固定でindex=0〜全部の順に呼び出されることを前提に、列挙型を使っている
	if (index==0) {
		em=[dArray objectEnumerator]; // 先頭にする
	}
	NSValue *dat=[em nextObject];
	#endif
	CMRotationRate xyz;
	[dat getValue:&xyz];
	float f;
	switch (gno_) {
		default: // 警告回避のため
		case GRAPH_X:
			f=(float)xyz.x;
			break;
		case GRAPH_Y:
			f=(float)xyz.y;
			break;
		case GRAPH_Z:
			f=(float)xyz.z;
			break;
	}
	f=ExchangeValue(f,_dno,gno_); // 数値変換
//	printf("%f\n",f);
	return (f);
}

// 時間軸方向のデータ数
-(NSUInteger)getDarrayCount
{
	return [dArray count]; // 今回は3軸同時なのですべて同数
}

-(float)getMaxY
{
	return (_maxY(_dno,_gno));
}

-(float)getMinY
{
	return (_minY(_dno,_gno));
}

//-------------------------------------------------------------------

// データ配列を空にする
-(void)eraseData
{
	printSelf();
	if (samples>0) {
		[dArray removeAllObjects];
		samples=0;
	}
}

// 1回前のデータを保存する
// 一時停止中の軸選択による再描画のため
-(void)copyData
{
	printSelf();
	if (samples>0) {
		cdArray=[dArray copy];
	}
	csamples=samples;
}

-(void)copyBackupData
// バックアップ内容のコピー
{
	samples=csamples;
	for (id dat in cdArray) {
		[dArray addObject:dat];
	}
}

-(BOOL)addData:(CMRotationRate *)xyz
{
	if (samples<_datasPerSec) {
		// データを配列に入れる
		// グラフモジュールにはfloat値を与える
		[dArray addObject:[NSValue value:xyz withObjCType:@encode(CMRotationRate)]];
		samples++;
	}
#if 0
	if (samples==_datasPerSec) {
		nslog(@"samples=%d:count=%d",samples,[dArray count]);
	}
#endif
	return (samples==_datasPerSec);
}

//-------------------------------------------------------------------

// しきい値設定が何処かにあるかどうか調べる
// YES=どこかにあり、NO=どこにもなし
-(BOOL)checkAllThreshold
{
	printSelf();
	for (NSUInteger j=DATA_DEGREE;j<=DATA_ROTATION;j++) {
		printf("dno=%d\n",(int)j);
		for (NSUInteger i=GRAPH_X;i<=GRAPH_Z;i++) {
			GRAPH_SPECIFICATIONS *spc=[self specDnoGno:j gno:i];
			printf("gno=%d:flag=%d:%d\n",(int)i,(int)spc->fthreshold1,(int)spc->fthreshold2);
			if (spc->fthreshold1 || spc->fthreshold2) return (YES);
		}
	}
	// どこにもない
	return (NO);
}

//-------------------------------------------------------------------

// 最小/最大値設定
// 制限なし
-(NSUInteger)checkMaxMin:(float)f maxwork:(float *)maxwork minwork:(float *)minwork
{
	if (f>*maxwork) {
		*maxwork=f;
		return (CHANGE_MAX); // 最大値変更
	}
	if (f<*minwork) {
		*minwork=f; // 最小値変更
		return(CHANGE_MIN);
	}
	return(CHANGE_NONE);
}

// 最小/最大値設定
// min/max制限付き
-(NSUInteger)checkMaxMin:(float)f maxwork:(float *)maxwork minwork:(float *)minwork dno:(NSUInteger)dno_
{
	float maxY_=_maxY(dno_,_gno);
	if (f>*maxwork) {
		if (f>maxY_) f=maxY_;
		*maxwork=f;
		return (CHANGE_MAX); // 最大値変更
	}
	float minY_=_minY(dno_,_gno);
	if (f<*minwork) {
		if (f<minY_) f=minY_;
		*minwork=f; // 最小値変更
		return(CHANGE_MIN);
	}
	return(CHANGE_NONE);
}

// 値の変換式
// dno=DATA_* , gno=GRAPH_*
float ExchangeValue(float f,NSUInteger dno,NSUInteger gno)
{
	switch (dno) {
		case DATA_DEGREE:
		case DATA_VELOCITY:
			switch (gno) {
				case GRAPH_X:
					// そのまま
					break;
				case GRAPH_Y:
				case GRAPH_Z:
					// 符号反転
					f=-f;
					break;
			}
			break;
		case DATA_ROTATION:
			switch (gno) {
				case GRAPH_Z: // 方位
					// 東西南北だけならこうしたいが、中間値がうまく変換されないのでそのままで
					// f+=M_PI; // 北を0にする
					break;
				case GRAPH_X:
				case GRAPH_Y:
					// そのまま
					break;
			}
			break;
	}
	return (f);
}

// 数値の文字列変換
// ローカル値に確保されるので注意
NSString *FloatString(NSUInteger dno,float f)
{
	NSString *fmt;
	switch (dno) {
		default: // 警告回避のため
		case DATA_DEGREE:
			fmt=FMT_DATA_DEGREE;	// -90〜+90
			f*=90.0;
			break;
		case DATA_VELOCITY:
			fmt=FMT_DATA_VELOCITY;	// -2π〜+2π(6.28)
			break;
		case DATA_ROTATION:
			fmt=FMT_DATA_ROTATION;	// -π〜+π*(180/π)=+-180
			f*=(180.0/M_PI); // 本当はf=90*x*2/π
			break;
	}
	return [NSString stringWithFormat:fmt,f];
}

// 数値を文字列化
-(NSString *)float2String:(float)f
{
	return (FloatString(_dno,f));
}

// しきい値アラーム判定
-(BOOL)alarmThreshold:(float)f gno:(NSUInteger)no
{
	NSUInteger flag=0;
	float thres1,thres2;
	GRAPH_SPECIFICATIONS *spc=[self specDnoGno:no];
//	printSelf();
//	nslog(@"dno=%d,no=%d:flag=%d:%d",_dno,no,spc->fthreshold1,spc->fthreshold2);
//	nslog(@"dno=%d,no=%d:f=%f",_dno,no,f);
	if (spc->fthreshold1) {
		thres1=spc->threshold1;
//		nslog(@"(1)=%f",thres1);
		flag|=BIT1;
	}
    if (spc->fthreshold2) {
		thres2=spc->threshold2;
//		nslog(@"(2)=%f",thres2);
		flag|=BIT2;
	}
	if (flag==0) {
//		nslog(@"しきい値設定なし");
		return(NO);
	}
	if (flag==(BIT1|BIT2)) {
		// 両方あるとき
//		nslog(@"%f<%f && >%f",f,thres1,thres2);
		if (thres2>thres1) {
			if (f<thres2 && f>thres1) {
//				nslog(@"f<thres2 && f>thres1");
				return (NO);
			}
		} else {
			if (f<thres1 && f>thres2) {
//				nslog(@"f<thres1 && f>thres2");
				return (NO);
			}
		}
		// 範囲外なので発報
	} else {
		// 片方しかない
		if (flag==BIT2) { // thres2の設定しかないとき
			// thres1に入れる
			thres1=thres2;
		}
		// 中央値算出
		float center=(_maxY(_dno,_gno)+_minY(_dno,_gno))/2;
//		nslog(@"%f<%f:center=%f",f,thres1,center);
		if (!(thres1>=center && f>=thres1) && !(thres1<center && f<=thres1)) {
//			nslog(@"(thres1>=center && f>=thres1) && !(thres1<center && f<=thres1)");
			return(NO);
		}
	}
	// 発報
//	nslog(@"しきい値超え:dno=%d,gno=%d",_dno,no);
	return (YES);
}

// １軸データPeak/しきい値判定(外部＝表示用)
-(BOOL)checkData1:(float *)f_ gno:(NSUInteger)no checkResult:(NSUInteger *)result
{
	float f=ExchangeValue(*f_,_dno,no); // 数値変換
	*f_=f;
	GRAPH_SPECIFICATIONS *spc=[self specDnoGno:no];
	NSUInteger ret=[self checkMaxMin:f maxwork:&spc->truePeakMax minwork:&spc->truePeakMin];
				   [self checkMaxMin:f maxwork:&spc->peakMax	 minwork:&spc->peakMin dno:_dno];
	if (result!=NULL) *result=ret; // 判定結果は真の値で行った結果を返す
	// しきい値アラーム判定
	return [self alarmThreshold:f gno:no];
}

// １軸データPeak/しきい値判定(内部＝アラーム検知用)
-(BOOL)checkData0:(float)f gno:(NSUInteger)no
{
	f=ExchangeValue(f,_dno,no); // 数値変換
#if 1
	// アラーム検知用なので本来はpeakの検出も不要だが、念のため残しておく
	// 処理が遅いようなら外しても構わない
	GRAPH_SPECIFICATIONS *spc=[self specDnoGno:no];
	[self checkMaxMin:f maxwork:&spc->truePeakMax minwork:&spc->truePeakMin];
	[self checkMaxMin:f maxwork:&spc->peakMax	  minwork:&spc->peakMin dno:_dno];
#endif
	// しきい値アラーム判定
	return [self alarmThreshold:f gno:no];
}

// アラーム発報処理
// falarm==YES : 発報してる
-(void)triggerAlarm3D:(BOOL)falarm
{
	if (falarm) {
		if (counter>0) { // 判定休止中
			nslog(@"判定休止中:%d",(int)counter);
			return;
		}
		// どこかで発報してるとき
		NSString *mode=NAME_ALARM_EDGE;
		nslog(@"しきい値超え:dno=%d",(int)_dno);
		if (_fLevel!=LEVEL_OFF) { // レベル判定時
			nslog(@"レベル判定");
			// レベルの時は、どのデータ源・軸で発報しても同じとみなして継続する
			if (_levelCount<UINT_MAX) _levelCount++; // オーバーフローしないように上限をつけておく
			if (_levelCount>1) {
				nslog(@"連続中=%d",(int)_levelCount);
				// 連続中の信号発行
				mode=NAME_ALARM_LEVEL;
				
			} // ==1は初回
#ifdef DEBUG
			else nslog(@"初回時は通常発報と同じ");
#endif
		} // if (_fLevel)
#ifdef DEBUG
		else {
			nslog(@"Edge判定");
		}
#endif
		// 発報信号を出す
		nslog(@"3Dアラーム発報:mode=%@",mode);
		counter=_datasPerSec*1;	// １秒の間アラーム判定を休止する
								// リトリガーなしなら、アラームポスト中止めるという手もある
		[[NSNotificationCenter defaultCenter]postNotificationName:NOTIFICATION_ALARM object:mode];
		// このアラームの処理が終了するまで発報を無視する
	} else { // if (!falarm)
		// どこも発報しないとき
		if (_fLevel!=LEVEL_OFF) { // レベル判定時
			_levelCount=0; // 連続カウンターリセット:次回は初回
		}
		counter=0; // 判定休止も解除
	}
}

// データ格納(内部＝アラーム検知用)
// 外部からデリゲートを指定しないときは_dataSource=selfを指定すること
-(void)rotationControlStoreData:(CMRotationRate *)xyz
{
//	printSelf();
	// データの判定
	// ここはアラーム判定なので、１つでも発報が見つかればそれでOK
	BOOL		 falarm=[self checkData0:xyz->x gno:GRAPH_X];
	if (!falarm) falarm=[self checkData0:xyz->y gno:GRAPH_Y];
	if (!falarm) falarm=[self checkData0:xyz->z gno:GRAPH_Z];
	[self triggerAlarm3D:falarm];
}

//-------------------------------------------------------------------
// 設定値の保存と復帰
//-------------------------------------------------------------------
// 保存するのはspecの値だけなので、データ源については別途外部で保存すること

// ファイルがないときに初期設定をする
-(void)defaultSettings
{
	printSelf();
	// initWithSamplingで完全初期化はされているので、ここで行うのは初期値の設定
#ifdef CHARIRECO
	spec[DATA_VELOCITY][GRAPH_X].threshold1=
	spec[DATA_VELOCITY][GRAPH_Y].threshold1=
	spec[DATA_VELOCITY][GRAPH_Z].threshold1=2.08; // π/2〜πの間
	spec[DATA_VELOCITY][GRAPH_X].fthreshold1=
	spec[DATA_VELOCITY][GRAPH_Y].fthreshold1=
	spec[DATA_VELOCITY][GRAPH_Z].fthreshold1=YES;
	//
	spec[DATA_VELOCITY][GRAPH_X].threshold2=
	spec[DATA_VELOCITY][GRAPH_Y].threshold2=
	spec[DATA_VELOCITY][GRAPH_Z].threshold2=-spec[DATA_VELOCITY][GRAPH_Z].threshold1;
	spec[DATA_VELOCITY][GRAPH_X].fthreshold2=
	spec[DATA_VELOCITY][GRAPH_Y].fthreshold2=
	spec[DATA_VELOCITY][GRAPH_Z].fthreshold2=YES;
#endif	// ZeroRecではデフォルト設定がOFF＝ユーザーによる設定が必要なので、これは不要
}

// 設定を書き出す
-(void)saveSettings:(NSString *)fname
{
	printSelfs(@"%@",fname);
	// Cの構造体をNSDataに直して保存する
	// 出力は常に最新構造にすること
	spec[0][0].version=GRAPH_SPECIFICATIONS_VERSION; // バージョン記録
	NSData *data=[NSData dataWithBytes:spec length:sizeof(spec)];
	if ([data writeToFile:makeFullPathForSaveData(fname) atomically:YES]==NO) {
		// !! error !!
		// 本来エラーを取るべきだが、今回はログは録画動作に対して致命的ではないので、無視する
		// ファイルの大きさは変化しないから、1回作成できたら後は大丈夫なはずで、
		// それでもエラーが出るときはiOS上で致命的な問題が起こっている可能せが高いので、ここで対処すべきではない
	}
	nslog(@"saveSettings:終了");
	// サンプリングされるデータを書きだすのなら、出力している間はサンプリングを一時停止させる必要がある
}

// 設定を読み込む(初期化も含む)
-(BOOL)readSettings:(NSString *)fname
{
	printSelf();
	BOOL ret=NO;
	NSString *path=makeFullPathForSaveData(fname);
	NSFileManager *fileManager=[NSFileManager defaultManager];
	if ([fileManager fileExistsAtPath:path]) {
		// ファイルが存在するときはそれを読み込む
		nslog(@"ファイルあり:path=%@",path);
		NSData *data=[NSData dataWithContentsOfFile:path];
		// 実体読み込み
		[data getBytes:spec length:sizeof(spec)];
		// バージョンチェック
		if (spec[0][0].version!=GRAPH_SPECIFICATIONS_VERSION) {
			nslog(@"specの構造が変わったので変換");
			// 現在は開発中なので何もしない
			// でも正常に読み込めてないにする
		} else {
			ret=YES; // 正常に読み込んだ
		}
	} else { // ファイルがない
		nslog(@"ファイルなし:デフォルト設定");
		[self defaultSettings];
	}
	return (ret);
}

//-------------------------------------------------------------------
// 設定の文字列化
//-------------------------------------------------------------------

#define	SPCAXIS	@"%@%4@(%d):" // スペーサーと軸名分

-(void)threshold2Str:(NSMutableString *)mstr no:(NSUInteger)no axis:(NSString *)axis valueStr:(NSString *)fstr fvalue:(BOOL)fvalue
{
	if (fvalue) {
		[mstr appendFormat:@"%@%4@(%d):%@\n",SPC4,axis,(int)no,fstr];
	} else {
		[mstr appendFormat:@"%@%4@(%d):%@\n",SPC4,axis,(int)no,mesONOFF(NO)];
	}
}

-(NSArray *)nameDataSourceArray
{
	return @[NSLocalStr(@"DEGREE"), NSLocalStr(@"VELOCITY"), NSLocalStr(@"ROTATION")];
}

-(void)makeAlarm3D:(NSMutableString *)mstr textView:(UITextView *)textView selectData:(NSUInteger)dno_
// 設定情報の文字列化(指定データ源）
// 現在はしきい値のみ
{
	/* しきい値あり＝アラーム判定あり
	 角度	x/y/z		* 1/2
	 角速度	x/y/z		* 1/2
	 方向	x=左右揺れ	* 1/2
	 y=上下揺れ	* 1/2
	 z=方位		* 1/2
	 */
	NSArray *axisArray;
	switch (dno_) {
		default: // 警告回避のため
		case DATA_DEGREE:			
		case DATA_VELOCITY:			
			axisArray =@[@"X",@"Y",@"Z"]; // これらはローカライズしようがないのでそのまま
			break;
		case DATA_ROTATION:			
			axisArray =@[
						NSLocalizedString(@"ROLL"	,@"左右"),
						NSLocalizedString(@"PITCH"	,@"上下"),
						NSLocalizedString(@"YAW"	,@"方位"),
						];
			break;
	}
	NSEnumerator *emAxis=[axisArray objectEnumerator]; // 先頭にする
	// 軸別出力
	// 軸前 しきい値\n
	for (NSUInteger i=GRAPH_X;i<=GRAPH_Z;i++) {
		NSString *axis=[emAxis nextObject];
		GRAPH_SPECIFICATIONS *spc=[self specDnoGno:dno_ gno:i];
		[self threshold2Str:mstr no:1 axis:axis valueStr:FloatString(dno_,spc->threshold1) fvalue:spc->fthreshold1];
		[self threshold2Str:mstr no:2 axis:axis valueStr:FloatString(dno_,spc->threshold2) fvalue:spc->fthreshold2];
	}
}

-(void)makeAlarm3D:(NSMutableString *)mstr textView:(UITextView *)textView
// 設定情報の文字列化（全データ源）
{
	NSArray *nameArray=[self nameDataSourceArray];
	NSUInteger j=DATA_DEGREE;
	for (NSString *name in nameArray) {
		[mstr appendFormat:@"%@%@ %@\n",SPC2,name,NSLocalizedString(@"THRESHOLD", @"しきい値")]; // 名前 しきい値\n
		[self makeAlarm3D:mstr textView:textView selectData:j];
		j++;
	}
}

///-------------------------------------------------------------------
// 解放処理
//-------------------------------------------------------------------

-(void)stopRotationControl
// rotationControlの停止
// 開始はinitWithSampling→startSampling
{
	printSelf();

	// サンプリングを停止＆無視させる；これをしないとrelease完了までの間にハンドラーが呼び出されてエラーが出てしまう
	_fpause=YES;
	[motionManager stopDeviceMotionUpdates];

	_fLevel=LEVEL_OFF;	// レベル判定を止める
	
	// アラーム通知の解除
	[self stopAlarm3DNortification];
	
	// startWithSamplingで確保分の開放
	if (_opQueueMotion!=nil) {
		_opQueueMotion=nil;
		nslog(@"_opQueueMotion release");
	} // startSamplingしてないときは確保されてない
	
	// initWithSamplingで確保分の開放
	motionManager = nil;
	
	printSelfs(@"終了%@",@"");
	// これを呼び出してもタイミングにより1回はサンプリングが走ってしまうことがあるので_fpause=YESにしておくのが重要
}

- (void)dealloc
{
	printSelf();
	[self stopRotationControl];
}

//-------------------------------------------------------------------


@end
