29335cc182cd7b550e8e1abd215f25c7c3a89f5d
[profile/ivi/automotive-message-broker.git] / plugins / gpsnmea / gpsnmea.cpp
1 /*
2 Copyright (C) 2012 Intel Corporation
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "gpsnmea.h"
20 #include "timestamp.h"
21 #include "serialport.hpp"
22 #include "bluetooth.hpp"
23
24 #include <iostream>
25 #include <boost/assert.hpp>
26 #include <boost/algorithm/string.hpp>
27 #include <time.h>
28
29
30 using namespace std;
31
32 #include "debugout.h"
33 #include "abstractpropertytype.h"
34
35 #define GPSTIME "GpsTime"
36
37 template<typename T2>
38 inline T2 lexical_cast(const std::string &in) {
39         T2 out;
40         std::stringstream ss;
41         ss << std::hex << in;
42         ss >> out;
43         return out;
44 }
45
46 class Location
47 {
48 public:
49         Location(AbstractRoutingEngine* re, std::string uuid);
50
51         void parse(std::string gprmc);
52
53         VehicleProperty::LatitudeType latitude()
54         {
55                 return mLatitude;
56         }
57
58         VehicleProperty::LongitudeType longitude()
59         {
60                 return mLongitude;
61         }
62
63         VehicleProperty::AltitudeType altitude()
64         {
65                 return mAltitude;
66         }
67
68         VehicleProperty::DirectionType direction()
69         {
70                 return mDirection;
71         }
72
73         VehicleProperty::VehicleSpeedType speed()
74         {
75                 return mSpeed;
76         }
77
78         BasicPropertyType<double> gpsTime()
79         {
80                 return mGpsTime;
81         }
82
83         std::list<AbstractPropertyType*> fix()
84         {
85                 std::list<AbstractPropertyType*> l;
86
87                 l.push_back(&mLatitude);
88                 l.push_back(&mLongitude);
89                 l.push_back(&mAltitude);
90                 l.push_back(&mDirection);
91                 l.push_back(&mSpeed);
92                 l.push_back(&mGpsTime);
93
94                 return l;
95         }
96
97 private: ///methods:
98
99         void parseGprmc(string gprmc);
100         void parseGpgga(string gpgga);
101
102         void parseTime(std::string h, std::string m, std::string s, string dd, string mm, string yy);
103         void parseLatitude(std::string d, std::string m, std::string ns);
104         void parseLongitude(std::string d, string m, string ew);
105         void parseSpeed(std::string spd);
106         void parseDirection(std::string dir);
107         void parseAltitude(std::string alt);
108
109         double degsToDecimal(double degs);
110
111 private:
112
113         VehicleProperty::LatitudeType mLatitude;
114         VehicleProperty::LongitudeType mLongitude;
115         VehicleProperty::AltitudeType mAltitude;
116         VehicleProperty::DirectionType  mDirection;
117         VehicleProperty::VehicleSpeedType mSpeed;
118         BasicPropertyType<double> mGpsTime;
119
120         bool isActive;
121
122         std::string mUuid;
123
124         AbstractRoutingEngine* routingEngine;
125
126 };
127
128 Location::Location(AbstractRoutingEngine* re, std::string uuid)
129         :mLatitude(0), mLongitude(0), mAltitude(0), mDirection(0), mSpeed(0), mGpsTime(GPSTIME,0), isActive(false), routingEngine(re), mUuid(uuid)
130 {
131
132 }
133
134 void Location::parse(string nmea)
135 {
136         if(boost::algorithm::starts_with(nmea,"GPRMC"))
137         {
138                 parseGprmc(nmea);
139         }
140         else if(boost::algorithm::starts_with(nmea,"GPGGA"))
141         {
142                 parseGpgga(nmea);
143         }
144         else
145         {
146                 DebugOut(DebugOut::Warning)<<"unknown/unhandled message: "<<nmea<<endl;
147         }
148 }
149
150 void Location::parseGprmc(string gprmc)
151 {
152         DebugOut(7)<<"parsing gprmc message"<<endl;
153
154         std::vector<std::string> tokens;
155         boost::split(tokens, gprmc, boost::is_any_of(","));
156
157         if(tokens[2] == "A")
158         {
159                 isActive = true;
160         }
161
162         parseTime(tokens[1].substr(0,2),tokens[1].substr(2,2),tokens[1].substr(4,2),tokens[9].substr(0,2),tokens[9].substr(2,2),tokens[9].substr(4,2));
163
164         parseLatitude(tokens[3], "", tokens[4]);
165         parseLongitude(tokens[5], "", tokens[6]);
166         parseSpeed(tokens[7]);
167         parseDirection(tokens[8]);
168
169 }
170
171 void Location::parseGpgga(string gpgga)
172 {
173
174         std::vector<std::string> tokens;
175         boost::split(tokens, gpgga, boost::is_any_of(","));
176
177         if(tokens.size() != 15)
178         {
179                 DebugOut()<<"Invalid GPGGA message: "<<gpgga<<endl;
180                 return;
181         }
182
183         parseLatitude(tokens[2],"",tokens[3]);
184         parseLongitude(tokens[4],"",tokens[5]);
185         if(tokens[6] != "0")
186         {
187                 isActive = true;
188         }
189         else isActive = false;
190
191         parseAltitude(tokens[9]);
192 }
193
194 void Location::parseTime(string h, string m, string s, string dd, string mm, string yy)
195 {
196         tm t;
197         t.tm_hour = boost::lexical_cast<int>(h);
198         t.tm_min = boost::lexical_cast<int>(m);
199         t.tm_sec = boost::lexical_cast<int>(s);
200         t.tm_mday = boost::lexical_cast<int>(dd);
201         t.tm_mon = boost::lexical_cast<int>(mm);
202         t.tm_year = boost::lexical_cast<int>(yy) + 100;
203
204         time_t time = mktime(&t);
205
206         BasicPropertyType<double> temp(GPSTIME,(double)time);
207
208         if(mGpsTime != temp)
209         {
210                 mGpsTime = temp;
211                 routingEngine->updateProperty(&mGpsTime, mUuid);
212         }
213 }
214
215 void Location::parseLatitude(string d, string m, string ns)
216 {
217         try
218         {
219                 if(d.empty() )
220                         return;
221
222                 double degs = boost::lexical_cast<double>(d + m);
223                 double dec = degsToDecimal(degs);
224
225                 if(ns == "S")
226                         dec *= -1;
227
228                 VehicleProperty::LatitudeType temp(dec);
229
230                 if(mLatitude != temp)
231                 {
232                         mLatitude = temp;\
233                         routingEngine->updateProperty(&mLatitude, mUuid);
234                 }
235         }
236         catch(...)
237         {
238                 DebugOut(DebugOut::Warning)<<"Failed to parse latitude"<<endl;
239         }
240 }
241
242 void Location::parseLongitude(string d, string m, string ew)
243 {
244         try
245         {
246                 if(d.empty()) return;
247
248                 double degs = boost::lexical_cast<double>(d + m);
249                 double dec = degsToDecimal(degs);
250
251                 if(ew == "W")
252                         dec *= -1;
253
254                 VehicleProperty::LongitudeType temp(dec);
255
256                 if(mLongitude != temp)
257                 {
258                         mLongitude = temp;\
259                         routingEngine->updateProperty(&mLongitude, mUuid);
260                 }
261         }
262         catch(...)
263         {
264                 DebugOut(DebugOut::Warning)<<"failed to parse longitude"<<endl;
265         }
266 }
267
268 void Location::parseSpeed(string spd)
269 {
270         double s = boost::lexical_cast<double>(spd);
271
272         ///to kph:
273         s *= 1.852;
274         VehicleProperty::VehicleSpeedType temp(s);
275         if(mSpeed != temp)
276         {
277                 mSpeed = temp;
278                 routingEngine->updateProperty(&mSpeed, mUuid);
279         }
280 }
281
282 void Location::parseDirection(string dir)
283 {
284         try {
285                 uint16_t d = boost::lexical_cast<double>(dir);
286
287                 VehicleProperty::DirectionType temp(d);
288                 if(mDirection != temp)
289                 {
290                         mDirection = temp;
291                         routingEngine->updateProperty(&mDirection, mUuid);
292                 }
293         }
294         catch(...)
295         {
296                 DebugOut(DebugOut::Warning)<<"Failed to parse direction: "<<dir<<endl;
297         }
298 }
299
300 void Location::parseAltitude(string alt)
301 {
302         try{
303
304                 if(alt.empty()) return;
305
306                 double a = boost::lexical_cast<double>(alt);
307
308                 VehicleProperty::AltitudeType temp(a);
309                 if(mAltitude != temp)
310                 {
311                         mAltitude = temp;
312                         routingEngine->updateProperty(&mAltitude, mUuid);
313                 }
314
315                 mAltitude = VehicleProperty::AltitudeType(a);
316         }
317         catch(...)
318         {
319                 DebugOut(DebugOut::Warning)<<"failed to parse altitude"<<endl;
320         }
321 }
322
323 double Location::degsToDecimal(double degs)
324 {
325         double deg;
326         double min = 100.0 * modf(degs / 100.0, &deg);
327         return deg + (min / 60.0);
328 }
329
330 bool readCallback(GIOChannel *source, GIOCondition condition, gpointer data)
331 {
332 //      DebugOut(5) << "Polling..." << condition << endl;
333
334         if(condition & G_IO_ERR)
335         {
336                 DebugOut(DebugOut::Error)<<"GpsNmeaSource polling error."<<endl;
337         }
338
339         if (condition & G_IO_HUP)
340         {
341                 //Hang up. Returning false closes out the GIOChannel.
342                 //printf("Callback on G_IO_HUP\n");
343                 DebugOut(DebugOut::Warning)<<"socket hangup event..."<<endl;
344                 return false;
345         }
346
347         GpsNmeaSource* src = static_cast<GpsNmeaSource*>(data);
348
349         src->canHasData();
350
351         return true;
352 }
353
354 extern "C" AbstractSource * create(AbstractRoutingEngine* routingengine, map<string, string> config)
355 {
356         return new GpsNmeaSource(routingengine, config);
357         
358 }
359
360 GpsNmeaSource::GpsNmeaSource(AbstractRoutingEngine *re, map<string, string> config)
361         :AbstractSource(re,config), mUuid("33d86462-1708-4f78-a001-99ea8d55422b")
362 {
363         location =new Location(re, mUuid);
364
365         VehicleProperty::registerProperty(GPSTIME,[](){ return new BasicPropertyType<double>(GPSTIME,0); });
366
367         addPropertySupport(VehicleProperty::Latitude, Zone::None);
368         addPropertySupport(VehicleProperty::Longitude, Zone::None);
369         addPropertySupport(VehicleProperty::Altitude, Zone::None);
370         addPropertySupport(VehicleProperty::VehicleSpeed, Zone::None);
371         addPropertySupport(VehicleProperty::Direction, Zone::None);
372         addPropertySupport(GPSTIME, Zone::None);
373
374
375         ///test:
376
377         if(config.find("test") != config.end())
378         {
379                 Location location(routingEngine, mUuid);
380                 location.parse("GPRMC,061211,A,2351.9605,S,15112.5239,E,000.0,053.4,170303,009.9,E*6E");
381
382                 DebugOut(0)<<"lat: "<<location.latitude().toString()<<endl;
383
384                 g_assert(location.latitude().toString() == "-23.86600833");
385                 g_assert(location.gpsTime().toString() == "1050585131");
386
387                 location.parse("GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47");
388
389                 DebugOut(0)<<"alt: "<<location.altitude().toString()<<endl;
390                 DebugOut(0)<<"lat: "<<location.latitude().toString()<<endl;
391                 g_assert(location.altitude().toString() == "545.4");
392                 g_assert(location.latitude().toString() == "48.1173");
393
394                 location.parse("GPRMC,060136.00,A,3101.40475,N,12126.87095,E,0.760,,160114,,,A*74");
395                 DebugOut(0)<<"lon: "<<location.longitude().toString()<<endl;
396                 DebugOut(0)<<"lat: "<<location.latitude().toString()<<endl;
397         }
398
399         std::string btaddapter = config["bluetoothAdapter"];
400
401         if(config.find("device")!= config.end())
402         {
403                 std::string dev = config["device"];
404                 if(dev.find(":") != string::npos)
405                 {
406                         BluetoothDevice bt;
407                         dev = bt.getDeviceForAddress(dev, btaddapter);
408                 }
409
410                 device = new SerialPort(dev);
411
412                 if(!device->open())
413                 {
414                         DebugOut(DebugOut::Error)<<"Failed to open gps tty: "<<config["device"]<<endl;
415                         perror("Error");
416                         return;
417                 }
418
419                 DebugOut()<<"read from device: "<<device->read()<<endl;
420
421                 GIOChannel *chan = g_io_channel_unix_new(device->fileDescriptor());
422                 g_io_add_watch(chan, GIOCondition(G_IO_IN | G_IO_HUP | G_IO_ERR),(GIOFunc)readCallback, this);
423                 g_io_channel_set_close_on_unref(chan, true);
424                 g_io_channel_unref(chan); //Pass ownership of the GIOChannel to the watch.
425         }
426
427         re->setSupported(supported(), this);
428 }
429
430 GpsNmeaSource::~GpsNmeaSource()
431 {
432         device->close();
433 }
434
435 const string GpsNmeaSource::uuid()
436 {
437         return mUuid;
438 }
439
440
441 void GpsNmeaSource::getPropertyAsync(AsyncPropertyReply *reply)
442 {
443         DebugOut()<<"GpsNmeaSource: getPropertyAsync called for property: "<<reply->property<<endl;
444
445         std::list<AbstractPropertyType*> f = location->fix();
446
447         for(auto property : f)
448         {
449                 if(property->name == reply->property)
450                 {
451                         reply->success = true;
452                         reply->value = property;
453                         reply->completed(reply);
454                         return;
455                 }
456         }
457
458         reply->success = false;
459         reply->error = AsyncPropertyReply::InvalidOperation;
460         reply->completed(reply);
461 }
462
463 void GpsNmeaSource::getRangePropertyAsync(AsyncRangePropertyReply *reply)
464 {
465
466 }
467
468 AsyncPropertyReply *GpsNmeaSource::setProperty(AsyncSetPropertyRequest request )
469 {
470
471 }
472
473 void GpsNmeaSource::subscribeToPropertyChanges(VehicleProperty::Property property)
474 {
475         mRequests.push_back(property);
476 }
477
478 PropertyList GpsNmeaSource::supported()
479 {
480         return mSupported;
481 }
482
483 int GpsNmeaSource::supportedOperations()
484 {
485         return Get;
486 }
487
488 void GpsNmeaSource::canHasData()
489 {
490         std::string data = device->read();
491
492         std::vector<std::string> lines;
493
494         boost::split(lines,data,boost::is_any_of("$"));
495
496         for(int i = 0; i < lines.size(); i++)
497         {
498                 if(checksum(lines[i]))
499                 {
500                         buffer = lines[i];
501                 }
502                 else
503                 {
504                         buffer += lines[i];
505                 }
506
507                 if(checksum(buffer))
508                 {
509                         /// we have a complete message.  parse it!
510                         location->parse(buffer);
511                 }
512
513                 DebugOut(7)<<"buffer: "<<buffer<<endl;
514
515         }
516 }
517
518 void GpsNmeaSource::unsubscribeToPropertyChanges(VehicleProperty::Property property)
519 {
520         mRequests.remove(property);
521 }
522
523 void GpsNmeaSource::addPropertySupport(VehicleProperty::Property property, Zone::Type zone)
524 {
525         mSupported.push_back(property);
526
527         std::list<Zone::Type> zones;
528
529         zones.push_back(zone);
530
531         PropertyInfo info(0, zones);
532
533         propertyInfoMap[property] = info;
534 }
535
536 bool GpsNmeaSource::checksum(std::string sentence)
537 {
538         if(sentence.empty() || sentence.length() < 4)
539         {
540                 return false;
541         }
542
543         int checksum = 0;
544
545         for(auto i : sentence)
546         {
547                 if(i == '*')
548                         break;
549                 if(i != '\n' || i != '\r')
550                         checksum ^= i;
551         }
552
553         std::string sentenceCheckStr = sentence.substr(sentence.length()-4,2);
554
555         try
556         {
557                 int sentenceCheck = lexical_cast<int>(sentenceCheckStr);
558
559                 return sentenceCheck == checksum;
560         }
561         catch(...)
562
563         {
564                 return false;
565         }
566 }