-
Notifications
You must be signed in to change notification settings - Fork 3
/
geofence.pde
361 lines (295 loc) · 9.61 KB
/
geofence.pde
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
#include <TinyGPS++.h>
#include <FlowCommandHandler.h>
#include <XMLNode.h>
extern "C" {
#include "flow/flowmessaging.h"
}
// The Datastore class wraps the Flow datastore in a C++ object and
// allows us easy access to load, clear and to save a XML node
// This class is implemented in Datastore.pde
class DataStore
{
public:
DataStore(char *name);
bool load();
bool clear(char *string);
bool save(XMLNode &node);
private:
char *name;
FlowDataStore _datastore;
};
// Store the 3 components of the geofence
struct Geofence
{
double latitude;
double longitude;
double radius;
};
/* We want to map pins 5 and 7 to Serial2 (for RX and TX respectively).
Serial2 uses using the WiFire's UART6 and both pin 5 and 7 support being mapped
to the relevant peripheral (U6RX and U6TX) .
Note that pin 5 is chosen (actually it is already mapped to U6RX by default)
as it is a 5v tolerant pin, so the UART chip connected may operate at 5v levels.
It is also necessary that the UART chip accepts 3v3 as a logic HIGH as this
is the logic HIGH provided by pin 7.
For this example the MAX232 is used, which operates at 5v but accepts 3v3 as HIGH */
#define SERIAL2_RX_PIN (5)
#define SERIAL2_TX_PIN (7)
#define BTN1 (46)
#define BTN2 (47)
// The GPS module we will be using uses a 9600-baud RS232 connection
#define GPSBaud (9600)
// Object for the tinyGPS++ library we are using
TinyGPSPlus gps;
// Access to the Flow datastore named "GPSReading"
DataStore datastore("GPSReading");
// logging period, the time between each write to the datastore
int loggingPeriod = 2 * 60 * 1000;
#define GET_LOCATION ("GET LOCATION")
#define SET_PERIOD ("SET PERIOD")
#define GET_PERIOD ("GET PERIOD")
#define SET_GEOFENCE ("SET GEOFENCE")
#define GET_GEOFENCE ("GET GEOFENCE")
#define GEOFENCE_ESCAPED ("GEOFENCE ESCAPED")
// Initially no fence
Geofence fence = {0};
void setup()
{
// use the same baud rate as the boot console so that we don't have to change the
// serial connection baud rate
Serial.begin(115200);
// We are not reading from Serial and don't mind sharing it with libappbase
g_EnableConsole = true;
g_EnableConsoleInput = true;
Serial.println("RS232_GPS.ino ");
Serial.print("Using TinyGPS++ library v. ");
Serial.println(TinyGPSPlus::libraryVersion());
Serial.print("Bringing up GPS serial connection... ");
// remap our desired RX and TX pins to U6 (corresponding to Serial2)
mapPps(SERIAL2_RX_PIN, PPS_IN_U6RX);
mapPps(SERIAL2_TX_PIN, PPS_OUT_U6TX);
Serial2.begin(GPSBaud);
Serial.println("OK!");
/* pin 29 is by default used for PPS_OUT_U6TX. We can leave it as this
* as well as pin 7, remap it to some other peripheral or we can just
* use it as a GPIO with
pinMode(29, OUTPUT);
* or
pinMode(29, INPUT);
*/
// Attach the various command handlers
FlowCommandHandler.attach(GET_LOCATION, getLocation);
FlowCommandHandler.attach(SET_PERIOD, setPeriod);
FlowCommandHandler.attach(GET_PERIOD, getPeriod);
FlowCommandHandler.attach(SET_GEOFENCE, setGeofence);
FlowCommandHandler.attach(GET_GEOFENCE, getGeofence);
// Load the datastore from FlowCloud
Serial.println("Fetching datastore from FlowCloud... ");
if (!datastore.load()){
Serial.println("FAILED!");
for(;;);
}
// Enable LEDs for output
pinMode(PIN_LED1, OUTPUT);
pinMode(PIN_LED2, OUTPUT);
pinMode(PIN_LED3, OUTPUT);
pinMode(PIN_LED4, OUTPUT);
Serial.println();
Serial.println();
}
// command handler for fetching the current location
void getLocation(ReadableXMLNode ¶ms, XMLNode &response)
{
if (gps.location.isValid())
{
XMLNode &reading = response.addChild("gpsreading");
readGPSToXML(reading);
}
}
// command handler for fetching the current logging period
void getPeriod(ReadableXMLNode ¶ms, XMLNode &response)
{
XMLNode &period = response.addChild("period");
period.setContent(loggingPeriod);
}
// command handler for setting the period
void setPeriod(ReadableXMLNode ¶ms, XMLNode &response)
{
loggingPeriod = params.getChild("period").getIntegerValue();
}
// command handler for setting the geofence
void setGeofence(ReadableXMLNode ¶ms, XMLNode &response)
{
fence.latitude = params.getChild("geofence/location/latitude").getFloatValue();
fence.longitude = params.getChild("geofence/location/longitude").getFloatValue();
fence.radius = params.getChild("geofence/radius").getFloatValue();
}
// command handler for getting the existing geofence
void getGeofence(ReadableXMLNode ¶ms, XMLNode &response)
{
XMLNode &geofence = response.addChild("geofence");
XMLNode &location = geofence.addChild("location");
location.addChild("latitude").setContent(fence.latitude, 9);
location.addChild("longitude").setContent(fence.longitude, 9);
geofence.addChild("radius").setContent(fence.radius, 5);
}
void setDatetimeContent(XMLNode &node)
{
// create a sting for the current datetime
#define DATETIME_FIELD_LENGTH 32
char datetimeStr[DATETIME_FIELD_LENGTH];
time_t currentDateTimeSeconds;
Flow_GetTime(¤tDateTimeSeconds);
struct tm *currentDateTimeUTC = gmtime(¤tDateTimeSeconds);
strftime(datetimeStr, DATETIME_FIELD_LENGTH, "%Y-%m-%dT%H:%M:%SZ", currentDateTimeUTC);
node.setContent(datetimeStr);
}
// read the current GPS location from the NMEA library to a XML node
void readGPSToXML(XMLNode &xml)
{
XMLNode &readingTime = xml.addChild("gpsreadingtime");
readingTime.addAttribute("type", "datetime");
readingTime.addAttribute("index", "true");
setDatetimeContent(readingTime);
XMLNode &location = xml.addChild("location");
XMLNode &lat = location.addChild("latitude");
lat.setContent(gps.location.lat(), 9);
XMLNode &lng = location.addChild("longitude");
lng.setContent(gps.location.lng(), 9);
XMLNode &readingAge = xml.addChild("readingage");
readingAge.setContent(gps.location.age());
XMLNode &satellites = xml.addChild("satellites");
satellites.setContent(gps.satellites.value());
XMLNode &altitude = xml.addChild("altitude");
altitude.addAttribute("unit", "meters");
altitude.setContent(gps.altitude.meters(), 3);
XMLNode &speed = xml.addChild("speed");
speed.addAttribute("unit", "mps");
speed.setContent(gps.speed.mps(), 3);
XMLNode &course = xml.addChild("course");
course.setContent(gps.course.deg(), 3);
XMLNode &hdop = xml.addChild("hdop");
hdop.setContent(gps.hdop.value(), 3);
}
// save the current GPS location to the FlowCloud datastore
bool saveReadingToDatastore(void)
{
bool result = false;
if (gps.location.isValid())
{
XMLNode reading("gpsreading");
readGPSToXML(reading);
if (datastore.save(reading))
{
result = true;
}
}
else
{
Serial.println("Not writing to datastore - location invalid");
}
return result;
}
void clearOldReadings(void)
{
char clearCmd[256];
strcpy(clearCmd, "@gpsreadingtime >= '");
char datetimeStr[DATETIME_FIELD_LENGTH];
time_t currentDateTimeSeconds;
// can we make this actually remove all but 40 items
currentDateTimeSeconds -= (loggingPeriod / 1000) * 40; // ~40 items in history
Flow_GetTime(¤tDateTimeSeconds);
struct tm *currentDateTimeUTC = gmtime(¤tDateTimeSeconds);
strftime(datetimeStr, DATETIME_FIELD_LENGTH, "%Y-%m-%dT%H:%M:%SZ", currentDateTimeUTC);
strcat(clearCmd, datetimeStr);
strcat(clearCmd, "'");
datastore.clear(clearCmd);
Serial.print("Clearing old datastore items ");
Serial.println(clearCmd);
}
void sendAlert(const char *type)
{
XMLNode alert("alert");
XMLNode &sent = alert.addChild("sent");
setDatetimeContent(sent);
XMLNode &typeNode = alert.addChild("type");
typeNode.setContent(type);
StringBuilder alertStringBuilder = StringBuilder_New(512);
alertStringBuilder = StringBuilder_Append(alertStringBuilder, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
alert.appendTo(alertStringBuilder);
FlowMessaging_SendMessageToUser(
g_OwnerID,
"text/plain",
(char *)StringBuilder_GetCString(alertStringBuilder),
StringBuilder_GetLength(alertStringBuilder),
120
);
StringBuilder_Free(&alertStringBuilder);
}
void loop()
{
// record the time of the las save so we know when to next save
// initially set this to long enough ago that we will always save
// the current location when we first start up
static long lastSave = -2*loggingPeriod;
static int count = 0;
while (Serial2.available() > 0)
{
if (gps.encode(Serial2.read()))
{
// if the configured period of time has passed then save a new reading
if (millis() - lastSave > loggingPeriod)
{
lastSave = millis();
// periodically clear old readings from the database
if (count++ > 10)
{
count = 0;
clearOldReadings();
}
// save a new reading
if (!saveReadingToDatastore())
{
Serial.println("Save to datastore failed");
}
}
}
}
static bool insideFence = true;
static long lastInsideFence = 0;
static long lastOutsideFence = 0;
#define FENCE_TIME_THRESHOLD (10*1000)
if (gps.location.isValid() && fence.radius > 0)
{
// check for escaped geofence
double distance = gps.distanceBetween(fence.latitude, fence.longitude, gps.location.lat(), gps.location.lng());
if (distance > fence.radius)
{
// escaped
lastOutsideFence = millis();
digitalWrite(PIN_LED1, HIGH);
if (insideFence && lastOutsideFence - lastInsideFence > FENCE_TIME_THRESHOLD)
{
digitalWrite(PIN_LED2, HIGH);
insideFence = false;
sendAlert(GEOFENCE_ESCAPED);
}
}
else
{
lastInsideFence = millis();
digitalWrite(PIN_LED1, LOW);
if (!insideFence && lastInsideFence - lastOutsideFence > FENCE_TIME_THRESHOLD)
{
digitalWrite(PIN_LED2, LOW);
insideFence = true;
}
}
}
// allow a manual clear of the database
if (digitalRead(BTN1) == HIGH){
Serial.println("Deleting datestore");
// match all times
datastore.clear("@gpsreadingtime >= '1900-01-01T00:00:00Z'");
}
}