dcac3e6364cfe06f9f7157b66b2e816a2749b7bd
[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         try
197         {
198                 tm t;
199                 t.tm_hour = boost::lexical_cast<int>(h);
200                 t.tm_min = boost::lexical_cast<int>(m);
201                 t.tm_sec = boost::lexical_cast<int>(s);
202                 t.tm_mday = boost::lexical_cast<int>(dd);
203                 t.tm_mon = boost::lexical_cast<int>(mm);
204                 t.tm_year = boost::lexical_cast<int>(yy) + 100;
205
206                 time_t time = mktime(&t);
207
208                 BasicPropertyType<double> temp(GPSTIME,(double)time);
209
210                 if(mGpsTime != temp)
211                 {
212                         mGpsTime = temp;
213                         routingEngine->updateProperty(&mGpsTime, mUuid);
214                 }
215         }
216         catch(...)
217         {
218                 DebugOut(DebugOut::Warning)<<"Failed to parse time "<<endl;
219         }
220 }
221
222 void Location::parseLatitude(string d, string m, string ns)
223 {
224         try
225         {
226                 if(d.empty() )
227                         return;
228
229                 double degs = boost::lexical_cast<double>(d + m);
230                 double dec = degsToDecimal(degs);
231
232                 if(ns == "S")
233                         dec *= -1;
234
235                 VehicleProperty::LatitudeType temp(dec);
236
237                 if(mLatitude != temp)
238                 {
239                         mLatitude = temp;\
240                         routingEngine->updateProperty(&mLatitude, mUuid);
241                 }
242         }
243         catch(...)
244         {
245                 DebugOut(DebugOut::Warning)<<"Failed to parse latitude"<<endl;
246         }
247 }
248
249 void Location::parseLongitude(string d, string m, string ew)
250 {
251         try
252         {
253                 if(d.empty()) return;
254
255                 double degs = boost::lexical_cast<double>(d + m);
256                 double dec = degsToDecimal(degs);
257
258                 if(ew == "W")
259                         dec *= -1;
260
261                 VehicleProperty::LongitudeType temp(dec);
262
263                 if(mLongitude != temp)
264                 {
265                         mLongitude = temp;\
266                         routingEngine->updateProperty(&mLongitude, mUuid);
267                 }
268         }
269         catch(...)
270         {
271                 DebugOut(DebugOut::Warning)<<"failed to parse longitude"<<endl;
272         }
273 }
274
275 void Location::parseSpeed(string spd)
276 {
277         try
278         {
279                 double s = boost::lexical_cast<double>(spd);
280
281                 ///to kph:
282                 s *= 1.852;
283                 VehicleProperty::VehicleSpeedType temp(s);
284                 if(mSpeed != temp)
285                 {
286                         mSpeed = temp;
287                         routingEngine->updateProperty(&mSpeed, mUuid);
288                 }
289         }
290         catch(...)
291         {
292                 DebugOut(DebugOut::Warning)<<"failed to parse speed"<<endl;
293         }
294 }
295
296 void Location::parseDirection(string dir)
297 {
298         try {
299                 uint16_t d = boost::lexical_cast<double>(dir);
300
301                 VehicleProperty::DirectionType temp(d);
302                 if(mDirection != temp)
303                 {
304                         mDirection = temp;
305                         routingEngine->updateProperty(&mDirection, mUuid);
306                 }
307         }
308         catch(...)
309         {
310                 DebugOut(DebugOut::Warning)<<"Failed to parse direction: "<<dir<<endl;
311         }
312 }
313
314 void Location::parseAltitude(string alt)
315 {
316         try{
317
318                 if(alt.empty()) return;
319
320                 double a = boost::lexical_cast<double>(alt);
321
322                 VehicleProperty::AltitudeType temp(a);
323                 if(mAltitude != temp)
324                 {
325                         mAltitude = temp;
326                         routingEngine->updateProperty(&mAltitude, mUuid);
327                 }
328
329                 mAltitude = VehicleProperty::AltitudeType(a);
330         }
331         catch(...)
332         {
333                 DebugOut(DebugOut::Warning)<<"failed to parse altitude"<<endl;
334         }
335 }
336
337 double Location::degsToDecimal(double degs)
338 {
339         double deg;
340         double min = 100.0 * modf(degs / 100.0, &deg);
341         return deg + (min / 60.0);
342 }
343
344 bool readCallback(GIOChannel *source, GIOCondition condition, gpointer data)
345 {
346 //      DebugOut(5) << "Polling..." << condition << endl;
347
348         if(condition & G_IO_ERR)
349         {
350                 DebugOut(DebugOut::Error)<<"GpsNmeaSource polling error."<<endl;
351         }
352
353         if (condition & G_IO_HUP)
354         {
355                 //Hang up. Returning false closes out the GIOChannel.
356                 //printf("Callback on G_IO_HUP\n");
357                 DebugOut(DebugOut::Warning)<<"socket hangup event..."<<endl;
358                 return false;
359         }
360
361         GpsNmeaSource* src = static_cast<GpsNmeaSource*>(data);
362
363         src->canHasData();
364
365         return true;
366 }
367
368 extern "C" AbstractSource * create(AbstractRoutingEngine* routingengine, map<string, string> config)
369 {
370         return new GpsNmeaSource(routingengine, config);
371         
372 }
373
374 GpsNmeaSource::GpsNmeaSource(AbstractRoutingEngine *re, map<string, string> config)
375         :AbstractSource(re,config), mUuid("33d86462-1708-4f78-a001-99ea8d55422b")
376 {
377         location =new Location(re, mUuid);
378
379         VehicleProperty::registerProperty(GPSTIME,[](){ return new BasicPropertyType<double>(GPSTIME,0); });
380
381         addPropertySupport(VehicleProperty::Latitude, Zone::None);
382         addPropertySupport(VehicleProperty::Longitude, Zone::None);
383         addPropertySupport(VehicleProperty::Altitude, Zone::None);
384         addPropertySupport(VehicleProperty::VehicleSpeed, Zone::None);
385         addPropertySupport(VehicleProperty::Direction, Zone::None);
386         addPropertySupport(GPSTIME, Zone::None);
387
388
389         ///test:
390
391         if(config.find("test") != config.end())
392         {
393                 Location location(routingEngine, mUuid);
394                 location.parse("GPRMC,061211,A,2351.9605,S,15112.5239,E,000.0,053.4,170303,009.9,E*6E");
395
396                 DebugOut(0)<<"lat: "<<location.latitude().toString()<<endl;
397
398                 g_assert(location.latitude().toString() == "-23.86600833");
399                 g_assert(location.gpsTime().toString() == "1050585131");
400
401                 location.parse("GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47");
402
403                 DebugOut(0)<<"alt: "<<location.altitude().toString()<<endl;
404                 DebugOut(0)<<"lat: "<<location.latitude().toString()<<endl;
405                 g_assert(location.altitude().toString() == "545.4");
406                 g_assert(location.latitude().toString() == "48.1173");
407
408                 location.parse("GPRMC,060136.00,A,3101.40475,N,12126.87095,E,0.760,,160114,,,A*74");
409                 DebugOut(0)<<"lon: "<<location.longitude().toString()<<endl;
410                 DebugOut(0)<<"lat: "<<location.latitude().toString()<<endl;
411                 
412                 //Test incomplete message:
413                 location.parse("GPRMC,023633.00,V,,,,,,,180314,,,N*75");
414                 DebugOut(0)<<"lon: "<<location.longitude().toString()<<endl;
415                 DebugOut(0)<<"lat: "<<location.latitude().toString()<<endl;
416         }
417
418         std::string btaddapter = config["bluetoothAdapter"];
419
420         if(config.find("device")!= config.end())
421         {
422                 std::string dev = config["device"];
423                 if(dev.find(":") != string::npos)
424                 {
425                         BluetoothDevice bt;
426                         dev = bt.getDeviceForAddress(dev, btaddapter);
427                 }
428
429                 device = new SerialPort(dev);
430
431                 if(!device->open())
432                 {
433                         DebugOut(DebugOut::Error)<<"Failed to open gps tty: "<<config["device"]<<endl;
434                         perror("Error");
435                         return;
436                 }
437
438                 DebugOut()<<"read from device: "<<device->read()<<endl;
439
440                 GIOChannel *chan = g_io_channel_unix_new(device->fileDescriptor());
441                 g_io_add_watch(chan, GIOCondition(G_IO_IN | G_IO_HUP | G_IO_ERR),(GIOFunc)readCallback, this);
442                 g_io_channel_set_close_on_unref(chan, true);
443                 g_io_channel_unref(chan); //Pass ownership of the GIOChannel to the watch.
444         }
445 }
446
447 GpsNmeaSource::~GpsNmeaSource()
448 {
449         device->close();
450 }
451
452 const string GpsNmeaSource::uuid()
453 {
454         return mUuid;
455 }
456
457
458 void GpsNmeaSource::getPropertyAsync(AsyncPropertyReply *reply)
459 {
460         DebugOut()<<"GpsNmeaSource: getPropertyAsync called for property: "<<reply->property<<endl;
461
462         std::list<AbstractPropertyType*> f = location->fix();
463
464         for(auto property : f)
465         {
466                 if(property->name == reply->property)
467                 {
468                         reply->success = true;
469                         reply->value = property;
470                         reply->completed(reply);
471                         return;
472                 }
473         }
474
475         reply->success = false;
476         reply->error = AsyncPropertyReply::InvalidOperation;
477         reply->completed(reply);
478 }
479
480 void GpsNmeaSource::getRangePropertyAsync(AsyncRangePropertyReply *reply)
481 {
482
483 }
484
485 AsyncPropertyReply *GpsNmeaSource::setProperty(AsyncSetPropertyRequest request )
486 {
487
488 }
489
490 void GpsNmeaSource::subscribeToPropertyChanges(VehicleProperty::Property property)
491 {
492         mRequests.push_back(property);
493 }
494
495 PropertyList GpsNmeaSource::supported()
496 {
497         return mSupported;
498 }
499
500 int GpsNmeaSource::supportedOperations()
501 {
502         return Get;
503 }
504
505 void GpsNmeaSource::canHasData()
506 {
507         std::string data = device->read();
508
509         std::vector<std::string> lines;
510
511         boost::split(lines,data,boost::is_any_of("$"));
512
513         for(int i = 0; i < lines.size(); i++)
514         {
515                 if(checksum(lines[i]))
516                 {
517                         buffer = lines[i];
518                 }
519                 else
520                 {
521                         buffer += lines[i];
522                 }
523
524                 if(checksum(buffer))
525                 {
526                         /// we have a complete message.  parse it!
527                         DebugOut(7)<<"Complete message: "<<buffer<<endl;
528                         location->parse(buffer);
529                 }
530
531                 DebugOut(7)<<"buffer: "<<buffer<<endl;
532
533         }
534 }
535
536 void GpsNmeaSource::unsubscribeToPropertyChanges(VehicleProperty::Property property)
537 {
538         mRequests.remove(property);
539 }
540
541 void GpsNmeaSource::addPropertySupport(VehicleProperty::Property property, Zone::Type zone)
542 {
543         mSupported.push_back(property);
544
545         std::list<Zone::Type> zones;
546
547         zones.push_back(zone);
548
549         PropertyInfo info(0, zones);
550
551         propertyInfoMap[property] = info;
552 }
553
554 bool GpsNmeaSource::checksum(std::string sentence)
555 {
556         if(sentence.empty() || sentence.length() < 4)
557         {
558                 return false;
559         }
560
561         int checksum = 0;
562
563         for(auto i : sentence)
564         {
565                 if(i == '*')
566                         break;
567                 if(i != '\n' || i != '\r')
568                         checksum ^= i;
569         }
570
571         std::string sentenceCheckStr = sentence.substr(sentence.length()-4,2);
572
573         try
574         {
575                 int sentenceCheck = lexical_cast<int>(sentenceCheckStr);
576
577                 return sentenceCheck == checksum;
578         }
579         catch(...)
580
581         {
582                 return false;
583         }
584 }