forked from kohlerz/SnowMelt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSnow_Master.ino
476 lines (412 loc) · 12.9 KB
/
Snow_Master.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
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
#include <IridiumSBD.h> // http://librarymanager/All#IridiumSBDI2C
#include <time.h>
#include <DS3232RTC.h> // https://github.com/JChristensen/DS3232RTC
#include <OneWire.h> // https://github.com/adafruit/MAX31850_OneWire
#include <DallasTemperature.h> //https://github.com/adafruit/MAX31850_DallasTemp
#include <SPI.h>
#include <SD.h>
#include <ArduinoJson.h> //https://arduinojson.org/?utm_source=meta&utm_medium=library.properties
#include <StreamUtils.h>
#define VOLTAGE_SENSOR_PIN A0
#define TEMP_PIN 7
#define UTRIG_PIN 13
#define UECHO_PIN 12
#define CS_SD_CARD_PIN 53
#define IridiumSerial Serial1
#define SLEEP_PIN 31
#define DIAGNOSTICS false // Set to true to see diagnostics
#define SCALE1 Serial2
#define SCALE2 Serial3
// Low = receive | High = transmit
#define SCALE1_MODE 3
#define SCALE2_MODE 4
#define SCALE1_POWER 5
#define SCALE2_POWER 6
#define MODE_SEND HIGH
#define MODE_RECV LOW
IridiumSBD modem(IridiumSerial, SLEEP_PIN);
OneWire oneWire(TEMP_PIN);
DallasTemperature temp_sensor(&oneWire);
const int ALARM_INTERVAL_HRS = 12;
const float BAD_VAL = -99.99;
const long DEPTH_OFFSET = 105; //Initial depth with no snow
struct LogData {
char time[21]; //time of measurements
float weight1 = BAD_VAL; //weight on scale1
float weight2 = BAD_VAL; //weight on scale2
float tempC = BAD_VAL; //temp in celcius
float snow_depth = BAD_VAL; //snow depth
float voltage = BAD_VAL; //battery voltage
};
LogData logData;
StaticJsonDocument<128> json_doc;
void setup() {
// Begin Serial communication at a baudrate of 9600:
Serial.begin(115200);
while (!Serial); // wait for serial port to connect. Needed for native USB port only
// Start the serial port connected to the satellite modem
IridiumSerial.begin(19200);
// If we're powering the device by USB, tell the library to
// relax timing constraints waiting for the supercap to recharge.
//modem.setPowerProfile(IridiumSBD::USB_POWER_PROFILE);
// For "high current" (battery-powered) applications
modem.setPowerProfile(IridiumSBD::DEFAULT_POWER_PROFILE);
modem.adjustSendReceiveTimeout(600); //Try to send msg for 10 mins
//test_basic_rock_block_wiring();
SCALE1.begin(9600);
SCALE1.setTimeout(5000);
SCALE2.begin(9600);
SCALE2.setTimeout(5000);
pinMode(SCALE1_POWER, OUTPUT);
pinMode(SCALE2_POWER, OUTPUT);
digitalWrite(SCALE1_POWER, LOW);
digitalWrite(SCALE2_POWER, LOW);
pinMode(SCALE1_MODE, OUTPUT);
pinMode(SCALE2_MODE, OUTPUT);
pinMode(VOLTAGE_SENSOR_PIN, INPUT);
pinMode(UTRIG_PIN, OUTPUT);
pinMode(UECHO_PIN, INPUT);
// Use this block of code to set time on RTC module
//tmElements_t tm;
//tm.Hour = 12;
//tm.Minute = 10;
//tm.Second = 20;
//tm.Day = 17;
//tm.Month = 11;
//tm.Year = 2021 - 1970;
//RTC.write(tm);
Serial.println("RTC time is: ");
digitalClockDisplay();
// initialize the alarms to known values, clear the alarm flags, clear the alarm interrupt flags
RTC.setAlarm(ALM1_MATCH_DATE, 0, 0, 0, 1);
RTC.setAlarm(ALM2_MATCH_DATE, 0, 0, 0, 1);
RTC.alarm(ALARM_1);
RTC.alarm(ALARM_2);
RTC.alarmInterrupt(ALARM_1, false);
RTC.alarmInterrupt(ALARM_2, false);
RTC.squareWave(SQWAVE_NONE);
//RTC.setAlarm(alarmType, minutes, hours, dayOrDate);
RTC.setAlarm(ALM1_MATCH_HOURS, 0, 13, 0);
RTC.alarm(ALARM_1);
//First few reading are bad, so purge them
for (int i = 0; i < 5; i++) {
Serial.println(get_snow_depth(get_temp_celcius()));
}
Serial.println("Setup complete, waiting 30 seconds and then first data will be sent");
delay(30000);
getSaveAndSendData();
}
void loop() {
if ( RTC.alarm(ALARM_1) ) {
time_t t = RTC.get();
digitalClockDisplay();
int new_alarm_time_hr = (hour(t) + ALARM_INTERVAL_HRS) % 24;
Serial.print("Setting alarm to hour");
Serial.println(new_alarm_time_hr);
RTC.setAlarm(ALM1_MATCH_HOURS, 0, new_alarm_time_hr, 0);
RTC.alarm(ALARM_1);
getSaveAndSendData();
}
delay(1000);
}
void getSaveAndSendData() {
Serial.println("Getting measurements...");
json_doc.clear();
time_t t = RTC.get();
int time_size = sprintf(logData.time, "%d-%02d-%02dT%02d:%02d:%02dZ", year(t), month(t), day(t), hour(t), minute(t), second(t));
logData.weight1 = getScale(1);
logData.weight2 = getScale(2);
logData.voltage = get_voltage();
Serial.print("Current battery voltage is: ");
Serial.println(logData.voltage);
logData.tempC = get_temp_celcius();
Serial.print("Current temp is: ");
Serial.println(logData.tempC);
logData.snow_depth = DEPTH_OFFSET - get_snow_depth(logData.tempC);
Serial.print("Current depth is: ");
Serial.println(logData.snow_depth);
create_json_from_data();
write_data_to_log_file();
send_data_to_rock_block();
json_doc.clear();
}
float getScale(int scale) {
float value = BAD_VAL;
setSerialMode(MODE_RECV);
switch (scale) {
case 1:
digitalWrite(SCALE1_POWER, HIGH);
value = readScaleData(SCALE1);
digitalWrite(SCALE1_POWER, LOW);
break;
case 2:
digitalWrite(SCALE2_POWER, HIGH);
value = readScaleData(SCALE2);
digitalWrite(SCALE2_POWER, LOW);
break;
}
return value;
}
float readScaleData(Stream & port) {
// Allows 5 errors because it will occasionally throw an Empty Input Error
for (int i = 0; i < 15; i++) {
setSerialMode(MODE_RECV);
StaticJsonDocument<64> doc;
if (port.available() > 5) {
ReadLoggingStream logging(port, Serial);
DeserializationError err = deserializeJson(doc, logging);
if (err == DeserializationError::Ok) {
const char* status = doc["status"];
return doc["value"];
} else if (err == DeserializationError::EmptyInput) {
// This error is expected b/c deserialze doesn't read the last null byte
Serial.println("Empty input");
} else {
Serial.println("Deserialization error");
}
} else {
Serial.println("Scale data not available. Checking again...");
delay(1000);
}
}
return BAD_VAL;
}
void setSerialMode(int mode) {
digitalWrite(SCALE1_MODE, mode);
digitalWrite(SCALE2_MODE, mode);
delay(50);
}
float get_voltage() {
float R1 = 30000.0;
float R2 = 7500.0;
uint8_t times = 5;
float s[times];
for (uint8_t i = 0; i < times; i++) {
float vout = (analogRead(VOLTAGE_SENSOR_PIN) * 5.0) / 1024.0;
s[i] = (vout / (R2 / (R1 + R2)));
}
insertSort(s, times);
if (times & 0x01) return s[times / 2];
return (s[times / 2] + s[times / 2 + 1]) / 2;
}
float get_temp_celcius() {
uint8_t times = 5;
float s[times];
for (uint8_t i = 0; i < times; i++) {
temp_sensor.requestTemperatures();
s[i] = (temp_sensor.getTempFByIndex(0) - 32) * 5 / 9;
yield();
delay(50);
}
insertSort(s, times);
if (times & 0x01) return s[times / 2];
return (s[times / 2] + s[times / 2 + 1]) / 2;
}
float get_snow_depth(float currTemp) {
unsigned long maxDistanceDurationMicroSec;
int maxDistanceCm = 900;
uint8_t times = 15;
float s[times];
float speedOfSoundInCmPerMicroSec = 0.03313 + 0.0000606 * currTemp; // Cair ≈ (331.3 + 0.606 ⋅ ϑ) m/s
for (uint8_t i = 0; i < times; i++)
{
// Make sure that trigger pin is LOW.
digitalWrite(UTRIG_PIN, LOW);
delayMicroseconds(2);
// Hold trigger for 10 microseconds, which is signal for sensor to measure distance.
digitalWrite(UTRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(UTRIG_PIN, LOW);
// Compute max delay based on max distance with 25% margin in microseconds
maxDistanceDurationMicroSec = 2.5 * maxDistanceCm / speedOfSoundInCmPerMicroSec;
// Measure the length of echo signal, which is equal to the time needed for sound to go there and back.
unsigned long durationMicroSec = pulseIn(UECHO_PIN, HIGH, maxDistanceDurationMicroSec); // can't measure beyond max distance
s[i] = durationMicroSec / 2.0 * speedOfSoundInCmPerMicroSec;
yield();
delay(100);
}
insertSort(s, times);
if (times & 0x01) return s[times / 2];
return (s[times / 2] + s[times / 2 + 1]) / 2;
}
void insertSort(float * array, uint8_t size) {
uint8_t t, z;
float temp;
for (t = 1; t < size; t++)
{
z = t;
temp = array[z];
while ( (z > 0) && (temp < array[z - 1] ))
{
array[z] = array[z - 1];
z--;
}
array[z] = temp;
yield();
}
}
void create_json_from_data() {
json_doc["ts"] = logData.time;
char weight1_buf[8];
if (logData.weight1 == BAD_VAL) {
strcpy(weight1_buf, "NM");
} else {
dtostrf(logData.weight1, 4, 3, weight1_buf);
}
json_doc["w1"] = weight1_buf;
char weight2_buf[8];
if (logData.weight2 == BAD_VAL) {
strcpy(weight2_buf, "NM");
} else {
dtostrf(logData.weight2, 4, 3, weight2_buf);
}
json_doc["w2"] = weight2_buf;
char temp_buf[8];
dtostrf(logData.tempC, 4, 2, temp_buf);
json_doc["t"] = temp_buf;
char snow_buf[8];
dtostrf(logData.snow_depth, 4, 2, snow_buf);
json_doc["d"] = snow_buf;
char voltage_buf[8];
dtostrf(logData.voltage, 4, 2, voltage_buf);
json_doc["v"] = voltage_buf;
serializeJson(json_doc, Serial);
Serial.println("");
}
bool write_data_to_log_file() {
Serial.println("Initializing SD card...");
if (!SD.begin(53)) {
Serial.println("SD card initialization failed!");
return false;
}
Serial.println("initialization done.");
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File myLogDataFile = SD.open("logData.log", FILE_WRITE);
// if the file opened okay, write to it:
if (myLogDataFile) {
Serial.println("Writing to logData.log...");
int err = serializeJson(json_doc, myLogDataFile); //write data to file
if (err == 0) {
Serial.println("Failed to write log data to logData.log");
} else {
myLogDataFile.println("");
}
// close the file:
myLogDataFile.close();
Serial.println("done.");
} else {
// if the file didn't open, print an error:
Serial.println("error opening logData.log");
}
}
void test_basic_rock_block_wiring() {
// Begin satellite modem operation
Serial.println(F("Starting modem..."));
int err = modem.begin();
if (err != ISBD_SUCCESS)
{
Serial.print(F("Begin failed: error "));
Serial.println(err);
if (err == ISBD_NO_MODEM_DETECTED)
Serial.println(F("No modem detected: check wiring."));
return;
}
// Example: Print the firmware revision
char version[12];
err = modem.getFirmwareVersion(version, sizeof(version));
if (err != ISBD_SUCCESS)
{
Serial.print(F("FirmwareVersion failed: error "));
Serial.println(err);
return;
}
Serial.print(F("Firmware Version is "));
Serial.print(version);
Serial.println(F("."));
// Put modem to sleep
Serial.println(F("Putting modem to sleep."));
err = modem.sleep();
if (err != ISBD_SUCCESS) {
Serial.print(F("sleep failed: error "));
Serial.println(err);
} else {
Serial.println(F("Successfully put RockBlock to sleep."));
}
}
void send_data_to_rock_block() {
int err = 0;
// Begin satellite modem operation
Serial.println("Starting modem...");
if (modem.isAsleep()) {
int err = modem.begin();
if (err != ISBD_SUCCESS) {
Serial.print("Begin failed: error ");
Serial.println(err);
if (err == ISBD_NO_MODEM_DETECTED)
Serial.println("No modem detected: check wiring.");
modem.sleep();
return;
}
} else {
Serial.println("Modem was not in sleep mode.");
}
char buffer[128];
serializeJson(json_doc, buffer);
Serial.println(buffer);
err = modem.sendSBDText(buffer);
if (err != ISBD_SUCCESS) {
Serial.print("sendSBDBinary failed: error ");
Serial.println(err);
if (err == ISBD_SENDRECEIVE_TIMEOUT)
Serial.println("Try again with a better view of the sky.");
} else {
Serial.println("Data sent!");
}
// Clear the Mobile Originated message buffer
Serial.println(F("Clearing the MO buffer."));
err = modem.clearBuffers(ISBD_CLEAR_MO); // Clear MO buffer
if (err != ISBD_SUCCESS) {
Serial.print(F("clearBuffers failed: error "));
Serial.println(err);
}
// Put modem to sleep
Serial.println(F("Putting modem to sleep."));
err = modem.sleep();
if (err != ISBD_SUCCESS) {
Serial.print(F("sleep failed: error "));
Serial.println(err);
} else {
Serial.println(F("Successfully put RockBlock to sleep."));
}
return;
}
#if DIAGNOSTICS
void ISBDConsoleCallback(IridiumSBD * device, char c) {
Serial.write(c);
}
void ISBDDiagsCallback(IridiumSBD * device, char c) {
Serial.write(c);
}
#endif
void digitalClockDisplay() {
time_t t = RTC.get();
// digital clock display of the time
Serial.print(hour(t));
printDigits(minute(t));
printDigits(second(t));
Serial.print(' ');
Serial.print(month(t));
Serial.print(' ');
Serial.print(day(t));
Serial.print(' ');
Serial.print(year(t));
Serial.println();
}
void printDigits(int digits) {
// utility function for digital clock display: prints preceding colon and leading 0
Serial.print(':');
if (digits < 10)
Serial.print('0');
Serial.print(digits);
}