C > timer event > double intervals

```txt:動作環境
C (組込み): stdint.hを使わない. time.hない
であるが、実装確認はideoneのCでtime.hを使用した
```

## v0.1

### 処理の概略

- ２つのインターバルがある
  - OI: 長期インターバル
  - MI: 短期インターバル
- 短期インターバルでNセットの処理を行う
- 1セット内でW, R, Sの状態を持つ
- S (Sleep)

![2017-08-02_17h08_41.png](https://qiita-image-store.s3.amazonaws.com/0/32870/f27cd244-032e-cb84-4402-e286b35648e4.png)

### 同値、境界値テスト

上記の図の(1)..(7)で検討した。

### 実装

http://ideone.com/F2WfJ9

```c
#include <stdio.h>
#include <time.h> // use for test. not available on IDE

#define NEXT_ON_EDGE (1)

time_t getElapsedSecond(int yyyy, int mm, int dd, int hh, int nn, int ss){
    struct tm workdt;
    workdt.tm_isdst = -1; // summer time flag
    workdt.tm_year = yyyy - 1900;
    workdt.tm_mon = mm - 1;
    workdt.tm_mday = dd;
    workdt.tm_hour = hh;
    workdt.tm_min = nn;
    workdt.tm_sec = ss;
    return mktime(&workdt);
}

void getDateTimeString(time_t srcdt, char *dstPtr)
{
    if (dstPtr == NULL) {
        return; // error
    }
    struct tm *workdt;
    workdt = localtime(&srcdt); 
    workdt->tm_year = workdt->tm_year + 1900;
    sprintf(dstPtr, "%04d/%02d/%02d %02d:%02d:%02d", 
        workdt->tm_year, workdt->tm_mon + 1, workdt->tm_mday, 
        workdt->tm_hour, workdt->tm_min, workdt->tm_sec
    );
}

typedef struct {
	unsigned int OI; // [s]
	unsigned int MI; // [s]
	unsigned int W; // [s]
	unsigned int R; // [s]
	unsigned int N; // [s]
} SETTING_t;

time_t getNextWtime(time_t torigin, time_t tnow, SETTING_t *setPtr)
{
	if (setPtr == NULL) {
		return 0; // error // TOOD: 0z > use error value for time_t
	}
	// case 1: integral multiple of OI intervals
	unsigned long A = (tnow - torigin) / setPtr->OI;
	if ((tnow - torigin) % setPtr->OI == 0) {
#ifdef NEXT_ON_EDGE
		return torigin + (A * setPtr->OI) + setPtr->MI;
#else
		return torigin + (A + 0) * setPtr->OI;
#endif
	}

    // case 2: after N * MI intervals
	time_t tleft = tnow - (torigin + A * setPtr->OI);
	time_t nmi = setPtr->N * setPtr->MI;
	if (tleft >= (nmi - setPtr->MI)) {
		return torigin + (A + 1) * setPtr->OI;
	}

	// case 3: not integral multiple of OI intervals
	unsigned long B = tleft / setPtr->MI;
	if (tleft % setPtr->MI == 0) {
		//   case 3a: integral multiple of MI intervals
#ifdef NEXT_ON_EDGE
		return torigin + A * setPtr->OI + (B + 1) * setPtr->MI;
#else
		return torigin + A * setPtr->OI + (B + 0) * setPtr->MI;
#endif		
	} else {
		//   case 3b: not integral multiple of MI intervals
		return torigin + A * setPtr->OI + (B + 1) * setPtr->MI;
	}
}

void AssertCheck(time_t torigin, time_t tnow, time_t tnextWans, SETTING_t *setPtr)
{
	static int scnt = 0;
	char szbuf[30];
	if (setPtr == NULL) {
		return; // error
	}
	time_t tnextWres = getNextWtime(torigin, tnow, setPtr);

	scnt++;
    getDateTimeString(tnow, szbuf);
	printf("%03d:%s => ", scnt, szbuf);
    getDateTimeString(tnextWres, szbuf);
	printf("%s:", szbuf);
	printf("%c", (tnextWres == tnextWans) ? 'T' : 'F');
	printf("\r\n");
}

void Test_group_run_basic()
{
	time_t torigin = getElapsedSecond(2017, 8, 2, 12, 48, 55);
	SETTING_t setting = { 3600, 600, 60, 5, 3 }; // OI, MI, W, R, N

	// case (1)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 54),
           	    getElapsedSecond(2017, 8, 2, 13, 48, 55), // W1
	            &setting);
	// case (2)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 55),
           	    getElapsedSecond(2017, 8, 2, 13, 58, 55), // W2
	            &setting);
	// case (3)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 56),
           	    getElapsedSecond(2017, 8, 2, 13, 58, 55), // W2
	            &setting);
	// case (4)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14,  8, 55),
           	    getElapsedSecond(2017, 8, 2, 14, 48, 55), // W4
	            &setting);
	// case (5) same as (4)?
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14,  8, 56),
           	    getElapsedSecond(2017, 8, 2, 14, 48, 55), // W4
	            &setting);
	// case (6) same as (4)?
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14, 48, 54),
           	    getElapsedSecond(2017, 8, 2, 14, 48, 55), // W4
	            &setting);
	// case (7) similar to (2)?
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14, 48, 55),
           	    getElapsedSecond(2017, 8, 2, 14, 58, 55), // W5
	            &setting);

}

void Test_group_run_extra(void)
{
	// TODO: 0c > add extra test, Just in case
}

int main(void) {
	Test_group_run_basic();
	Test_group_run_extra();

	return 0;
}
```

```txt:run
001:2017/08/02 13:48:54 => 2017/08/02 13:48:55:T
002:2017/08/02 13:48:55 => 2017/08/02 13:58:55:T
003:2017/08/02 13:48:56 => 2017/08/02 13:58:55:T
004:2017/08/02 14:08:55 => 2017/08/02 14:48:55:T
005:2017/08/02 14:08:56 => 2017/08/02 14:48:55:T
006:2017/08/02 14:48:54 => 2017/08/02 14:48:55:T
007:2017/08/02 14:48:55 => 2017/08/02 14:58:55:T
```

用意したテストは全部Tになった。

まだテストの抜けはあるかもしれない。

(2)の場合、W2にするのではなくW1にするという定義もありうる。

### 追加テスト

W2の前後のテストも追加したが、全部Tだった。
http://ideone.com/3coLxX

## v0.2 > 仕様漏れ対応 (tnow < torigin)

tnow < toriginの場合の処理が考慮されていなかった。
このため、(tnow - torigin)の計算で不具合が出る。

追加仕様: tnow < toriginの場合、toriginを返す。
(toriginまで待機をする)

http://ideone.com/UJ105S

```c
#include <stdio.h>
#include <time.h> // use for test. not available on IDE

#define NEXT_ON_EDGE (1)

time_t getElapsedSecond(int yyyy, int mm, int dd, int hh, int nn, int ss){
    struct tm workdt;
    workdt.tm_isdst = -1; // summer time flag
    workdt.tm_year = yyyy - 1900;
    workdt.tm_mon = mm - 1;
    workdt.tm_mday = dd;
    workdt.tm_hour = hh;
    workdt.tm_min = nn;
    workdt.tm_sec = ss;
    return mktime(&workdt);
}

void getDateTimeString(time_t srcdt, char *dstPtr)
{
    if (dstPtr == NULL) {
        return; // error
    }
    struct tm *workdt;
    workdt = localtime(&srcdt); 
    workdt->tm_year = workdt->tm_year + 1900;
    sprintf(dstPtr, "%04d/%02d/%02d %02d:%02d:%02d", 
        workdt->tm_year, workdt->tm_mon + 1, workdt->tm_mday, 
        workdt->tm_hour, workdt->tm_min, workdt->tm_sec
    );
}

typedef struct {
	unsigned int OI; // [s]
	unsigned int MI; // [s]
	unsigned int W; // [s]
	unsigned int R; // [s]
	unsigned int N; // [s]
} SETTING_t;

time_t getNextWtime(time_t torigin, time_t tnow, SETTING_t *setPtr)
{
	if (setPtr == NULL) {
		return 0; // error // TOOD: 0z > use error value for time_t
	}

	if (tnow < torigin) {
		return torigin;
	}

	// case 1: integral multiple of OI intervals
	unsigned long A = (tnow - torigin) / setPtr->OI;
	if ((tnow - torigin) % setPtr->OI == 0) {
#ifdef NEXT_ON_EDGE
		return torigin + (A * setPtr->OI) + setPtr->MI;
#else
		return torigin + (A + 0) * setPtr->OI;
#endif
	}

    // case 2: after N * MI intervals
	time_t tleft = tnow - (torigin + A * setPtr->OI);
	time_t nmi = setPtr->N * setPtr->MI;
	if (tleft >= (nmi - setPtr->MI)) {
		return torigin + (A + 1) * setPtr->OI;
	}

	// case 3: not integral multiple of OI intervals
	unsigned long B = tleft / setPtr->MI;
	if (tleft % setPtr->MI == 0) {
		//   case 3a: integral multiple of MI intervals
#ifdef NEXT_ON_EDGE
		return torigin + A * setPtr->OI + (B + 1) * setPtr->MI;
#else
		return torigin + A * setPtr->OI + (B + 0) * setPtr->MI;
#endif		
	} else {
		//   case 3b: not integral multiple of MI intervals
		return torigin + A * setPtr->OI + (B + 1) * setPtr->MI;
	}
}

void AssertCheck(time_t torigin, time_t tnow, time_t tnextWans, SETTING_t *setPtr)
{
	static int scnt = 0;
	char szbuf[30];
	if (setPtr == NULL) {
		return; // error
	}
	time_t tnextWres = getNextWtime(torigin, tnow, setPtr);

	scnt++;
    getDateTimeString(tnow, szbuf);
	printf("%03d:%s => ", scnt, szbuf);
    getDateTimeString(tnextWres, szbuf);
	printf("%s:", szbuf);
	printf("%c", (tnextWres == tnextWans) ? 'T' : 'F');
	printf("\r\n");
}

void Test_group_run_basic()
{
	time_t torigin = getElapsedSecond(2017, 8, 2, 12, 48, 55);
	SETTING_t setting = { 3600, 600, 60, 5, 3 }; // OI, MI, W, R, N

	// case (1)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 54),
           	    getElapsedSecond(2017, 8, 2, 13, 48, 55), // W1
	            &setting);
	// case (2)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 55),
           	    getElapsedSecond(2017, 8, 2, 13, 58, 55), // W2
	            &setting);
	// case (3)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 56),
           	    getElapsedSecond(2017, 8, 2, 13, 58, 55), // W2
	            &setting);
	// case (4)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14,  8, 55),
           	    getElapsedSecond(2017, 8, 2, 14, 48, 55), // W4
	            &setting);
	// case (5) same as (4)?
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14,  8, 56),
           	    getElapsedSecond(2017, 8, 2, 14, 48, 55), // W4
	            &setting);
	// case (6) same as (4)?
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14, 48, 54),
           	    getElapsedSecond(2017, 8, 2, 14, 48, 55), // W4
	            &setting);
	// case (7) similar to (2)?
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 14, 48, 55),
           	    getElapsedSecond(2017, 8, 2, 14, 58, 55), // W5
	            &setting);

}

void Test_group_run_extra(void)
{
	time_t torigin = getElapsedSecond(2017, 8, 2, 12, 48, 55);
	SETTING_t setting = { 3600, 600, 60, 5, 3 }; // OI, MI, W, R, N

	// case (8)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 58, 54),
           	    getElapsedSecond(2017, 8, 2, 13, 58, 55), // W2
	            &setting);
	// case (9)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 58, 55),
           	    getElapsedSecond(2017, 8, 2, 14,  8, 55), // W3
	            &setting);
	// case (10)
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 58, 56),
           	    getElapsedSecond(2017, 8, 2, 14,  8, 55), // W3
	            &setting);

}
	
void Test_group_run_originIsW1(void)
{
	time_t torigin = getElapsedSecond(2017, 8, 2, 13, 48, 55);
	SETTING_t setting = { 3600, 600, 60, 5, 3 }; // OI, MI, W, R, N

	// case (1)'
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 54),
           	    getElapsedSecond(2017, 8, 2, 13, 48, 55), // W1
	            &setting);
	// case (2)'
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 55),
           	    getElapsedSecond(2017, 8, 2, 13, 58, 55), // W2
	            &setting);
	// case (3)'
	AssertCheck(torigin,
		        getElapsedSecond(2017, 8, 2, 13, 48, 56),
           	    getElapsedSecond(2017, 8, 2, 13, 58, 55), // W2
	            &setting);
	
}

int main(void) {
	Test_group_run_basic();
	Test_group_run_extra();
	Test_group_run_originIsW1();

	return 0;
}
```

```txt:run
001:2017/08/02 13:48:54 => 2017/08/02 13:48:55:T
002:2017/08/02 13:48:55 => 2017/08/02 13:58:55:T
003:2017/08/02 13:48:56 => 2017/08/02 13:58:55:T
004:2017/08/02 14:08:55 => 2017/08/02 14:48:55:T
005:2017/08/02 14:08:56 => 2017/08/02 14:48:55:T
006:2017/08/02 14:48:54 => 2017/08/02 14:48:55:T
007:2017/08/02 14:48:55 => 2017/08/02 14:58:55:T
008:2017/08/02 13:58:54 => 2017/08/02 13:58:55:T
009:2017/08/02 13:58:55 => 2017/08/02 14:08:55:T
010:2017/08/02 13:58:56 => 2017/08/02 14:08:55:T
011:2017/08/02 13:48:54 => 2017/08/02 13:48:55:T
012:2017/08/02 13:48:55 => 2017/08/02 13:58:55:T
013:2017/08/02 13:48:56 => 2017/08/02 13:58:55:T
```

## テスト案

こういう場合のテストを行う場合、案としては以下のようなものだろうか。

1秒ごとにカウントアップしていく時刻を引数に取り、ある範囲の時間nextWtimeを取り続ける。
その結果の連続性を確認して、特異な結果が見つからないこと。

### テスト追加

以下のようなテストを追加。

```c
void Test_loop_hours(void)
{
    time_t torigin = getElapsedSecond(2017, 8, 2, 13, 48, 55);
    SETTING_t setting = { 3600, 600, 60, 5, 3 }; // OI, MI, W, R, N

    time_t tnow = getElapsedSecond(2017, 8, 2, 12, 0, 0);
    time_t tnextW;

    for(int hi=0; hi < 7; hi++) { // hi: hour index
        for(int loop=0; loop < 3600; loop++) { // 3600: 1hour
            tnextW = getNextWtime(torigin, tnow, &setting);
            printf("%ld,%ld\n", tnow, tnextW);
            tnow++;
        }
    }
}
```

CentOS 6.8 (64bit)にてgcc version 4.4.7環境で実行して、csv出力した。

EXCELでExcel Timeに変換した。
参考: [C | datetime > ideoneのCで出力したUNIX timeをExcel上でyyyy/mm/dd hh:nn形式にする](http://qiita.com/7of9/items/4fd0c7497865f5bda266)。

![2017-08-02_19h08_28.png](https://qiita-image-store.s3.amazonaws.com/0/32870/9afc1849-a469-d01e-f691-e1d6bac8f9ef.png)

青色が現在日時、オレンジがnextWタイム。
(EXCEL上の凡例の変更は5秒以上の操作がかかりそうだったので、省略)。

大きくは間違っていないような気がする。

日時はUTCになっている。CentOS上とideoneでタイムゾーンが違うのか。

値がロールオーバーしそうな設定には注意が必要かもしれない。

## TODO

[x] NEXT_ON_EDGEを定義していない場合の処理にミス有り。
ifdef定義はこういう点で失敗をする。

```c
#ifdef NEXT_ON_EDGE
        return torigin + (A * setPtr->OI) + setPtr->MI;
#else
        return torigin + (A + 0) * setPtr->OI;
#endif
```

を以下とした。内容は同じであるが、ソースを読んだ時の違和感がなくなった。

```c
#ifdef NEXT_ON_EDGE
        return torigin + (A * setPtr->OI) + setPtr->MI;
#else
        return torigin + (A * setPtr->OI);
#endif
```

### 備考
(追記 2017/08/08)

`S = 0`の場合の対応はしていない。

## v0.3 作り直し

以下のパターンを使うことにした。

- W,R,S,W,R,S
- R,S,R,S (W=0)
- W,R,W,R (S=0)
- W,R,R,R (複数R, S=0)
- R,R,R (複数R, W=0, S=0)

v0.2のコードから修正したものでは可読性が悪いと感じたため設計からしなおした。
stdint.hを使っているが、実使用の組込み上では別のビルトイン型で使っている。
組込み開発環境上で、5分類のそれぞれに境界テストと同値テストを実装して、すべてのパターンのテストをパスしている。

```c
#include <stdio.h>
#include <time.h>
#include <stdint.h>

typedef struct {
    uint32_t OI; // [s] 0..2,764,800 (32days)
    uint16_t MI; // [s] 0..39,600 (11 hours)
    uint16_t W; // [s] 0..999
    uint8_t R; // [s] 1..99
    uint16_t N; // [s] 1..3600
} SETTING_t;

static uint16_t calcNumberOfRun(SETTING_t *setPtr)
{
    uint16_t res;

    if (setPtr->MI != setPtr->R) {
        res = 1;
    } else {
        res = setPtr->N;
    }
    return res;
}

static uint32_t calcSleepSecond(SETTING_t *setPtr)
{
    uint32_t res;
    if (setPtr->MI != setPtr->R) {
        res = setPtr->MI - setPtr->W - setPtr->R;
    } else {
        res = 0;
    }
    return res;    
}

static time_t calcAfterLastR(time_t torigin, time_t tnow, SETTING_t *setPtr)
{   
    /*
    [W,R,S]の場合[R]の後を返す
    [R,S]の場合[R]の後を返す
    [W,R]の場合[R]の後を返す
    [W,R,R,R]の場合、最後の[R]の後を返す
    [R,R,R]の場合、最後の[R]の後を返す
    */

    if (setPtr == NULL) {
        return -1; // error
    }

    uint32_t A = (tnow - torigin) / setPtr->OI; // OIの数
    time_t beforeW = torigin + A * setPtr->OI; // W前
    uint16_t numR = calcNumberOfRun(setPtr);
    uint32_t sleep_sec = calcSleepSecond(setPtr);
    //
    uint16_t B; // MIの数
    if (numR == 1) { // e.g. [W,R,S,W,R,S]
        B = (tnow - beforeW) / setPtr->MI;
    } else { // e.g. [W,R,R,R] or [R,R,R]
        B = 0;
    }

    //
    time_t res = beforeW + B * setPtr->MI;
    res += setPtr->W;
    res += (setPtr->R * numR);

    return res;
}

time_t getNextWtime(time_t torigin, time_t tnow, SETTING_t *setPtr)
{
    /*
    シーケンス[W,R,S,W,R,S]
    シーケンス[R,S,R,S]
    シーケンス[W,R,W,R]
    シーケンス[W,R,R,R]
    シーケンス[R,R,R]
    */

    if (setPtr == NULL) {
        return -1; // error
    }

    if (tnow < torigin) {
        return torigin;
    }

    time_t afterLastR = calcAfterLastR(torigin, tnow, setPtr);    // 短期インターバルの最後のR後
    //
    uint16_t numR = calcNumberOfRun(setPtr); // 連続するRの個数. [W,R,R,R]では3, [W,R,S,W,R,S]では1
    uint32_t slp_sec = calcSleepSecond(setPtr); // 設定値のS秒数. [W,R,S,W,R,S]のS期間

    time_t nextW = afterLastR + slp_sec; // nowから見た次のW前の日時
    // 特殊ケース
    if (slp_sec == 0 && numR == 1) { // [W,R,W,R]の場合
        // 短期インターバルで見た時の[W,R,S]のW前 (特殊ケースでのみ使用)
        time_t beforeW = afterLastR - setPtr->W - setPtr->R; // MI内のW前
        if (tnow == beforeW) {
            // すぐに次の[W]を開始するため、現在日時を返す
            nextW = tnow;
        }
    }

    uint32_t A = (afterLastR - torigin) / setPtr->OI; // 長期インターバルの繰り返し数
    time_t elpsd_sec = nextW - (torigin + A * setPtr->OI); // 短期インターバル中かの判断に使う
    uint32_t datDur_sec = setPtr->MI * setPtr->N; // 短期インターバル終了までの時間
    if (elpsd_sec >= datDur_sec) {
        //　Long Sleep後にする
        return torigin + (A + 1) * setPtr->OI;
    }
    return nextW; // Short Sleep後
}
```

数年後に読みやすいどうか。

手書きの設計資料を整理してみた。
PTN3の境界の扱いが他のPTNと違う。
設計当初は「境界時(W前)には1つ先のWで良い」と考えていたが、PTN3の場合だけは「今のWを使う」必要があると気づいた。
これにより、設計の統一感が崩れた。
PTN3に合わせて他のPTNも「今のWを使う」に変更する方がいいかもしれない。

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/8cfa64b3-8ce6-76b3-307f-cc7bf828591f.png)

## v0.4 > 境界時の処理の変更

v0.3で気づいたPTN3だけ別処理となる点を修正した(PTN3以外をPTN3と同じようにした)。

テストを書き直し、実装を変更したらすべてのテストをパスできた。
以下の部分が変更箇所。特殊ケースがなくなったため、将来のメンテナンス時の混乱が減る。

```c
#if 1 // 2017/08/10
    time_t beforeW = afterLastR - setPtr->W;
    beforeW -= (numR *setPtr->R); // datIntvl内のW前
    if (tnow == beforeW) {
        // すぐに次の[W]を開始するため、現在日時を返す
        nextW = tnow;
    }
#else
    // 特殊ケース
    if (slp_sec == 0 && numR == 1) { // [W,R,W,R]の場合
        // 短期インターバルで見た時の[W,R,S]のW前 (特殊ケースでのみ使用)
        time_t beforeW = afterLastR - setPtr->W- setPtr->R; // datIntvl内のW前
        if (tnow == beforeW) {
            // すぐに次の[W]を開始するため、現在日時を返す
            nextW = tnow;
        }
    }
#endif    
```

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/aeb11b55-a9e9-beb1-6a4e-c63e87eca4d1.png)

## v0.5 > 掛け算時のwrap aroundエラーの修正

設定項目の値を大きくした時に、掛け算のwrap aroundが発生した。

関連:
[組込みC > A + B*n > A:unsigned long, Bとn:unsigned intの時のwrap around > 対策:掛け算の１つをキャストする](http://qiita.com/7of9/items/97b94ab830983fd0e669)


以下のように修正した。

```c
//----------------------------------------------------------------------
#include <stdio.h>
#include <time.h>
#include <stdint.h>

typedef struct {
    uint32_t OI; // [s] 0..2,764,800 (32days)
    uint16_t MI; // [s] 0..39,600 (11 hours)
    uint16_t W; // [s] 0..999
    uint8_t R; // [s] 1..99
    uint16_t N; // [s] 1..3600
} SETTING_t;

static uint16_t calcNumberOfRun(SETTING_t *setPtr)
{
    uint16_t res;

    if (setPtr->MI != setPtr->R) {
        res = 1;
    } else {
        res = setPtr->N;
    }
    return res;
}

static uint32_t calcSleepSecond(SETTING_t *setPtr)
{
    uint32_t res;
    if (setPtr->MI != setPtr->R) {
        res = setPtr->MI - setPtr->W - setPtr->R;
    } else {
        res = 0;
    }
    return res;    
}

static time_t calcAfterLastR(time_t torigin, time_t tnow, SETTING_t *setPtr)
{   
    /*
    [W,R,S]の場合[R]の後を返す
    [R,S]の場合[R]の後を返す
    [W,R]の場合[R]の後を返す
    [W,R,R,R]の場合、最後の[R]の後を返す
    [R,R,R]の場合、最後の[R]の後を返す
    */

    if (setPtr == NULL) {
        return -1; // error
    }

    uint32_t A = (tnow - torigin) / setPtr->OI; // OIの数
    time_t beforeW = torigin + A * setPtr->OI; // W前
    uint16_t numR = calcNumberOfRun(setPtr);
    uint32_t sleep_sec = calcSleepSecond(setPtr);
    //
    uint16_t B; // MIの数
    if (numR == 1) { // e.g. [W,R,S,W,R,S]
        B = (tnow - beforeW) / setPtr->MI;
        if (B == setPtr->N) {
           B--;
        }
    } else { // e.g. [W,R,R,R] or [R,R,R]
        B = 0;
    }

    //
    time_t res = beforeW + B * (time_t)setPtr->MI;
    res += setPtr->W;
    res += ( (time_t)setPtr->R * numR);

    return res;
}

time_t getNextWtime(time_t torigin, time_t tnow, SETTING_t *setPtr)
{
    /*
    シーケンス[W,R,S,W,R,S]
    シーケンス[R,S,R,S]
    シーケンス[W,R,W,R]
    シーケンス[W,R,R,R]
    シーケンス[R,R,R]
    */

    if (setPtr == NULL) {
        return -1; // error
    }

    if (tnow < torigin) {
        return torigin;
    }

    time_t afterLastR = calcAfterLastR(torigin, tnow, setPtr);    // 短期インターバルの最後のR後
    //
    uint16_t numR = calcNumberOfRun(setPtr); // 連続するRの個数. [W,R,R,R]では3, [W,R,S,W,R,S]では1
    time_t slp_sec = calcSleepSecond(setPtr); // 設定値のS秒数. [W,R,S,W,R,S]のS期間

    time_t beforeW = afterLastR - setPtr->W;
    beforeW -= (numR * (time_t)setPtr->R); // datIntvl内のW前
    if (tnow == beforeW) {
        // すぐに次の[W]を開始するため、現在日時を返す
        return tnow;
    }

    time_t A = (afterLastR - torigin) / setPtr->OI; // 長期インターバルの繰り返し数
    time_t elpsd_sec = nextW - (torigin + A * setPtr->OI); // 短期インターバル中かの判断に使う
    time_t datDur_sec = (time_t)setPtr->MI * setPtr->N; // 短期インターバル終了までの時間
    if (elpsd_sec >= datDur_sec) {
        //　Long Sleep後にする
        return torigin + (A + 1) * setPtr->OI;
    }
    return nextW; // Short Sleep後
}
```