-
Notifications
You must be signed in to change notification settings - Fork 0
/
sekisan.ino
346 lines (299 loc) · 14.9 KB
/
sekisan.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
////////////////////////////////////////////////////////////////
// 電池長期間駆動Arduinoロガー
// deslemLogger (deep sleep EEPROM logger)用補助スケッチ
// 積算温度を使う場合の積算処理、LCD表示処理
//
// センサはサーミスタ使用を想定しているが、data_t型の最初の変数を使って
// 積算処理するので、同様の設定のセンサであれば積算処理可能
// ただし、LCD表示は積算に使う温度データ以外には現状対応していない。
//
////////////////////////////////////////////////////////////////
// 積算関係定義
////////////////////////////////////////////////////////////////
#ifdef SEKISAN
#define SEKISAN_LEVERAGE 10 // 積算温度を0.1℃単位で整数計算するため、10倍する。
// unsigned intなので最大積算温度は6502℃日。足りない場合は精度は少し落ちるが5倍値等にする。
// #define BACKUP_DATA_ADDRESS 0 // 積算温度、積算開始日等のバックアップデータを保存するArduino EEPROMのアドレス
// deslemLoggerCofig.h内での定義に変更
#define REF_TEMP (16.0 * SEKISAN_LEVERAGE) // 積算温度の基準温度。16℃は熱帯作物用
////////////////////////////////////////////////////////////////
// 型宣言 + 付属広域変数宣言 積算温度部
/////////////////////////////////////////////////////////////////
typedef struct {
byte Year; // 年
byte Month; // 月
byte Day; // 日
} YMD_t;
typedef struct { //1日の簡易データバックアップ(トラブル復旧用)
byte dataStatus; // 0x00は最終日データ記録有りを示す。
YMD_t startYMD[5]; // 積算開始日(5つ)
YMD_t lastYMD; // 最後の積算日
unsigned int lastDayAvgTemp; // 最後の日平均温度(10倍値)
unsigned int accumTemp[5]; // 最後の積算温度 (10倍値)(16℃基準)(5つ)
unsigned int accumTemp0[5]; // 最後の積算温度 (10倍値)( 0℃基準)(5つ)
byte at[5]; // 積算中のフラグ(5つ)
} lastSekisanData_t;
typedef struct { // 1日の簡易データバックアップのうちstartYMDアクセス用(lastSekisanData_tの前方一部。lcdTime()で使う。)
byte dataStatus; // 0x00は最終日データ記録有りを示す。
YMD_t startYMD[5]; // 積算開始日(5つ)
} startYMDdata_t;
//YMD_t gStartYMD[5] = {{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0}}; // メモリ節約のため、EEPROMの情報を直接使うことにした。
// コードがわかりにくくなったので良くなかったか。
// 積算温度 10倍値のunsigned intなので、最大積算温度は6502℃日。足りない場合は精度は少し落ちるが5倍値等にする。もしくはlongで定義する.
unsigned int gAccumTemp[5] = {0, 0, 0, 0, 0}; // 積算温度(10倍値)
unsigned int gAccumTemp0[5] = {0, 0, 0, 0, 0}; // 積算温度(10倍値) 0℃基準
byte gAt[5] = {0, 0, 0, 0, 0}; // 5個の積算温度のどれを積算するか
//////////////////////////////////////////////////////
// 温度積算処理
//////////////////////////////////////////////////////
// 1. ログ時の温度を加算
// 2. 日付が変わったら加算した温度を加算回数で割って日平均温度算出
// 3. 算出した平均温度を積算
// 4. 新しい積算値等を前日の日付でバックアップ
// 5. 加算温度、回数をリセット
//////////////////////////////////////////////////////
void dataSekisan(data_t tData, tmElements_t tm) {
// static tmElements_t lastTm = {0, 0, 0, 0, 1, 1, 0}; // 1970/1/1 00:00:00 // 前回加算時の時刻
static tmElements_t lastTm = tm; // 前回加算時の日付
static long kasanData = 0; // 10倍値なのでintだと条件によってはオーバーフローする。25x10x6x24=36,000, 30x10x6x24=43,200
static int dataCount = 0;
lastSekisanData_t lastSekisanData;
byte i;
kasanData += (tData.dt1a * SEKISAN_LEVERAGE); // 10倍値とする。
// Serial.println(kasanData);//OK
dataCount++;
if (tm.Day != lastTm.Day) { // 日付が変わったら日平均温度算出して積算&バックアップ処理
kasanData /= dataCount; // 日平均温度算出。単純に1日の回数(6*24)で割ると初日は正確になるが、いろいろ面倒
EEPROM.get(BACKUP_DATA_ADDRESS, lastSekisanData); // バックアップしていた値を読み出す。
if (lastSekisanData.dataStatus == 0x00) { // バックアップデータ有の場合=積算中は情報更新
lastSekisanData.lastYMD = {lastTm.Year, lastTm.Month, lastTm.Day}; // 日付を更新(前日=平均気温を算出した日)
lastSekisanData.lastDayAvgTemp = kasanData; // 日平均温度を更新(10倍値)
for (i = 0; i < 5; i++) { // 5種類について積算処理&バックアップ処理
gAccumTemp[i] += ((effectiveTemp(kasanData, REF_TEMP) * gAt[i]));
gAccumTemp0[i] += ((effectiveTemp((int)kasanData, 0) * gAt[i]));
lastSekisanData.accumTemp[i] = gAccumTemp[i];
lastSekisanData.accumTemp0[i] = gAccumTemp0[i];
lastSekisanData.at[i] = gAt[i];
}
EEPROM.put(BACKUP_DATA_ADDRESS, lastSekisanData); // バックアップを EEPROMに書き込む。
} // バックアップデータ無しの場合はまだ積算開始していないのでなにもしない。
kasanData = 0;
dataCount = 0;
lastTm = tm;
}
}
/*
void dataSekisanHour(data_t tData, tmElements_t tm) {
// static tmElements_t lastTm = {0, 0, 0, 0, 1, 1, 0}; // 1970/1/1 00:00:00 // 前回加算時の時刻
static tmElements_t lastTm = tm; // 前回加算時の日付
static long kasanData = 0; // 10倍値なのでintだと条件によってはオーバーフローする。
static int dataCount = 0;
static long kasanDataHour = 0;
static int dataCoutHour = 0;
lastSekisanData_t lastSekisanData;
byte i;
kasanData += (tData.dt1a * SEKISAN_LEVERAGE); // 10倍値とする。
// Serial.println(kasanData);//OK
dataCount++;
if (tm.Hour != lastTm.Hour) { // 時が変わったら平均温度算出して積算処理
kasanData /= (dataCount); // 平均温度算出
kasanDataHour += kasanData;
for (i = 0; i < 5; i++) { // 5種類について積算処理&バックアップ処理
gAccumTemp[i] += (((effectiveTemp(kasanData, REF_TEMP) / 24) * gAt[i]));
gAccumTemp0[i] += (((effectiveTemp((int)kasanData, 0) / 24) * gAt[i]));
}
if (tm.Day != lastTm.Day) { // 日付が変わったら日平均温度算出して積算&バックアップ処理
EEPROM.get(BACKUP_DATA_ADDRESS, lastSekisanData); // バックアップしていた値を読み出す。
if (lastSekisanData.dataStatus == 0x00) { // バックアップデータ有の場合=積算中は情報更新
lastSekisanData.lastYMD = {lastTm.Year, lastTm.Month, lastTm.Day}; // 日付を更新(前日=平均気温を算出した日)
lastSekisanData.lastDayAvgTemp = kasanData / ; // 日平均温度を更新(10倍値)
for (i = 0; i < 5; i++) { // 5種類について積算処理&バックアップ処理
lastSekisanData.accumTemp[i] = gAccumTemp[i];
lastSekisanData.accumTemp0[i] = gAccumTemp0[i];
lastSekisanData.at[i] = gAt[i];
}
EEPROM.put(BACKUP_DATA_ADDRESS, lastSekisanData); // バックアップを EEPROMに書き込む。
} // バックアップデータ無しの場合はまだ積算開始していないのでなにもしない。
kasanData = 0;
dataCount = 0;
lastTm = tm;
}
}
*/
int effectiveTemp(int temp, int refTemp) { // 有効温度算出。intなのは整数計算してメモリ節約するため。実際の温度の10倍値を使って精度確保(0.1℃単位)
temp -= refTemp;
if (temp < 0) temp = 0;
return temp;
}
void sekisanStart(tmElements_t tm, byte atNo) { // 最初の積算開始時に、開始する積算番号だけでなく全ての開始日時をリセット(1970/1/1)する。
lastSekisanData_t lastSekisanData;
EEPROM.get(BACKUP_DATA_ADDRESS, lastSekisanData);
if (lastSekisanData.dataStatus != 0x00) { // バックアップデータ無しの場合
resetBackupData();
EEPROM.get(BACKUP_DATA_ADDRESS, lastSekisanData);
}
lastSekisanData.dataStatus = 0x00;
if (gAt[atNo] == 0) {
gAt[atNo] = 1;
if (lastSekisanData.startYMD[atNo].Year == 0) { // リセットしたときの00のままなら、最初の積算開始。一旦積算終了して再開した場合は開始日は元のまま
lastSekisanData.startYMD[atNo].Year = tm.Year;
lastSekisanData.startYMD[atNo].Month = tm.Month;
lastSekisanData.startYMD[atNo].Day = tm.Day;
}
logIcon(true);
} else {
gAt[atNo] = 0;
logIcon(false);
}
lastSekisanData.at[atNo] = gAt[atNo];
EEPROM.put(BACKUP_DATA_ADDRESS, lastSekisanData);
}
///////////// write backup data to EEPROM ////////////////////////
// store last day data for recovery in case of trouble
//////////////////////////////////////////////////////////////////
//void writeBackupData(lastSekisanData_t lastSekisanData) {
// lastSekisanData.dataStatus = 0x00; // 0x00は最終日データ記録有りを示す。
// EEPROM.put(BACKUP_DATA_ADDRESS, lastSekisanData);
//}
bool readBackupData(tmElements_t tm) { // 電源断後の再起動時にAVR内蔵EEPROMから保存データ読み込み。
lastSekisanData_t lastSekisanData;
tmElements_t lastTm;
int i, j;
EEPROM.get(BACKUP_DATA_ADDRESS, lastSekisanData);
if (lastSekisanData.dataStatus != 0x00) { // バックアップデータ無しの場合
// resetBackupData(); // 初期化はしない方が良いか。
return false;
} else { // 最終日データがある場合は、
lastTm = {0, 0, 0, 0, lastSekisanData.lastYMD.Day, lastSekisanData.lastYMD.Month, lastSekisanData.lastYMD.Year};
int dDays = daysDiff(lastTm, tm); // 最終データ日から今日までの日数
// if ((dDays < 0) || (dDays > 30)) { // 日付が戻った場合や30日以上空いた場合 // 起算開始日は最終日データがないので、この処理はしない。
// resetBackupData(); // 初期化
// return false;
// } else { // 最終日データが有効な場合
for (i = 0; i < 5; i++) { // 最終日の平均気温で空いた日の積算概算
gAccumTemp[i] = lastSekisanData.accumTemp[i];
gAccumTemp0[i] = lastSekisanData.accumTemp0[i];
gAt[i] = lastSekisanData.at[i];
for (j = 1; j < dDays; j++) { // 日数分不足データを推計 1日差(前日まで積算あり)の場合は積算しない
gAccumTemp[i] += (effectiveTemp(lastSekisanData.lastDayAvgTemp, REF_TEMP) * gAt[i]);
gAccumTemp0[i] += (effectiveTemp(lastSekisanData.lastDayAvgTemp, 0) * gAt[i]);
}
}
// }
}
return true;
}
void resetBackupData() { // EEPROMバックアップデータを初期化&バックアップ無とする。
lastSekisanData_t lastSekisanData;
lastSekisanData.dataStatus = EM_FORMAT_MARK;
lastSekisanData.lastYMD = {0, 0, 0};
lastSekisanData.lastDayAvgTemp = 0;
for (byte i = 0; i < 5; i++) {
lastSekisanData.startYMD[i] = {0, 0, 0};
lastSekisanData.accumTemp[i] = 0;
lastSekisanData.accumTemp0[i] = 0;
lastSekisanData.startYMD[i] = {0, 0, 0};
gAccumTemp[i] = 0; // 積算温度
gAccumTemp0[i] = 0; // 積算温度 0℃基準
gAt[i] = 0; // 5個の積算温度のどれを積算するか
}
EEPROM.put(BACKUP_DATA_ADDRESS, lastSekisanData);
}
void lcdTime(tmElements_t tm, lcdTimeMode_t mode, char cursorColumn) { // LCD first line
// lastSekisanData_t lastSekisanData;
startYMDdata_t emBackup;
EEPROM.get(BACKUP_DATA_ADDRESS, emBackup);
if (mode != DATA_TIME_MODE){ // メニュー導入画面,時計設定モード時は年も表示
lcd.print(tmYearToCalendar(tm.Year));
lcd.print(F("/"));
}
if (tm.Month < 10) lcd.print(F("0"));
lcd.print(tm.Month);
lcd.print(F("/"));
if (tm.Day < 10) lcd.print(F("0"));
lcd.print(tm.Day);
lcd.print(F(" "));
if (tm.Hour < 10) lcd.print(F("0"));
lcd.print(tm.Hour, DEC);
if (mode != DATA_TIME_MODE){ // メニュー導入画面,時計設定モード時は年も表示
lcd.print(F(":"));
}
if (tm.Minute < 10) lcd.print(F("0"));
lcd.print(tm.Minute, DEC);
lcd.print(F(" "));
if (mode == DATA_TIME_MODE) {
if (emBackup.dataStatus == 0) { // 積算情報がある場合
if (emBackup.startYMD[gDispMode].Month < 10) lcd.print(F("0"));
lcd.print(emBackup.startYMD[gDispMode].Month);
lcd.print(F("/"));
if (emBackup.startYMD[gDispMode].Day < 10) lcd.print(F("0"));
lcd.print(emBackup.startYMD[gDispMode].Day);
}
}
if (cursorColumn >= 0) {
lcd.setCursor(cursorColumn, 0);
lcd.blink();
}
}
//////////////////////////////////////////////////////
// display data to lcd //
//////////////////////////////////////////////////////
void lcdData(data_t tData) {
if (gDispMode >= CONFIG_NO) return; // RTC割り込みでデータ処理後に通常はデータを表示するが、設定表示、メニュー時は表示しない。
logIcon(gAt[gDispMode] == 1);
// printAccumIcon(gAt[atNo] == 1);
lcd.setCursor(0, 1);
lcd.print(gDispMode + 1);
lcd.print(F(" "));
if (tData.dt1a != NULLDATA_MARK) {
if (tData.dt1a < 10) {
lcd.print(F(" "));
}
lcd.print(tData.dt1a, 0);
lcd.print(F("C ")); // 異常値表示時に桁数あふれが出た場合に消すため。
} else {
lcd.print(F("ERR "));
}
lcd.setCursor(6, 1);
lcd.print(F("A"));
printAT(gAccumTemp0[gDispMode]);
lcd.print(F(" "));
printAT(gAccumTemp[gDispMode]);
if (gAt[gDispMode] == 1) {
lcd.setCursor(6, 1);
lcd.blink();
}
}
// 0123456789abcdef
// 06/25 1200 04/20
// 5 30C A0900 0800
//以下は以前の表示
//0123456789ABCDEF
//1:01/23 A00:3456
//12.3C A16:3456
void printAccumIcon(bool isDisp) {
logIcon(isDisp);
}
void printAT(unsigned int AT) {
AT /= SEKISAN_LEVERAGE; // 10倍値なので元に戻す。
if (AT < 1000) lcd.print(F("0"));
if (AT < 100) lcd.print(F("0"));
if (AT < 10) lcd.print(F("0"));
lcd.print(AT);
}
/*
void lcdDate(startYMD_t startYMD, bool dispSlash) {
if (startYMD.Month < 10) {
lcd.print(F("0"));
}
lcd.print(startYMD.Month);
if (dispSlash) {
lcd.print(F("/"));
}
if (startYMD.Day < 10) {
lcd.print(F("0"));
}
lcd.print(startYMD.Day);
}
*/
#endif // SEKISAN